빌드
This commit is contained in:
+175
-6
@@ -1,17 +1,186 @@
|
||||
import { Router } from 'express';
|
||||
import type { AnyAPIExecuter, APINamespace } from './defs.js';
|
||||
import { Router, type Request, type Response } from 'express';
|
||||
import type { AnyAPIExecuter, APINamespace, ClassType } from './defs.js';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validate } from 'class-validator';
|
||||
import type { ExtractQuery, RawArgType } from '../apiStructure/defs.js';
|
||||
import type { Empty, InvalidProc, ProcDecorator, ProcDecoratorChain, ResolveChain } from './ProcDecorator/base.js';
|
||||
import clamp from 'lodash-es/clamp.js';
|
||||
|
||||
async function parseParam<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: ClassType<Q>): Promise<Q> {
|
||||
if (!argValidator) {
|
||||
return req.query as Q;
|
||||
}
|
||||
|
||||
const query = req.query;
|
||||
const classObject = plainToInstance(argValidator, query);
|
||||
const errors = await validate(classObject);
|
||||
if (errors.length) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return classObject as Q;
|
||||
}
|
||||
|
||||
async function parseBody<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: ClassType<Q>): Promise<Q> {
|
||||
if (!argValidator) {
|
||||
return req.body as Q;
|
||||
}
|
||||
|
||||
const query = req.body;
|
||||
const classObject = plainToInstance(argValidator, query);
|
||||
const errors = await validate(classObject);
|
||||
if (errors.length) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return classObject as Q;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type PlainDecorator = ProcDecorator<any, any>;
|
||||
|
||||
async function preCtx<PD extends ProcDecorator<any, any>>(preDecorator: PlainDecorator[], req: Request, res: Response): Promise<ResolveChain<PD> | [InvalidProc, ResolveChain<PD>, number]> {
|
||||
if (!preDecorator) {
|
||||
return {} as ResolveChain<PD>;
|
||||
}
|
||||
|
||||
let ctx = {} as ResolveChain<PD>;
|
||||
for (const [idx, proc] of preDecorator.entries()) {
|
||||
try {
|
||||
const result = await proc(ctx, req, res, true);
|
||||
if ('invalidProcSymbol' in result) {
|
||||
return [result, ctx, idx];
|
||||
}
|
||||
ctx = result;
|
||||
}
|
||||
catch (e) {
|
||||
return [{
|
||||
result: false,
|
||||
reason: `internal error[${idx}]: ${e}`,
|
||||
invalidProcSymbol: ['preCtx', idx],
|
||||
}, ctx, idx];
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
async function postCtx<PD extends ProcDecoratorChain>(ctx: ResolveChain<PD>, postDecorator: (undefined|PlainDecorator)[], req: Request, res: Response, index = -1): Promise<[InvalidProc, number] | undefined> {
|
||||
if (!postDecorator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isValidRoute = true;
|
||||
if (index !== -1 && index !== postDecorator.length) {
|
||||
isValidRoute = false;
|
||||
index = clamp(index, 0, postDecorator.length);
|
||||
}
|
||||
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const proc = postDecorator[i];
|
||||
if (!proc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await proc(ctx, req, res, isValidRoute);
|
||||
if ('invalidProcSymbol' in result) {
|
||||
result.invalidProcSymbol = ['postCtx', i, result.invalidProcSymbol];
|
||||
return [result, i];
|
||||
}
|
||||
ctx = result;
|
||||
}
|
||||
catch (e) {
|
||||
return [{
|
||||
result: false,
|
||||
reason: `internal error[${i}]: ${e}`,
|
||||
invalidProcSymbol: ['postCtx', i],
|
||||
}, i];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function apiRun(query: object, req: Request, res: Response, api: AnyAPIExecuter): Promise<void> {
|
||||
const ctx = await preCtx(api.preDecorator, req, res);
|
||||
if (Array.isArray(ctx)) {
|
||||
const [invalidProc, errCtx, idx] = ctx;
|
||||
if (idx == 0) {
|
||||
res.json(invalidProc);
|
||||
return;
|
||||
}
|
||||
const result = await postCtx(errCtx, api.postDecorator, req, res, idx);
|
||||
if (result === undefined) {
|
||||
res.json(invalidProc);
|
||||
return;
|
||||
}
|
||||
const [postInvalidProc,] = result;
|
||||
res.json(postInvalidProc);
|
||||
return;
|
||||
}
|
||||
const result = await api(query, ctx, req, res);
|
||||
const postResult = await postCtx(ctx, api.postDecorator, req, res);
|
||||
if (postResult !== undefined) {
|
||||
if (result === true) {
|
||||
//NOTE: 이미 api에서 response를 보낸 특이 케이스.
|
||||
return;
|
||||
}
|
||||
const [invalidProc,] = postResult;
|
||||
invalidProc.alreadyProcessed = true;
|
||||
res.json(invalidProc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result !== true) {
|
||||
res.json(result);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAPISystem<N extends APINamespace, Q extends AnyAPIExecuter>(api: N | Q): Router {
|
||||
const router = Router();
|
||||
for (const [key, value] of Object.entries(api)) {
|
||||
if (typeof value === 'function') {
|
||||
const executer = new value() as Q;
|
||||
if (!executer.reqType) {
|
||||
|
||||
if (!executer.httpMethod) {
|
||||
throw new Error('APIExecuter.reqType is not defined');
|
||||
}
|
||||
router[executer.reqType](key, async (req, res) => {
|
||||
await executer.run(req, res);
|
||||
});
|
||||
|
||||
if (executer.httpMethod === 'head') {
|
||||
router[executer.httpMethod](key, async (req, res) => {
|
||||
let query: ExtractQuery<Q>;
|
||||
try {
|
||||
query = await parseParam(req, executer.argValidator);
|
||||
}
|
||||
catch (e) {
|
||||
res.json({
|
||||
result: false,
|
||||
reason: `invalid parameter: ${e}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await apiRun(query, req, res, executer);
|
||||
});
|
||||
}
|
||||
else {
|
||||
router[executer.httpMethod](key, async (req, res) => {
|
||||
let query: ExtractQuery<Q>;
|
||||
try {
|
||||
query = await parseBody(req, executer.argValidator);
|
||||
}
|
||||
catch (e) {
|
||||
res.json({
|
||||
result: false,
|
||||
reason: `invalid parameter: ${e}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await apiRun(query, req, res, executer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
continue;
|
||||
} else {
|
||||
router.use(key, buildAPISystem(value));
|
||||
|
||||
Reference in New Issue
Block a user