monorepo/server/common/utils.ts

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