import type { Hook, z } from '@hono/zod-openapi'; import type { Context, Next } from 'hono'; import type { PaginationResult } from 'prisma-paginate'; import { OpenAPIHono } from '@hono/zod-openapi'; import { prettyJSON } from 'hono/pretty-json'; import { isNil, omit } from 'lodash'; import type { AuthJwtPayload } from '../auth/type'; import type { HonoAppCreateConfig, PaginateReturn } from './types'; import { passportInitialize, verifyJWT } from '../auth/utils'; import { errorSchema } from './schema'; /** * 异常响应生成 * @param title * @param error * @param code */ export const createErrorResult = (title: string, error?: any, code?: number) => { let message = title; if (!isNil(error)) { message = error instanceof Error || 'message' in error ? `${title}:${error.message}` : `${title}:${error.toString()}`; } return { code, message, }; }; /** * 请求数据验证失败的默认响应 * @param result * @param c */ export const defaultValidatorErrorHandler = (result: any, c: Context) => { if (!result.success) { return c.json( { ...createErrorResult('请求数据验证失败', 400), errors: result.error.format(), }, 400, ); } return result; }; /** * 创建OpenAPI响应信息 * @param description * @param schema */ export const createResponse = ( description: string, schema: z.ZodSchema, status: S, ) => { return { [status]: { description, content: { 'application/json': { schema } } } } as { [K in S]: { description: string; content: { 'application/json': { schema: z.ZodSchema } }; }; }; }; /** * 创建OpenAPI成功响应信息 * @param description * @param schema * @param status */ export const createSuccessResponse = (description: string, schema: z.ZodSchema) => { return createResponse(description ?? '请求成功', schema, 200); }; /** * 创建OpenAPI 201 成功响应信息 * @param description * @param schema */ export const create201SuccessResponse = (description: string, schema: z.ZodSchema) => { return createResponse(description ?? '请求成功', schema, 201); }; /** * 创建OpenAPI异常响应信息 * @param description */ export const createErrorResponse = (description: string, status: S) => { return { [status]: { description, content: { 'application/json': { schema: errorSchema } } }, } as { [K in S]: { description: string; content: { 'application/json': { schema: typeof errorSchema } }; }; }; }; /** * 创建请求数据验证失败的响应信息 * @param description */ export const createValidatorErrorResponse = (description?: string) => { return createErrorResponse(description ?? '请求数据验证失败', 400); }; /** * 创建服务器错误响应信息 * @param description */ export const createServerErrorResponse = (description?: string) => { return createErrorResponse(description ?? '服务器错误', 500); }; /** * 创建数据不存在响应信息 * @param description */ export const createNotFoundErrorResponse = (description?: string) => { return createErrorResponse(description ?? '数据不存在', 404); }; /** * 创建未授权响应信息 * @param description */ export const createUnauthorizedErrorResponse = (description?: string) => { return createErrorResponse(description ?? '未授权', 401); }; export const createBodyRequest = (schema: z.ZodSchema) => { return { body: { content: { 'application/json': { schema }, }, }, }; }; /** * * @param data 分頁數據 * @returns 格式化後的數據 */ export const paginateTransform = >( data: R, ): PaginateReturn => { return { items: data.result, meta: { itemCount: data.result.length, totalItems: data.count, perPage: data.limit, totalPages: data.totalPages, currentPage: data.page, ...omit(data, ['result', 'count', 'limit', 'page', 'totalPages']), }, }; }; /** * 创建Hono应用 * @param config */ export const createHonoApp = < E extends { Bindings?: object; Variables?: { user: AuthJwtPayload }; }, >( config: HonoAppCreateConfig = {}, ) => { const options: Omit, 'defaultHook'> & { defaultHook?: Hook; } = {}; if (config.defaultHook !== false) { options.defaultHook = config.defaultHook ?? defaultValidatorErrorHandler; } const app = new OpenAPIHono(); app.use(prettyJSON()); app.use('*', passportInitialize()); app.use('/api/posts', async (c: Context, next: Next) => { const root = c.req.path; console.log(root); const isAuthenticated = await verifyJWT(c); if (!isAuthenticated && root !== '/api/posts') { return c.json(createErrorResult('用户未认证'), 401); } await next(); return undefined; }); return app; };