diff --git a/server/api/APIPathGen.ts b/server/api/APIPathGen.ts new file mode 100644 index 0000000..df8fca9 --- /dev/null +++ b/server/api/APIPathGen.ts @@ -0,0 +1,37 @@ +export function APIPathGen( + obj: T, + callback: (path: string[], tail: V, pathParam?: Record) => unknown, + path: string[] = [], +): T { + const map = new Map(); + return new Proxy(obj, { + get(target, key) { + if(typeof key === 'symbol'){ + throw new Error('Symbol is not supported'); + } + + if(map.has(key)){ + return map.get(key)!; + } + + const nextPath = [...path, key]; + + let next: T[keyof T]; + if (key in target) { + next = target[key as keyof typeof target]; + } + else { + throw `${nextPath} is not exists`; + } + + if (typeof (next) === 'function') { + const result = callback(nextPath, next); + map.set(key, result); + return result; + } + const result = APIPathGen(next, callback, nextPath); + map.set(key, result); + return result; + } + }) as T; +}; \ No newline at end of file diff --git a/server/api/base.ts b/server/api/base.ts index 8723d5e..9201658 100644 --- a/server/api/base.ts +++ b/server/api/base.ts @@ -28,8 +28,8 @@ export type APINamespace = { ; } -export abstract class APIExecuter{ - readonly reqType: HttpMethod | HttpMethod[]; +export abstract class APIExecuter{ + readonly abstract reqType: HttpMethod | HttpMethod[]; protected abstract parseQuery(expressReq: Request): Promise; protected abstract api(query: Q, expressReq: Request, expressRes: Response): Promise; public async run(expressReq: Request, expressRes: Response): Promise { @@ -43,15 +43,15 @@ export abstract class APIExecuter extends APIExecuter{ +export abstract class APIBodyParseExecuter extends APIExecuter{ protected async parseQuery(expressReq: Request): Promise { throw new Error('Not implemented'); return expressReq.body; } } -export abstract class GET extends APIExecuter{ - readonly reqType = 'get'; +export abstract class GET extends APIExecuter{ + override readonly reqType = 'get'; protected async parseQuery(expressReq: Request): Promise { expressReq.query; throw new Error('Not implemented'); @@ -59,22 +59,22 @@ export abstract class GET extends APIBodyParseExecuter{ +export abstract class POST extends APIBodyParseExecuter{ readonly reqType = 'post'; } -export abstract class PUT extends APIBodyParseExecuter{ +export abstract class PUT extends APIBodyParseExecuter{ readonly reqType = 'put'; } -export abstract class DELETE extends APIBodyParseExecuter{ +export abstract class DELETE extends APIBodyParseExecuter{ readonly reqType = 'delete'; } -export abstract class PATCH extends APIBodyParseExecuter{ +export abstract class PATCH extends APIBodyParseExecuter{ readonly reqType = 'patch'; } -export abstract class HEAD extends APIBodyParseExecuter{ +export abstract class HEAD extends APIBodyParseExecuter{ readonly reqType = 'head'; } \ No newline at end of file diff --git a/server/api/clientAPI.ts b/server/clientAPI/clientAPI.ts similarity index 85% rename from server/api/clientAPI.ts rename to server/clientAPI/clientAPI.ts index 84281da..754daa9 100644 --- a/server/api/clientAPI.ts +++ b/server/clientAPI/clientAPI.ts @@ -1,10 +1,53 @@ import ky from 'ky'; -import type { APIExecuter, APINamespace, HttpMethod, InvalidResponse, RawArgType, ValidResponse } from './base.js'; +import type { APIExecuter, APINamespace, HttpMethod, InvalidResponse, RawArgType, ValidResponse } from '../api/base.js'; import isArray from 'lodash-es/isArray.js'; import isEmpty from 'lodash-es/isEmpty.js'; const apiPath = process.env.API_ROOT_PATH ?? process.env.VITE_API_ROOT_PATH ?? '/api'; +interface BasicAPICallT< + ArgType extends RawArgType, + ResultType extends ValidResponse, + ErrorType extends InvalidResponse +> { + (args: ArgType): Promise; + (args: ArgType, returnError: false): Promise; + (args: ArgType, returnError: true): Promise; +} + +interface EmptyAPICallT { + (): Promise; + (args: undefined): Promise; + (args: undefined, returnError: false): Promise; + (args: undefined, returnError: true): Promise; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ArgTypeOf = T extends APICallT ? A : never; + + +export type APICallT< + ArgType extends RawArgType, + ResultType extends ValidResponse = ValidResponse, + ErrorType extends InvalidResponse = InvalidResponse +> = ArgType extends undefined ? EmptyAPICallT : BasicAPICallT; + +export type APITail = typeof GET | typeof POST | typeof PUT | typeof PATCH | typeof HEAD | typeof DELETE; + +const httpMethodMap = new Map([ + [GET, "get"], + [POST, "post"], + [PUT, "put"], + [PATCH, "patch"], + [HEAD, "head"], + [DELETE, "delete"], + ]); + + export function extractHttpMethod(tail: APITail): HttpMethod { + return httpMethodMap.get(tail) ?? "post"; + } + + export async function GET( args?: ArgType ): Promise; diff --git a/server/clientAPI/sammoRootAPI.ts b/server/clientAPI/sammoRootAPI.ts new file mode 100644 index 0000000..c893961 --- /dev/null +++ b/server/clientAPI/sammoRootAPI.ts @@ -0,0 +1,56 @@ +import { APIPathGen } from "../api/APIPathGen"; +import { RawArgType } from "../api/base"; +import { APICallT, APITail, GET, POST, callClientAPI, extractHttpMethod } from "./clientAPI"; + +export type LoginResponse = { + result: true, + nextToken: [number, string] | undefined, +} + +export type LoginFailed = { + result: false, + reqOTP: boolean, + reason: string, +} + + +export type AutoLoginNonceResponse = { + result: true, + loginNonce: string, +}; + +export type AutoLoginResponse = { + result: true, + nextToken: [number, string] | undefined, +} + + +export type AutoLoginFailed = { + result: false, + silent: boolean, + reason: string, +} + +const apiRealPath = { + Login: { + LoginByID: POST as APICallT<{ + username: string, + password: string, + }, LoginResponse, LoginFailed>, + LoginByToken: POST as APICallT<{ + hashedToken: string, + token_id: number, + }, AutoLoginResponse, AutoLoginFailed>, + ReqNonce: GET as APICallT + }, +} as const; + +export const SammoRootAPI = APIPathGen(apiRealPath, (path: string[], tail: APITail, pathParam) => { + const method = extractHttpMethod(tail); + return (args?: RawArgType, returnError?: boolean) => { + if (returnError) { + return callClientAPI(method, path.join('/'), args, pathParam, true); + } + return callClientAPI(method, path.join('/'), args, pathParam); + }; +}); \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index e782055..b39b5a7 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,6 +1,6 @@ import 'dotenv/config'; import 'reflect-metadata'; -import { BasicQueryDTO } from './api/index.js'; +import { BasicQueryDTO } from './api/serverImpl.js'; import { validate } from 'class-validator'; import express, { type Request, type Response } from "express" import { AppDataSource } from "./data_source.js" @@ -21,22 +21,13 @@ export async function test() { console.log(result); //must be integer가 나타나야함 - const rawRng = new LiteHashDRBG(simpleSerialize( - 'haha', - 'hoho' - )); - - const arr: Promise[] = []; - let j = 0; - for(let i = 0; i < 100; i += 1){ - arr.push(rawRng.nextBits(32).then((v) => { - console.log(`${j}: ${i}`); - j += 1; - return v; - })); + const abc= { + a: 1, + b: 2, + c: 3, } - - await Promise.all(arr); + const t2 = await validate(abc); + console.log(t2); } diff --git a/tsconfig.json b/tsconfig.json index 883b4b1..3706827 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,18 +11,20 @@ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, + "module": "ESNext", + "moduleResolution": "node", "resolveJsonModule": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": true, + "importHelpers": true, "isolatedModules": true, "verbatimModuleSyntax": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", "strict": true, - "importHelpers": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, "paths": { "@util/*": ["./server/util/*"], "@server/*": ["./server/*"],