feat: refactor zod validations and openapi errors
This commit is contained in:
parent
34bc012ceb
commit
bfc8ae2f21
@ -3,8 +3,10 @@ import { access_token, db } from '@boring.tools/database'
|
||||
import { AccessTokenCreateInput, AccessTokenOutput } from '@boring.tools/schema'
|
||||
import { createRoute, type z } from '@hono/zod-openapi'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import type { accessTokenApi } from '.'
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
export const route = createRoute({
|
||||
method: 'post',
|
||||
@ -24,13 +26,9 @@ export const route = createRoute({
|
||||
},
|
||||
description: 'Commits created',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerAccessTokenCreate = (api: typeof accessTokenApi) => {
|
||||
@ -53,6 +51,6 @@ export const registerAccessTokenCreate = (api: typeof accessTokenApi) => {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(AccessTokenOutput.parse(result), 201)
|
||||
})
|
||||
}
|
||||
|
@ -3,27 +3,25 @@ import { GeneralOutput } 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 { accessTokenApi } from '.'
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
export const route = createRoute({
|
||||
method: 'delete',
|
||||
path: '/:id',
|
||||
tags: ['access-token'],
|
||||
responses: {
|
||||
201: {
|
||||
200: {
|
||||
content: {
|
||||
'application/json': { schema: GeneralOutput },
|
||||
},
|
||||
description: 'Removes a access token by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerAccessTokenDelete = (api: typeof accessTokenApi) => {
|
||||
@ -40,6 +38,6 @@ export const registerAccessTokenDelete = (api: typeof accessTokenApi) => {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(GeneralOutput.parse({ message: 'Access token deleted' }), 200)
|
||||
})
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ import { AccessTokenListOutput } from '@boring.tools/schema'
|
||||
import { createRoute } from '@hono/zod-openapi'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import type { accessTokenApi } from '.'
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'get',
|
||||
@ -19,13 +21,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return version by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerAccessTokenList = (api: typeof accessTokenApi) => {
|
||||
@ -44,6 +42,6 @@ export const registerAccessTokenList = (api: typeof accessTokenApi) => {
|
||||
token: at.token.substring(0, 10),
|
||||
}))
|
||||
|
||||
return c.json(mappedData, 200)
|
||||
return c.json(AccessTokenListOutput.parse(mappedData), 200)
|
||||
})
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ import { CommitCreateInput, CommitCreateOutput } 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 { changelogCommitApi } from '.'
|
||||
import { verifyAuthentication } from '../../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi'
|
||||
|
||||
export const route = createRoute({
|
||||
method: 'post',
|
||||
@ -24,13 +26,9 @@ export const route = createRoute({
|
||||
},
|
||||
description: 'Commits created',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerCommitCreate = (api: typeof changelogCommitApi) => {
|
||||
@ -64,6 +62,6 @@ export const registerCommitCreate = (api: typeof changelogCommitApi) => {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(CommitCreateOutput.parse(result), 201)
|
||||
})
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ import { createRoute } from '@hono/zod-openapi'
|
||||
import { and, eq, isNotNull, isNull } from 'drizzle-orm'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
import { P, match } from 'ts-pattern'
|
||||
|
||||
import type { changelogCommitApi } from '.'
|
||||
import { verifyAuthentication } from '../../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../../utils/openapi'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'get',
|
||||
@ -23,13 +25,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return version by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerCommitList = (api: typeof changelogCommitApi) => {
|
||||
@ -80,6 +78,6 @@ export const registerCommitList = (api: typeof changelogCommitApi) => {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(commits, 200)
|
||||
return c.json(CommitListOutput.parse(commits), 200)
|
||||
})
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ export type Variables = {
|
||||
|
||||
export const app = new OpenAPIHono<{ Variables: Variables }>({
|
||||
defaultHook: handleZodError,
|
||||
strict: false,
|
||||
})
|
||||
|
||||
// app.use(
|
||||
@ -33,6 +34,14 @@ export const app = new OpenAPIHono<{ Variables: Variables }>({
|
||||
app.onError(handleError)
|
||||
app.use('*', cors())
|
||||
app.use('/v1/*', authentication)
|
||||
app.openAPIRegistry.registerComponent('securitySchemes', 'AccessToken', {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
})
|
||||
app.openAPIRegistry.registerComponent('securitySchemes', 'Clerk', {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
})
|
||||
|
||||
app.route('/v1/user', user)
|
||||
app.route('/v1/changelog', changelog)
|
||||
|
@ -5,6 +5,7 @@ import { and, eq } from 'drizzle-orm'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
import type { pageApi } from './index'
|
||||
|
||||
const route = createRoute({
|
||||
@ -24,13 +25,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return changelog by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerPageById = (api: typeof pageApi) => {
|
||||
@ -60,6 +57,6 @@ export const registerPageById = (api: typeof pageApi) => {
|
||||
changelogs: changelogs.map((log) => log.changelog),
|
||||
}
|
||||
|
||||
return c.json(mappedResult, 200)
|
||||
return c.json(PageOutput.parse(mappedResult), 200)
|
||||
})
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { createRoute, type z } from '@hono/zod-openapi'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
import type { pageApi } from './index'
|
||||
|
||||
const route = createRoute({
|
||||
@ -27,13 +28,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return changelog by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerPageCreate = (api: typeof pageApi) => {
|
||||
@ -42,6 +39,7 @@ export const registerPageCreate = (api: typeof pageApi) => {
|
||||
|
||||
const { changelogIds, ...rest }: z.infer<typeof PageCreateInput> =
|
||||
await c.req.json()
|
||||
|
||||
const [result] = await db
|
||||
.insert(page)
|
||||
.values({
|
||||
@ -50,6 +48,10 @@ export const registerPageCreate = (api: typeof pageApi) => {
|
||||
})
|
||||
.returning()
|
||||
|
||||
if (!result) {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
// TODO: implement transaction
|
||||
if (changelogIds.length > 0) {
|
||||
await db.insert(changelogs_to_pages).values(
|
||||
@ -59,10 +61,7 @@ export const registerPageCreate = (api: typeof pageApi) => {
|
||||
})),
|
||||
)
|
||||
}
|
||||
if (!result) {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(PageOutput.parse(result), 200)
|
||||
})
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ import { GeneralOutput, PageByIdParams } from '@boring.tools/schema'
|
||||
import { createRoute } from '@hono/zod-openapi'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import type { pageApi } from '.'
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'delete',
|
||||
@ -23,27 +25,25 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Removes a changelog by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerPageDelete = (api: typeof pageApi) => {
|
||||
return api.openapi(route, async (c) => {
|
||||
const userId = await verifyAuthentication(c)
|
||||
const { id } = c.req.valid('param')
|
||||
|
||||
const result = await db
|
||||
.delete(page)
|
||||
.where(and(eq(page.userId, userId), eq(page.id, id)))
|
||||
.returning()
|
||||
|
||||
if (!result) {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json({}, 200)
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { timing } from 'hono/timing'
|
||||
import type { Variables } from '..'
|
||||
import { handleZodError } from '../utils/errors'
|
||||
import type { ContextModule } from '../utils/sentry'
|
||||
import { registerPageById } from './byId'
|
||||
import { registerPageCreate } from './create'
|
||||
@ -9,7 +10,9 @@ import { registerPageList } from './list'
|
||||
import { registerPagePublic } from './public'
|
||||
import { registerPageUpdate } from './update'
|
||||
|
||||
export const pageApi = new OpenAPIHono<{ Variables: Variables }>()
|
||||
export const pageApi = new OpenAPIHono<{ Variables: Variables }>({
|
||||
defaultHook: handleZodError,
|
||||
})
|
||||
pageApi.use('*', timing())
|
||||
const module: ContextModule = {
|
||||
name: 'page',
|
||||
|
@ -1,16 +1,17 @@
|
||||
import { db, page } from '@boring.tools/database'
|
||||
import { PageListOutput } from '@boring.tools/schema'
|
||||
import { createRoute } from '@hono/zod-openapi'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
|
||||
import { PageListOutput } from '@boring.tools/schema'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
import type { pageApi } from './index'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'get',
|
||||
tags: ['page'],
|
||||
description: 'Get a page list',
|
||||
description: 'Get a list of pages',
|
||||
path: '/',
|
||||
responses: {
|
||||
200: {
|
||||
@ -21,13 +22,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return changelog by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerPageList = (api: typeof pageApi) => {
|
||||
@ -42,6 +39,6 @@ export const registerPageList = (api: typeof pageApi) => {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(PageListOutput.parse(result), 200)
|
||||
})
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { changelog_version, db, page } from '@boring.tools/database'
|
||||
import { PagePublicOutput, PagePublicParams } from '@boring.tools/schema'
|
||||
import { createRoute } from '@hono/zod-openapi'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { endTime, setMetric, startTime } from 'hono/timing'
|
||||
|
||||
import { PagePublicOutput, PagePublicParams } from '@boring.tools/schema'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
import { endTime, startTime } from 'hono/timing'
|
||||
|
||||
import { openApiErrorResponses } from '../utils/openapi'
|
||||
import { redis } from '../utils/redis'
|
||||
import type { pageApi } from './index'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'get',
|
||||
tags: ['page'],
|
||||
description: 'Get a page',
|
||||
description: 'Get a page by id for public view',
|
||||
path: '/:id/public',
|
||||
request: {
|
||||
params: PagePublicParams,
|
||||
@ -24,14 +24,9 @@ const route = createRoute({
|
||||
schema: PagePublicOutput,
|
||||
},
|
||||
},
|
||||
description: 'Return changelog by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
description: 'Get a page by id for public view',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
})
|
||||
|
||||
@ -96,6 +91,6 @@ export const registerPagePublic = (api: typeof pageApi) => {
|
||||
}
|
||||
|
||||
redis.set(id, JSON.stringify(mappedResult), { EX: 60 })
|
||||
return c.json(mappedResult, 200)
|
||||
return c.json(PagePublicOutput.parse(mappedResult), 200)
|
||||
})
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
import { redis } from '../utils/redis'
|
||||
import type { pageApi } from './index'
|
||||
|
||||
@ -34,13 +35,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return changelog by id',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerPageUpdate = (api: typeof pageApi) => {
|
||||
@ -60,6 +57,10 @@ export const registerPageUpdate = (api: typeof pageApi) => {
|
||||
.where(and(eq(page.userId, userId), eq(page.id, id)))
|
||||
.returning()
|
||||
|
||||
if (!result) {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
// TODO: implement transaction
|
||||
if (changelogIds) {
|
||||
if (changelogIds.length === 0) {
|
||||
@ -80,12 +81,8 @@ export const registerPageUpdate = (api: typeof pageApi) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
throw new HTTPException(404, { message: 'Not Found' })
|
||||
}
|
||||
|
||||
redis.del(id)
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(PageOutput.parse(result), 200)
|
||||
})
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ import { changelog, db, page } from '@boring.tools/database'
|
||||
import { StatisticOutput } from '@boring.tools/schema'
|
||||
import { createRoute } from '@hono/zod-openapi'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
import type { statisticApi } from '.'
|
||||
import { verifyAuthentication } from '../utils/authentication'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'get',
|
||||
@ -16,13 +18,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return user',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerStatisticGet = (api: typeof statisticApi) => {
|
||||
@ -108,6 +106,6 @@ export const registerStatisticGet = (api: typeof statisticApi) => {
|
||||
},
|
||||
}
|
||||
|
||||
return c.json(mappedData, 200)
|
||||
return c.json(StatisticOutput.parse(mappedData), 200)
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ import { db, user as userDb } from '@boring.tools/database'
|
||||
import { UserOutput } from '@boring.tools/schema'
|
||||
import { createRoute } from '@hono/zod-openapi'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
import type { userApi } from '.'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'get',
|
||||
@ -15,13 +17,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return user',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
export const registerUserGet = (api: typeof userApi) => {
|
||||
@ -35,6 +33,6 @@ export const registerUserGet = (api: typeof userApi) => {
|
||||
throw new Error('User not found')
|
||||
}
|
||||
|
||||
return c.json(result, 200)
|
||||
return c.json(UserOutput.parse(result), 200)
|
||||
})
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import { UserOutput, UserWebhookInput } from '@boring.tools/schema'
|
||||
import { createRoute, type z } from '@hono/zod-openapi'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
import { Webhook } from 'svix'
|
||||
|
||||
import type userApi from '.'
|
||||
import { openApiErrorResponses, openApiSecurity } from '../utils/openapi'
|
||||
|
||||
const route = createRoute({
|
||||
method: 'post',
|
||||
@ -24,13 +26,9 @@ const route = createRoute({
|
||||
},
|
||||
description: 'Return success',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
...openApiSecurity,
|
||||
})
|
||||
|
||||
const userCreate = async ({
|
||||
@ -72,7 +70,7 @@ export const registerUserWebhook = (api: typeof userApi) => {
|
||||
case 'user.created': {
|
||||
const result = await userCreate({ payload: verifiedPayload })
|
||||
logger.info('Clerk Webhook', result)
|
||||
return c.json(result, 200)
|
||||
return c.json(UserOutput.parse(result), 200)
|
||||
}
|
||||
default:
|
||||
throw new HTTPException(404, { message: 'Webhook type not supported' })
|
||||
|
26
apps/api/src/utils/createRoute.ts
Normal file
26
apps/api/src/utils/createRoute.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { type RouteConfig, createRoute as route } from '@hono/zod-openapi'
|
||||
import { openApiErrorResponses } from './openapi'
|
||||
|
||||
type CreateRoute = {
|
||||
method: 'get' | 'post' | 'put' | 'delete'
|
||||
tags: string[]
|
||||
description: string
|
||||
path: string
|
||||
responses: RouteConfig['responses']
|
||||
request?: RouteConfig['request']
|
||||
}
|
||||
|
||||
export const createRoute = (input: CreateRoute) =>
|
||||
route({
|
||||
...input,
|
||||
responses: {
|
||||
...input.responses,
|
||||
...openApiErrorResponses,
|
||||
},
|
||||
security: [
|
||||
{
|
||||
Clerk: [],
|
||||
AccessToken: [],
|
||||
},
|
||||
],
|
||||
})
|
68
apps/api/src/utils/openapi.ts
Normal file
68
apps/api/src/utils/openapi.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { createErrorSchema } from './errors'
|
||||
|
||||
export const openApiSecurity = {
|
||||
security: [
|
||||
{
|
||||
Clerk: [],
|
||||
AccessToken: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const openApiErrorResponses = {
|
||||
400: {
|
||||
description:
|
||||
'The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: createErrorSchema('BAD_REQUEST').openapi('ErrBadRequest'),
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description:
|
||||
'The client must authenticate itself to get the requested response.',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: createErrorSchema('UNAUTHORIZED').openapi('ErrUnauthorized'),
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description:
|
||||
'The client does not have the necessary permissions to access the resource.',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: createErrorSchema('FORBIDDEN').openapi('ErrForbidden'),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: "The server can't find the requested resource.",
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: createErrorSchema('NOT_FOUND').openapi('ErrNotFound'),
|
||||
},
|
||||
},
|
||||
},
|
||||
409: {
|
||||
description:
|
||||
'The request could not be completed due to a conflict mainly due to unique constraints.',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: createErrorSchema('CONFLICT').openapi('ErrConflict'),
|
||||
},
|
||||
},
|
||||
},
|
||||
500: {
|
||||
description:
|
||||
"The server has encountered a situation it doesn't know how to handle.",
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: createErrorSchema('INTERNAL_SERVER_ERROR').openapi(
|
||||
'ErrInternalServerError',
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
@ -40,7 +40,7 @@ const Component = () => {
|
||||
resolver: zodResolver(PageUpdateInput),
|
||||
defaultValues: {
|
||||
...page.data,
|
||||
changelogIds: page.data?.changelogs.map((log) => log.id),
|
||||
changelogIds: page.data?.changelogs?.map((log) => log.id),
|
||||
},
|
||||
})
|
||||
const onSubmit = (values: z.infer<typeof PageUpdateInput>) => {
|
||||
@ -138,6 +138,10 @@ const Component = () => {
|
||||
key={changelog.id}
|
||||
onSelect={() => {
|
||||
const getIds = () => {
|
||||
if (!field.value) {
|
||||
return [changelog.id]
|
||||
}
|
||||
|
||||
if (field.value?.includes(changelog.id)) {
|
||||
return field.value.filter(
|
||||
(id) => id !== changelog.id,
|
||||
|
@ -31,12 +31,14 @@ const Component = () => {
|
||||
<Card className="w-full max-w-screen-sm">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>Changelogs ({data.changelogs?.length})</CardTitle>
|
||||
<CardTitle>
|
||||
Changelogs ({data.changelogs?.length ?? 0})
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-1">
|
||||
{data.changelogs.map((changelog) => {
|
||||
{data.changelogs?.map((changelog) => {
|
||||
return (
|
||||
<div className="flex gap-3" key={changelog.id}>
|
||||
<Link
|
||||
|
@ -1 +1 @@
|
||||
{"root":["./src/main.tsx","./src/routeTree.gen.ts","./src/vite-env.d.ts","./src/components/Layout.tsx","./src/components/PageWrapper.tsx","./src/components/Sidebar.tsx","./src/components/SidebarChangelog.tsx","./src/components/SidebarPage.tsx","./src/components/SidebarUser.tsx","./src/components/AccessToken/Delete.tsx","./src/components/AccessToken/Table/Columns.tsx","./src/components/AccessToken/Table/DataTable.tsx","./src/components/Changelog/CommitList.tsx","./src/components/Changelog/Delete.tsx","./src/components/Changelog/VersionDelete.tsx","./src/components/Changelog/VersionList.tsx","./src/components/Changelog/VersionStatus.tsx","./src/components/Changelog/Version/Create/Step01.tsx","./src/components/Changelog/Version/Create/index.tsx","./src/components/Page/Delete.tsx","./src/hooks/useAccessToken.ts","./src/hooks/useChangelog.ts","./src/hooks/usePage.ts","./src/routes/__root.tsx","./src/routes/access-tokens.index.lazy.tsx","./src/routes/access-tokens.new.lazy.tsx","./src/routes/changelog.$id.edit.lazy.tsx","./src/routes/changelog.$id.index.lazy.tsx","./src/routes/changelog.$id.lazy.tsx","./src/routes/changelog.$id.version.$versionId.tsx","./src/routes/changelog.$id.versionCreate.lazy.tsx","./src/routes/changelog.create.lazy.tsx","./src/routes/changelog.index.lazy.tsx","./src/routes/cli.lazy.tsx","./src/routes/index.lazy.tsx","./src/routes/page.$id.edit.lazy.tsx","./src/routes/page.$id.index.lazy.tsx","./src/routes/page.$id.lazy.tsx","./src/routes/page.create.lazy.tsx","./src/routes/page.index.lazy.tsx","./src/routes/user/index.lazy.tsx","./src/utils/navigation-routes.ts","./src/utils/queryFetch.ts"],"version":"5.6.3"}
|
||||
{"root":["./src/main.tsx","./src/routeTree.gen.ts","./src/vite-env.d.ts","./src/components/Layout.tsx","./src/components/PageWrapper.tsx","./src/components/Sidebar.tsx","./src/components/SidebarChangelog.tsx","./src/components/SidebarPage.tsx","./src/components/SidebarUser.tsx","./src/components/AccessToken/Delete.tsx","./src/components/AccessToken/Table/Columns.tsx","./src/components/AccessToken/Table/DataTable.tsx","./src/components/Changelog/CommitList.tsx","./src/components/Changelog/Delete.tsx","./src/components/Changelog/VersionDelete.tsx","./src/components/Changelog/VersionList.tsx","./src/components/Changelog/VersionStatus.tsx","./src/components/Changelog/Version/Create/Step01.tsx","./src/components/Changelog/Version/Create/Step02.tsx","./src/components/Changelog/Version/Create/index.tsx","./src/components/Page/Delete.tsx","./src/hooks/useAccessToken.ts","./src/hooks/useChangelog.ts","./src/hooks/usePage.ts","./src/hooks/useStatistic.ts","./src/routes/__root.tsx","./src/routes/access-tokens.index.lazy.tsx","./src/routes/access-tokens.new.lazy.tsx","./src/routes/changelog.$id.edit.lazy.tsx","./src/routes/changelog.$id.index.lazy.tsx","./src/routes/changelog.$id.lazy.tsx","./src/routes/changelog.$id.version.$versionId.tsx","./src/routes/changelog.$id.versionCreate.lazy.tsx","./src/routes/changelog.create.lazy.tsx","./src/routes/changelog.index.lazy.tsx","./src/routes/cli.lazy.tsx","./src/routes/index.lazy.tsx","./src/routes/page.$id.edit.lazy.tsx","./src/routes/page.$id.index.lazy.tsx","./src/routes/page.$id.lazy.tsx","./src/routes/page.create.lazy.tsx","./src/routes/page.index.lazy.tsx","./src/routes/user/index.lazy.tsx","./src/utils/navigation-routes.ts","./src/utils/queryFetch.ts"],"version":"5.6.3"}
|
@ -30,9 +30,9 @@ const data: PageById = await response.json()
|
||||
<p class="prose text-sm">{data.description}</p>
|
||||
</div>
|
||||
|
||||
{data.changelogs.length >= 2 && <div class="flex flex-col">
|
||||
{data.changelogs?.length >= 2 && <div class="flex flex-col">
|
||||
<h2 class="prose prose-xl">Changelogs</h2>
|
||||
{data.changelogs.map((changelog) => {
|
||||
{data.changelogs?.map((changelog) => {
|
||||
if (changelog.versions && changelog.versions?.length < 1) {
|
||||
return null
|
||||
}
|
||||
@ -65,7 +65,7 @@ const data: PageById = await response.json()
|
||||
</div>
|
||||
})}
|
||||
</div>}
|
||||
{data.changelogs.length === 1 && <div>
|
||||
{data.changelogs?.length === 1 && <div>
|
||||
<h2 class="uppercase text-sm prose tracking-widest">Changelog</h2>
|
||||
{data.changelogs.map((changelog) => {
|
||||
if (changelog.versions && changelog.versions?.length < 1) {
|
||||
|
@ -17,6 +17,6 @@ auth:bearer {
|
||||
body:json {
|
||||
{
|
||||
"changelogId": "d83fe688-3331-4e64-9af6-318f82e511d4",
|
||||
"commitIds": ["01cc79df-6d16-4496-b9ba-b238d686efc4"]
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
|
15
bruno/Page/By Id.bru
Normal file
15
bruno/Page/By Id.bru
Normal file
@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: By Id
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{API_URL}}/page/:id/public
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:path {
|
||||
id: 74ab8978-e6d4-436e-98b8-fb6d6fde35fe
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/pg": "^8.11.10",
|
||||
"drizzle-kit": "^0.27.1"
|
||||
"drizzle-kit": "^0.28.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
|
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@logtail/node": "^0.5.0",
|
||||
"@logtail/winston": "^0.5.0",
|
||||
"winston": "^3.14.2"
|
||||
"winston": "^3.14.2",
|
||||
"winston-loki": "^6.1.3"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { Logtail } from '@logtail/node'
|
||||
import { LogtailTransport } from '@logtail/winston'
|
||||
import winston from 'winston'
|
||||
import LokiTransport from 'winston-loki'
|
||||
|
||||
// Create a Winston logger - passing in the Logtail transport
|
||||
export const logger = winston.createLogger({
|
||||
@ -9,6 +10,14 @@ export const logger = winston.createLogger({
|
||||
new winston.transports.Console({
|
||||
format: winston.format.json(),
|
||||
}),
|
||||
new LokiTransport({
|
||||
host: 'http://localhost:9100',
|
||||
labels: { app: 'api' },
|
||||
json: true,
|
||||
format: winston.format.json(),
|
||||
replaceTimestamp: true,
|
||||
onConnectionError: (err) => console.error(err),
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
|
@ -7,6 +7,6 @@ export const AccessTokenOutput = z
|
||||
}),
|
||||
token: z.string().optional(),
|
||||
name: z.string(),
|
||||
lastUsedOn: z.string().or(z.date()),
|
||||
lastUsedOn: z.string().or(z.date()).optional().nullable(),
|
||||
})
|
||||
.openapi('Access Token')
|
||||
|
@ -13,13 +13,15 @@ export const ChangelogOutput = z
|
||||
example: 'This is a changelog',
|
||||
}),
|
||||
versions: z.array(VersionOutput).optional(),
|
||||
computed: z.object({
|
||||
versionCount: z.number().openapi({
|
||||
example: 5,
|
||||
}),
|
||||
commitCount: z.number().openapi({
|
||||
example: 10,
|
||||
}),
|
||||
}),
|
||||
computed: z
|
||||
.object({
|
||||
versionCount: z.number().openapi({
|
||||
example: 5,
|
||||
}),
|
||||
commitCount: z.number().openapi({
|
||||
example: 10,
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.openapi('Changelog')
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { z } from '@hono/zod-openapi'
|
||||
import { ChangelogOutput } from '../changelog'
|
||||
|
||||
export const PageOutput = z
|
||||
.object({
|
||||
@ -8,5 +9,6 @@ export const PageOutput = z
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
icon: z.string(),
|
||||
changelogs: z.array(ChangelogOutput).optional(),
|
||||
})
|
||||
.openapi('Page')
|
||||
|
@ -1,9 +1,4 @@
|
||||
import { z } from '@hono/zod-openapi'
|
||||
import { ChangelogOutput } from '../changelog'
|
||||
import { PageOutput } from './base'
|
||||
|
||||
export const PageListOutput = z.array(
|
||||
PageOutput.extend({
|
||||
changelogs: z.array(ChangelogOutput),
|
||||
}),
|
||||
)
|
||||
export const PageListOutput = z.array(PageOutput)
|
||||
|
Loading…
Reference in New Issue
Block a user