202 lines
5.2 KiB
TypeScript
202 lines
5.2 KiB
TypeScript
|
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 = <T, S extends number>(
|
||
|
description: string,
|
||
|
schema: z.ZodSchema<T>,
|
||
|
status: S,
|
||
|
) => {
|
||
|
return { [status]: { description, content: { 'application/json': { schema } } } } as {
|
||
|
[K in S]: {
|
||
|
description: string;
|
||
|
content: { 'application/json': { schema: z.ZodSchema<T> } };
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 创建OpenAPI成功响应信息
|
||
|
* @param description
|
||
|
* @param schema
|
||
|
* @param status
|
||
|
*/
|
||
|
export const createSuccessResponse = <T>(description: string, schema: z.ZodSchema<T>) => {
|
||
|
return createResponse(description ?? '请求成功', schema, 200);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 创建OpenAPI 201 成功响应信息
|
||
|
* @param description
|
||
|
* @param schema
|
||
|
*/
|
||
|
export const create201SuccessResponse = <T>(description: string, schema: z.ZodSchema<T>) => {
|
||
|
return createResponse(description ?? '请求成功', schema, 201);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 创建OpenAPI异常响应信息
|
||
|
* @param description
|
||
|
*/
|
||
|
export const createErrorResponse = <S extends number>(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 = <T>(schema: z.ZodSchema<T>) => {
|
||
|
return {
|
||
|
body: {
|
||
|
content: {
|
||
|
'application/json': { schema },
|
||
|
},
|
||
|
},
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param data 分頁數據
|
||
|
* @returns 格式化後的數據
|
||
|
*/
|
||
|
|
||
|
export const paginateTransform = <M, R extends PaginationResult<M[]>>(
|
||
|
data: R,
|
||
|
): PaginateReturn<M> => {
|
||
|
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<E> = {},
|
||
|
) => {
|
||
|
const options: Omit<HonoAppCreateConfig<E>, 'defaultHook'> & {
|
||
|
defaultHook?: Hook<any, E, any, any>;
|
||
|
} = {};
|
||
|
if (config.defaultHook !== false) {
|
||
|
options.defaultHook = config.defaultHook ?? defaultValidatorErrorHandler;
|
||
|
}
|
||
|
const app = new OpenAPIHono<E>();
|
||
|
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;
|
||
|
};
|