diff --git a/apps/api/src/changelog/version/byId.ts b/apps/api/src/changelog/version/byId.ts index 0eee95f..194a3dc 100644 --- a/apps/api/src/changelog/version/byId.ts +++ b/apps/api/src/changelog/version/byId.ts @@ -3,6 +3,11 @@ import { VersionByIdParams, VersionOutput } from '@boring.tools/schema' import { createRoute } from '@hono/zod-openapi' import { and, eq } from 'drizzle-orm' +import { HTTPException } from 'hono/http-exception' +import type changelogVersionApi from '.' +import { verifyAuthentication } from '../../utils/authentication' +import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi' + export const byId = createRoute({ method: 'get', path: '/:id', @@ -19,49 +24,41 @@ export const byId = createRoute({ }, description: 'Return version by id', }, - 400: { - description: 'Bad Request', - }, - 500: { - description: 'Internal Server Error', - }, + ...openApiErrorResponses, }, + ...openApiSecurity, }) -export const byIdFunc = async ({ - userId, - id, -}: { - userId: string - id: string -}) => { - const versionResult = await db.query.changelog_version.findFirst({ - where: eq(changelog_version.id, id), - with: { - commits: true, - }, +export const registerVersionById = (api: typeof changelogVersionApi) => { + return api.openapi(byId, async (c) => { + const userId = await verifyAuthentication(c) + const { id } = c.req.valid('param') + + const versionResult = await db.query.changelog_version.findFirst({ + where: eq(changelog_version.id, id), + }) + + if (!versionResult) { + throw new HTTPException(404, { message: 'Not Found' }) + } + + if (!versionResult.changelogId) { + throw new HTTPException(404, { message: 'Not Found' }) + } + + const changelogResult = await db.query.changelog.findMany({ + where: and(eq(changelog.userId, userId)), + columns: { + id: true, + }, + }) + + const changelogIds = changelogResult.map((cl) => cl.id) + + if (!changelogIds.includes(versionResult.changelogId)) { + throw new HTTPException(404, { message: 'Not Found' }) + } + + return c.json(VersionOutput.parse(versionResult), 200) }) - - if (!versionResult) { - return null - } - - if (!versionResult.changelogId) { - return null - } - - const changelogResult = await db.query.changelog.findMany({ - where: and(eq(changelog.userId, userId)), - columns: { - id: true, - }, - }) - - const changelogIds = changelogResult.map((cl) => cl.id) - - if (!changelogIds.includes(versionResult.changelogId)) { - return null - } - - return versionResult } diff --git a/apps/api/src/changelog/version/create.ts b/apps/api/src/changelog/version/create.ts index 36f15cd..74795ee 100644 --- a/apps/api/src/changelog/version/create.ts +++ b/apps/api/src/changelog/version/create.ts @@ -9,9 +9,13 @@ import { createRoute, type z } from '@hono/zod-openapi' import { and, eq, inArray } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' import semver from 'semver' + +import type changelogVersionApi from '.' +import { verifyAuthentication } from '../../utils/authentication' +import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi' import { redis } from '../../utils/redis' -export const create = createRoute({ +export const route = createRoute({ method: 'post', path: '/', tags: ['version'], @@ -29,76 +33,71 @@ export const create = createRoute({ }, description: 'Version created', }, - 400: { - description: 'Bad Request', - }, - 500: { - description: 'Internal Server Error', - }, + ...openApiErrorResponses, }, + ...openApiSecurity, }) -export const createFunc = async ({ - userId, - payload, -}: { - userId: string - payload: z.infer -}) => { - const changelogResult = await db.query.changelog.findFirst({ - where: and( - eq(changelog.userId, userId), - eq(changelog.id, payload.changelogId), - ), - with: { - versions: { - where: and( - eq(changelog_version.changelogId, payload.changelogId), - eq(changelog_version.version, payload.version), - ), +export const registerVersionCreate = (api: typeof changelogVersionApi) => { + return api.openapi(route, async (c) => { + const userId = await verifyAuthentication(c) + const payload: z.infer = await c.req.json() + + const changelogResult = await db.query.changelog.findFirst({ + where: and( + eq(changelog.userId, userId), + eq(changelog.id, payload.changelogId), + ), + with: { + versions: { + where: and( + eq(changelog_version.changelogId, payload.changelogId), + eq(changelog_version.version, payload.version), + ), + }, }, - }, + }) + + if (!changelogResult) { + throw new HTTPException(404, { + message: 'Changelog not found', + }) + } + + if (changelogResult.versions.length) { + throw new HTTPException(409, { + message: 'Version exists already', + }) + } + + const formattedVersion = semver.coerce(payload.version) + const validVersion = semver.valid(formattedVersion) + + if (validVersion === null) { + throw new HTTPException(409, { + message: 'Version is not semver compatible', + }) + } + + const [versionCreateResult] = await db + .insert(changelog_version) + .values({ + changelogId: payload.changelogId, + version: validVersion, + status: payload.status, + markdown: payload.markdown, + }) + .returning() + + if (changelogResult.pageId) { + redis.del(changelogResult.pageId) + } + + await db + .update(changelog_commit) + .set({ versionId: versionCreateResult.id }) + .where(inArray(changelog_commit.id, payload.commitIds)) + + return c.json(VersionCreateOutput.parse(versionCreateResult), 201) }) - - if (!changelogResult) { - throw new HTTPException(404, { - message: 'Changelog not found', - }) - } - - if (changelogResult.versions.length) { - throw new HTTPException(409, { - message: 'Version exists already', - }) - } - - const formattedVersion = semver.coerce(payload.version) - const validVersion = semver.valid(formattedVersion) - - if (validVersion === null) { - throw new HTTPException(409, { - message: 'Version is not semver compatible', - }) - } - - const [versionCreateResult] = await db - .insert(changelog_version) - .values({ - changelogId: payload.changelogId, - version: validVersion, - status: payload.status, - markdown: payload.markdown, - }) - .returning() - - if (changelogResult.pageId) { - redis.del(changelogResult.pageId) - } - - await db - .update(changelog_commit) - .set({ versionId: versionCreateResult.id }) - .where(inArray(changelog_commit.id, payload.commitIds)) - - return versionCreateResult } diff --git a/apps/api/src/changelog/version/createAuto.ts b/apps/api/src/changelog/version/createAuto.ts index 592c74d..84964f4 100644 --- a/apps/api/src/changelog/version/createAuto.ts +++ b/apps/api/src/changelog/version/createAuto.ts @@ -13,9 +13,11 @@ import { format } from 'date-fns' import { and, eq, isNull } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' import semver from 'semver' + import type { changelogVersionApi } from '.' import { verifyAuthentication } from '../../utils/authentication' import { commitsToMarkdown } from '../../utils/git/commitsToMarkdown' +import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi' import { redis } from '../../utils/redis' export const route = createRoute({ @@ -36,13 +38,9 @@ export const route = createRoute({ }, description: 'Commits created', }, - 400: { - description: 'Bad Request', - }, - 500: { - description: 'Internal Server Error', - }, + ...openApiErrorResponses, }, + ...openApiSecurity, }) const getVersion = (version: string) => { @@ -173,6 +171,6 @@ export const registerVersionCreateAuto = (api: typeof changelogVersionApi) => { redis.del(changelogResult.pageId) } - return c.json(versionCreateResult, 201) + return c.json(VersionCreateOutput.parse(versionCreateResult), 201) }) } diff --git a/apps/api/src/changelog/version/delete.ts b/apps/api/src/changelog/version/delete.ts index a7cbad8..e4e5176 100644 --- a/apps/api/src/changelog/version/delete.ts +++ b/apps/api/src/changelog/version/delete.ts @@ -8,9 +8,12 @@ import { GeneralOutput } from '@boring.tools/schema' import { createRoute } from '@hono/zod-openapi' import { and, eq } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' +import type changelogVersionApi from '.' +import { verifyAuthentication } from '../../utils/authentication' +import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi' import { redis } from '../../utils/redis' -export const remove = createRoute({ +export const route = createRoute({ method: 'delete', path: '/:id', tags: ['version'], @@ -23,57 +26,55 @@ export const remove = createRoute({ }, description: 'Removes a version by id', }, - 404: { - content: { - 'application/json': { - schema: GeneralOutput, - }, - }, - description: 'Version not found', - }, - 500: { - description: 'Internal Server Error', - }, + ...openApiErrorResponses, }, + ...openApiSecurity, }) -export const removeFunc = async ({ - userId, - id, -}: { - userId: string - id: string -}) => { - const changelogResult = await db.query.changelog.findMany({ - where: and(eq(changelog.userId, userId)), - with: { - versions: { - where: eq(changelog_version.id, id), +export const registerVersionDelete = async ( + api: typeof changelogVersionApi, +) => { + return api.openapi(route, async (c) => { + const userId = await verifyAuthentication(c) + const id = c.req.param('id') + + const changelogResult = await db.query.changelog.findMany({ + where: and(eq(changelog.userId, userId)), + with: { + versions: { + where: eq(changelog_version.id, id), + }, }, - }, - }) - - const findChangelog = changelogResult.find((change) => - change.versions.find((ver) => ver.id === id), - ) - - if (!findChangelog?.versions.length) { - throw new HTTPException(404, { - message: 'Version not found', }) - } - await db - .update(changelog_commit) - .set({ versionId: null }) - .where(eq(changelog_commit.versionId, id)) + if (!changelogResult) { + throw new HTTPException(404, { message: 'Not Found' }) + } - if (findChangelog.pageId) { - redis.del(findChangelog.pageId) - } + const findChangelog = changelogResult.find((change) => + change.versions.find((ver) => ver.id === id), + ) - return db - .delete(changelog_version) - .where(and(eq(changelog_version.id, id))) - .returning() + if (!findChangelog?.versions.length) { + throw new HTTPException(404, { + message: 'Version not found', + }) + } + + await db + .update(changelog_commit) + .set({ versionId: null }) + .where(eq(changelog_commit.versionId, id)) + + if (findChangelog.pageId) { + redis.del(findChangelog.pageId) + } + + await db + .delete(changelog_version) + .where(and(eq(changelog_version.id, id))) + .returning() + + return c.json(GeneralOutput.parse({ message: 'Version deleted' }), 200) + }) } diff --git a/apps/api/src/changelog/version/index.ts b/apps/api/src/changelog/version/index.ts index 7bb0f35..52d7706 100644 --- a/apps/api/src/changelog/version/index.ts +++ b/apps/api/src/changelog/version/index.ts @@ -1,12 +1,12 @@ import { OpenAPIHono } from '@hono/zod-openapi' + import type { Variables } from '../..' -import { verifyAuthentication } from '../../utils/authentication' -import { type ContextModule, captureSentry } from '../../utils/sentry' -import { byId, byIdFunc } from './byId' -import { create, createFunc } from './create' +import type { ContextModule } from '../../utils/sentry' +import { registerVersionById } from './byId' +import { registerVersionCreate } from './create' import { registerVersionCreateAuto } from './createAuto' -import { remove, removeFunc } from './delete' -import { update, updateFunc } from './update' +import { registerVersionDelete } from './delete' +import { registerVersionUpdate } from './update' export const changelogVersionApi = new OpenAPIHono<{ Variables: Variables }>() @@ -16,114 +16,9 @@ const module: ContextModule = { } registerVersionCreateAuto(changelogVersionApi) - -changelogVersionApi.openapi(create, async (c) => { - const userId = await verifyAuthentication(c) - try { - const payload = await c.req.json() - const result = await createFunc({ userId, payload }) - - if (!result) { - return c.json({ message: 'Version not created' }, 400) - } - - return c.json(result, 201) - } catch (error) { - return captureSentry({ - c, - error, - module, - user: { - id: userId, - }, - }) - } -}) - -changelogVersionApi.openapi(byId, async (c) => { - const userId = await verifyAuthentication(c) - try { - const id = c.req.param('id') - const result = await byIdFunc({ userId, id }) - - if (!result) { - return c.json({ message: 'Version not found' }, 404) - } - - // Ensure all required properties are present and non-null - return c.json( - { - ...result, - changelogId: result.changelogId || '', - version: result.version || '', - status: result.status || 'draft', - releasedAt: result.releasedAt, - commits: result.commits || [], - markdown: result.markdown || '', - }, - 200, - ) - } catch (error) { - return captureSentry({ - c, - error, - module, - user: { - id: userId, - }, - }) - } -}) - -changelogVersionApi.openapi(update, async (c) => { - const userId = await verifyAuthentication(c) - try { - const id = c.req.param('id') - - if (!id) { - return c.json({ message: 'Version not found' }, 404) - } - - const result = await updateFunc({ - userId, - payload: await c.req.json(), - id, - }) - - return c.json(result) - } catch (error) { - return captureSentry({ - c, - error, - module, - user: { - id: userId, - }, - }) - } -}) - -changelogVersionApi.openapi(remove, async (c) => { - const userId = await verifyAuthentication(c) - try { - const id = c.req.param('id') - const result = await removeFunc({ userId, id }) - - if (result.length === 0) { - return c.json({ message: 'Version not found' }, 404) - } - - return c.json({ message: 'Version removed' }) - } catch (error) { - return captureSentry({ - c, - error, - module, - user: { - id: userId, - }, - }) - } -}) +registerVersionById(changelogVersionApi) +registerVersionCreate(changelogVersionApi) +registerVersionDelete(changelogVersionApi) +registerVersionUpdate(changelogVersionApi) export default changelogVersionApi diff --git a/apps/api/src/changelog/version/update.ts b/apps/api/src/changelog/version/update.ts index 4d3501d..f288875 100644 --- a/apps/api/src/changelog/version/update.ts +++ b/apps/api/src/changelog/version/update.ts @@ -3,9 +3,13 @@ import { VersionUpdateInput, VersionUpdateOutput } from '@boring.tools/schema' import { createRoute, type z } from '@hono/zod-openapi' import { and, eq } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' + +import type changelogVersionApi from '.' +import { verifyAuthentication } from '../../utils/authentication' +import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi' import { redis } from '../../utils/redis' -export const update = createRoute({ +export const route = createRoute({ method: 'put', path: '/:id', tags: ['version'], @@ -23,57 +27,57 @@ export const update = createRoute({ }, description: 'Return updated version', }, - 400: { - description: 'Bad Request', - }, - 500: { - description: 'Internal Server Error', - }, + ...openApiErrorResponses, }, + ...openApiSecurity, }) -export const updateFunc = async ({ - userId, - payload, - id, -}: { - userId: string - payload: z.infer - id: string -}) => { - const changelogResult = await db.query.changelog.findMany({ - where: and(eq(changelog.userId, userId)), - with: { - versions: { - where: eq(changelog_version.id, id), +export const registerVersionUpdate = (api: typeof changelogVersionApi) => { + return api.openapi(route, async (c) => { + const userId = await verifyAuthentication(c) + const id = c.req.param('id') + const payload: z.infer = await c.req.json() + + const changelogResult = await db.query.changelog.findMany({ + where: and(eq(changelog.userId, userId)), + with: { + versions: { + where: eq(changelog_version.id, id), + }, }, - }, + }) + + if (!changelogResult) { + throw new HTTPException(404, { + message: 'Version not found', + }) + } + + const findChangelog = changelogResult.find((change) => + change.versions.find((ver) => ver.id === id), + ) + + if (!findChangelog?.versions.length) { + throw new HTTPException(404, { + message: 'Version not found', + }) + } + + const [versionUpdateResult] = await db + .update(changelog_version) + .set({ + version: payload.version, + status: payload.status, + markdown: payload.markdown, + releasedAt: payload.releasedAt ? new Date(payload.releasedAt) : null, + }) + .where(and(eq(changelog_version.id, id))) + .returning() + + if (findChangelog.pageId) { + redis.del(findChangelog.pageId) + } + + return c.json(VersionUpdateOutput.parse(versionUpdateResult), 200) }) - - const findChangelog = changelogResult.find((change) => - change.versions.find((ver) => ver.id === id), - ) - - if (!findChangelog?.versions.length) { - throw new HTTPException(404, { - message: 'Version not found', - }) - } - - const [versionUpdateResult] = await db - .update(changelog_version) - .set({ - version: payload.version, - status: payload.status, - markdown: payload.markdown, - releasedAt: payload.releasedAt ? new Date(payload.releasedAt) : null, - }) - .where(and(eq(changelog_version.id, id))) - .returning() - - if (findChangelog.pageId) { - redis.del(findChangelog.pageId) - } - - return versionUpdateResult } diff --git a/packages/schema/src/version/base.ts b/packages/schema/src/version/base.ts index 8ca5828..08d5a4c 100644 --- a/packages/schema/src/version/base.ts +++ b/packages/schema/src/version/base.ts @@ -14,7 +14,7 @@ export const VersionOutput = z markdown: z.string().openapi({ example: '### Changelog\n\n- Added a new feature', }), - releasedAt: z.date().optional().openapi({ + releasedAt: z.date().optional().nullable().openapi({ example: '2024-01-01T00:00:00.000Z', }), status: z.enum(['draft', 'review', 'published']).default('draft').openapi({ diff --git a/packages/schema/src/version/create.ts b/packages/schema/src/version/create.ts index 0998631..2f6c19e 100644 --- a/packages/schema/src/version/create.ts +++ b/packages/schema/src/version/create.ts @@ -6,7 +6,7 @@ export const VersionCreateInput = z .object({ changelogId: z.string().uuid(), version: z.string(), - releasedAt: z.date().or(z.string()).optional(), + releasedAt: z.date().or(z.string()).optional().nullable(), status: z.enum(['draft', 'review', 'published']).default('draft'), markdown: z.string(), commitIds: z.array(z.string()),