feat(api): more refactor

This commit is contained in:
Lars Hampe 2024-11-06 22:04:20 +01:00
parent bfc8ae2f21
commit 95e00816c4
8 changed files with 222 additions and 328 deletions

View File

@ -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
}

View File

@ -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<typeof VersionCreateInput>
}) => {
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<typeof VersionCreateInput> = 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
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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

View File

@ -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<typeof VersionUpdateInput>
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<typeof VersionUpdateInput> = 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
}

View File

@ -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({

View File

@ -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()),