commit bfeb01484c5be49b9bab60aa0e3abb3cc37662c4 Author: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon Jun 26 16:46:12 2023 +0300 Initial commit diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..3e90b47 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e40272 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.vscode/ +node_modules/ +build/ +tmp/ +temp/ +keys/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fdaaf8 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Awesome Project Build with TypeORM + +Steps to run this project: + +1. Run `npm i` command +2. Setup database settings inside `data-source.ts` file +3. Run `npm start` command diff --git a/package.json b/package.json new file mode 100644 index 0000000..188c262 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "clonegur", + "version": "1.0.0", + "main": "index.ts", + "license": "MIT", + "type": "commonjs", + "dependencies": { + "mongodb": "^5.2.0", + "reflect-metadata": "^0.1.13" + }, + "devDependencies": { + "@types/express": "^4.17.14", + "@types/node": "^16.11.10", + "ts-node": "^10.9.1", + "typescript": "^5.1.3" + }, + "scripts": { + "start": "ts-node src/index.ts", + "typeorm": "typeorm-ts-node-commonjs" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7161f14 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,30 @@ +import { Collection, MongoClient } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; +import UserRouter from "./routers/UserRouter.ts"; +import { Router, rest, route } from "./server/Server.ts"; +import User from "./models/User.ts"; +import Response from "./server/Response.ts"; + +class RootRouter extends Router { + @route('users/*') users: UserRouter; + + @rest('*', '*') + default() { + return new Response().body(new Blob(['Page not found :/'])).status(404); + } + + constructor(salt: string, users: Collection) { + super(); + this.users = new UserRouter(salt, users); + } +} + +export default async function clonegur() { + const salt = new TextDecoder().decode(await Deno.readFile('keys/salt.txt')); + const db = await new MongoClient().connect({ + db: 'clonegur', + servers: [ { host: '127.0.0.1', port: 27017 } ] + }); + await new RootRouter(salt, db.collection('users')).attach(Deno.listen({ port: 4000, hostname: 'localhost' })); +} + +clonegur(); diff --git a/src/models/User.js b/src/models/User.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/src/models/User.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..08282db --- /dev/null +++ b/src/models/User.ts @@ -0,0 +1,5 @@ +export default interface User { + _id: string; + username: string; + password: string; +} diff --git a/src/routers/AppRouter.ts b/src/routers/AppRouter.ts new file mode 100644 index 0000000..d9c1ff8 --- /dev/null +++ b/src/routers/AppRouter.ts @@ -0,0 +1,8 @@ +import { HttpError, RestRequest, RestResponse, Router } from "../server/Server.ts"; + +export default class AppRouter extends Router { + public onError(_req: RestRequest, error: unknown): RestResponse | HttpError | Promise { + if (error instanceof HttpError) return new HttpError({ error: error.body }, error.status); + return super.onError(_req, error); + } +} \ No newline at end of file diff --git a/src/routers/UserRouter.ts b/src/routers/UserRouter.ts new file mode 100644 index 0000000..947a5ff --- /dev/null +++ b/src/routers/UserRouter.ts @@ -0,0 +1,46 @@ +import { Collection } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; +import { HttpError, rest } from "../server/Server.ts"; +import User from "../models/User.ts"; +import { body, schema } from "../server/Router.ts"; +import AppRouter from "./AppRouter.ts"; +import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"; + +interface SignupRequest { + username: string; + password: string; +} + +export default class UserRouter extends AppRouter { + @rest('GET', '/') + async get(@schema('string') username: string) { + const res = await this.users.findOne({ username }); + + if (res === undefined) throw new HttpError('User not found.'); + + return { username: res.username }; + } + @rest('POST', '/signup') + async signup( + @schema({ + username: 'string', password: 'string', + }) @body() body: SignupRequest + ) { + if (await this.users.countDocuments({ username: body.username }) > 0) { + throw new HttpError('User with the same username already exists.'); + } + + const password = await bcrypt.hash(body.password, this.salt); + + await this.users.insertOne({ + username: body.username, + password: password, + }); + + return {}; + } + + public constructor(private salt: string, private users: Collection) { + super(); + users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] }); + } +} \ No newline at end of file diff --git a/src/server/Request.ts b/src/server/Request.ts new file mode 100644 index 0000000..b9dcaaa --- /dev/null +++ b/src/server/Request.ts @@ -0,0 +1,122 @@ +export type ParamDict = Record; +export type Headers = Record; + +function splitUrl(url: string) { + return url.split('/').map(v => v.trim()).filter(v => v !== ''); +} +function sanitizeUrl(url: string, forceAbsolute = true) { + url = url.trim(); + if (forceAbsolute || url.startsWith('/')) { + return '/' + splitUrl(url).join('/'); + } + else { + return '/' + splitUrl(url).join('/'); + } +} + +export const FetchRequest = Request; + +export default class RestRequest { + public readonly body: unknown; + public readonly method: string; + public readonly url: string; + public readonly pathParams: ParamDict; + public readonly queryParams: ParamDict; + public readonly headers: Headers; + + public get params() { + return { ...this.queryParams, ...this.pathParams }; + } + + public constructor( + body: unknown, + headers: Headers, + method: string, + url: string, + pathParams: ParamDict = {}, + queryParams: ParamDict = {} + ) { + this.body = body; + this.headers = headers; + this.pathParams = { ...pathParams }; + this.queryParams = { ...queryParams }; + this.method = method.toLowerCase(); + this.url = sanitizeUrl(url); + + if (this.url.includes('?')) { + const questionIndex = this.url.indexOf('?'); + this.url = this.url.substring(0, questionIndex); + const params = this.url + .substring(questionIndex + 1) + .split('&') + .map(v => v.trim()) + .filter(v => v !== ''); + + for (const rawParam of params) { + const i = rawParam.indexOf('='); + if (i < 0) continue; + + const name = rawParam.substring(0, i); + const val = rawParam.substring(i + 1); + + if (name === '') continue; + this.queryParams[name] = val; + } + } + } + + public match(predicate: string) { + const urlSegments = splitUrl(this.url); + const predSegments = splitUrl(predicate); + const wildcardIndex = predSegments.indexOf('*'); + const hasWildcard = wildcardIndex >= 0; + const pathParams: ParamDict = { ...this.pathParams }; + + if (wildcardIndex >= 0) { + if (predSegments.includes('*', wildcardIndex + 1)) throw new Error("A path predicate may not have more than one wildcard."); + if (predSegments.splice(wildcardIndex).length > 1) throw new Error("A path predicate must be the last segment."); + } + + for (const predSeg of predSegments) { + const urlSeg = urlSegments.shift(); + if (urlSeg === undefined) return undefined; + else if (predSeg.startsWith(':')) { + const name = predSeg.substring(1); + if (name.length === 0) throw new Error('Invalid path predicate - a segment may not be ":".'); + pathParams[name] = decodeURI(urlSeg); + } + else if (predSeg === urlSeg) continue; + else return undefined; + } + + if (!hasWildcard && urlSegments.length > 0) return undefined; + + return new RestRequest( + this.body, this.headers, this.method, '/' + urlSegments.join('/'), + { ...this.pathParams, ...pathParams }, this.queryParams + ); + } + + public static async fromMessage(msg: Deno.RequestEvent) { + const raw = msg.request.body; + const headers = {} as Headers; + + for (const entry of msg.request.headers.entries()) { + headers[entry[0]] = entry[1]; + } + + const parts: Uint8Array[] = []; + + if (raw !== null) { + for await (const part of raw) { + parts.push(part); + } + } + + const url = new URL(msg.request.url); + const params = {} as ParamDict; + for (const entry of url.searchParams.entries()) params[entry[0]] = entry[1]; + + return new RestRequest(new Blob(parts), headers, msg.request.method, url.pathname, {}, params); + } +} diff --git a/src/server/Response.ts b/src/server/Response.ts new file mode 100644 index 0000000..830c0af --- /dev/null +++ b/src/server/Response.ts @@ -0,0 +1,51 @@ +import { Headers } from "./Request.ts"; + +export const FetchResponse = Response; + +export default class RestResponse { + #status = 200; + #statusMsg = ''; + #body?: Blob; + + headers: Headers = {}; + + public constructor() { } + + public get statusCode() { return this.#status; } + public get statusMessage() { return this.#statusMsg; } + public get content() { return this.#body; } + + public header(name: string, val: string | string[]) { + this.headers[name] = val; + return this; + } + public status(val: number, message = '') { + this.#status = val; + this.#statusMsg = message; + return this; + } + public body(val: string | Blob) { + if (typeof val === 'string') val = new Blob([val]); + this.#body = val; + return this; + } + + public toFetchResponse(): Response { + const headers: string[][] = []; + for (const key in this.headers) { + const val = this.headers[key]; + if (typeof val === 'string') { + headers.push([key, val]); + } + else if (val instanceof Array) { + headers.push([key, ...val]); + } + else headers.push([key]); + } + return new Response(this.#body, { + headers: headers, + status: this.#status, + statusText: this.#statusMsg, + }); + } +} \ No newline at end of file diff --git a/src/server/Router.ts b/src/server/Router.ts new file mode 100644 index 0000000..cb289a5 --- /dev/null +++ b/src/server/Router.ts @@ -0,0 +1,249 @@ +// deno-lint-ignore-file no-explicit-any ban-types +import { Reflect } from "https://deno.land/x/reflect_metadata@v0.1.12/mod.ts"; +import { HttpError, RestRequest, RestResponse, serialize } from "./Server.ts"; + +export type HandlerRes = Promise | RestResponse | undefined; + +export type HttpMethod = '*' | 'GET' | 'POST' | 'CHANGE' | 'DELETE' | 'PUT' | 'UPDATE'; +export type BodyType = 'raw' | 'json'; +export type AuthType = 'raw' | 'jwt'; + +export type PrimitiveSchema = 'string' | 'number' | 'boolean' | 'object'; +export type OptionalSchema = `${PrimitiveSchema}?`; +export type Schema = 'any' | PrimitiveSchema | OptionalSchema | Schema[] | ({ $optional?: boolean; } & { [key: string]: Schema }); + +export interface Handler { + handle(req: RestRequest): HandlerRes; +} +export interface RestOptions { + route?: string; +} + +type ProcessFunc = (req: RestRequest, arg: unknown, name: string) => Promise | unknown; +type Base = Router & { [X in KeyT]: Function; }; + +interface RouterHandler { + path: string; + handler: Handler; +} + +export function makeParameterModifier(func: ProcessFunc) { + return (target: Router, key: string, index: number) => { + let res = Reflect.getOwnMetadata('router:params', target, key); + + if (res === undefined) Reflect.defineMetadata('router:params', res = [], target, key); + (res[index] ??= []).push(func); + + return res; + } +} + +export function schema(desc: Schema) { + function stringify(desc: Schema): string { + if (typeof desc === 'string') return desc; + if (desc instanceof Array) return desc.map(stringify).join(' | '); + + let res = '{ '; + + for (const key in desc) { + if (key === '$optional') continue; + if (res != '{ ') res += ', '; + res += key + ': '; + res += stringify(desc[key]); + } + res += '}'; + if (desc.$optional) res += '?'; + + return res; + } + function test(path: string[], val: unknown, desc: Schema) { + if (desc === 'any') return; + if (typeof desc === 'string') { + let type: string = desc; + const opt = desc.endsWith('?'); + + if (opt) type = type.substring(0, desc.length - 1); + if (opt && val === undefined) return; + + if (typeof val as any !== type) throw new HttpError(`${path.join('.')}: Expected a ${type}, got ${typeof val} instead.`); + } + else if (desc instanceof Array) { + for (const type of desc) { + try { + test(path, val, type); + return; + } + catch { /**/ } + } + throw new HttpError(`${path.join('.')}: Expected a ${stringify(desc)}, got ${typeof val} instead.`); + } + else { + if (desc.$optional && val === undefined) return; + if (typeof val !== 'object' || val === null) throw new HttpError(`${path.join('.')}: Expected an object, got ${typeof val} instead.`); + + for (const key in desc) { + if (key === '$optional') continue; + test([ ...path, key ], (val as any)[key], desc[key]); + } + } + } + + return makeParameterModifier((_req, val, name) => (test([name], val, desc), val)); +} +export function body(type: BodyType = 'json') { + return makeParameterModifier(async req => { + let body = req.body; + if (type === 'json') { + try { + if (body instanceof Blob) body = await body.text(); + if (typeof body === 'string') body = JSON.parse(body.toString()); + } + catch (e) { + if (e instanceof SyntaxError) throw new HttpError('Body syntax error: ' + e.message); + } + } + return body; + }); +} +export function auth(type: AuthType = 'jwt') { + return makeParameterModifier(req => { + let res = req.headers.authorization; + if (typeof res !== 'string') return undefined; + if (res.startsWith('Bearer')) res = res.substring(6).trimStart(); + + if (type === 'jwt') throw new Error('JWT is not supported.'); + + return res; + }); +} + +function addMetaQuery(target: any, ...handlers: ((r: Router) => RouterHandler)[]) { + let props = Reflect.getOwnMetadata('router:queries', target); + if (props === undefined) Reflect.defineMetadata('router:queries', props = [], target); + + props.push(...handlers); +} + +export function rest>(method: HttpMethod, route?: string) { + return (target: T, key: KeyT) => { + const path = route ?? key; + + addMetaQuery(target, (r: any) => ({ + path, handler: { + async handle(req) { + if (method !== '*' && req.method.toUpperCase() !== method) return undefined; + + const params: string[] = []; + const func = (r as T)[key]; + const args: any[] = []; + const allMods: ([ProcessFunc, ...ProcessFunc[]] | undefined)[] = []; + + let signature = (target[key] as Function).toString(); + signature = signature.substring(signature.indexOf('(') + 1, signature.indexOf(')')); + params.push(...signature.split(',').map(v => v.trim()).filter(v => v !== '')); + + for (let proto = target; proto instanceof Router; proto = Object.getPrototypeOf(proto)) { + const data = Reflect.getOwnMetadata('router:params', proto, key); + if (!data) continue; + for (let i = 0; i < data.length; i++) { + allMods[i] ??= [] as any; + if (data[i]) allMods[i]!.push(...data[i]); + } + } + + for (let i = 0; i < params.length; i++) { + const param = params[i]; + + let arg: any = req.params[param]; + + for (const mod of allMods[i] ?? []) { + arg = await mod(req, arg, params[i]); + } + + args.push(arg); + } + + const res = func.apply(r, args); + if (res instanceof RestResponse) return res; + return new RestResponse().body(await serialize(res)); + } + }, + })); + }; +} +export function route(path?: string) { + return (target: T, key: KeyT) => { + addMetaQuery(target, (r: any) => ({ + handler: r[key], + path: path ?? key, + })); + }; +} + +export default class Router { + private _handlers?: RouterHandler[]; + public defaultHandler?: Handler; + + private _init() { + if (this._handlers === undefined) { + this._handlers = []; + + // why the actual fuck not + // deno-lint-ignore no-this-alias + for (let proto = this; proto instanceof Router; proto = Object.getPrototypeOf(proto)) { + const props = Reflect.getOwnMetadata('router:queries', proto); + for (const handler of props ?? []) { + this._handlers.push(handler(this)); + } + } + } + return this._handlers; + } + + public async handle(req: RestRequest) { + for (const hnd of this._init()) { + const _req = req.match(hnd.path); + if (_req) { + try { + const res = await hnd.handler.handle(_req); + if (res) return res; + } + catch (e) { + const res = await this.onError(_req, e); + if (res instanceof HttpError) return new RestResponse() + .body(await serialize(res.body)) + .status(res.status); + else return res; + } + } + } + + return this.defaultHandler?.handle(req); + } + public addHandler(path: string, handler: Handler) { + this._init().push({ path, handler }); + return this; + } + + public onError(_req: RestRequest, error: unknown): Promise | RestResponse | HttpError { + if (error instanceof HttpError) return error; + else { + console.error(error); + try { + return new HttpError(`Internal error: ${error}\nSee logs for details`, 500); + } + catch { + return new HttpError('Internal error.\nSee logs for details', 500); + } + } + } + + public async attach(server: Deno.Listener) { + for await (const conn of server) { + for await (const req of Deno.serveHttp(conn)) { + const r = await this.handle(await RestRequest.fromMessage(req)); + if (r) req.respondWith(r.toFetchResponse()); + } + } + } +} diff --git a/src/server/Server.ts b/src/server/Server.ts new file mode 100644 index 0000000..e9260d8 --- /dev/null +++ b/src/server/Server.ts @@ -0,0 +1,38 @@ +import Router, { rest, route } from "./Router.ts"; +import RestRequest from "./Request.ts"; +import RestResponse from "./Response.ts"; + +export { RestRequest, RestResponse, Router, rest, route }; + +export class HttpError extends Error { + constructor(public readonly body: unknown, public readonly status = 400) { + super(); + serialize(body).then(v => this.message = v.toString()); + } +} + +const undefinedBuff = new Blob([new TextEncoder().encode('undefined')]); +const nullBuff = new Blob([new TextEncoder().encode('null')]); + +export async function serialize(val: unknown, depth = 16): Promise { + while (true) { + if (depth <= 0) throw new Error("Call depth exceeded limit."); + if (val instanceof Promise) val = await val; + else if (val instanceof Function) { + if (val.length !== 0) throw new Error('Can\'t serialize an argument-accepting function'); + val = val(); + } + else break; + depth--; + } + + if (val === undefined) return undefinedBuff; + if (val === null) return nullBuff; + if (val instanceof Blob) return val; + + while (typeof val !== 'string' && val && val.toString !== Object.prototype.toString && val.toString instanceof Function) { + val = val.toString(); + } + + return new Blob([new TextEncoder().encode(JSON.stringify(val))]); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..06e1407 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "outDir": "./dst", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitAny": false + // "module": "ESNext" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..03bd9a5 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,292 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.17.35" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz" + integrity sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.14": + version "4.17.17" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/node@*", "@types/node@^16.11.10": + version "16.18.36" + resolved "https://registry.npmjs.org/@types/node/-/node-16.18.36.tgz" + integrity sha512-8egDX8dE50XyXWH6C6PRCNkTP106DuUrvdrednFouDSmCi7IOvrqr0frznfZaHifHH/3aq/7a7v9N4wdXMqhBQ== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/send@*": + version "0.17.1" + resolved "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.1" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz" + integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +"@types/webidl-conversions@*": + version "7.0.0" + resolved "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.9.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz" + integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +bson@^5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz" + integrity sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +mongodb-connection-string-url@^2.6.0: + version "2.6.0" + resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz" + integrity sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@^5.2.0: + version "5.6.0" + resolved "https://registry.npmjs.org/mongodb/-/mongodb-5.6.0.tgz" + integrity sha512-z8qVs9NfobHJm6uzK56XBZF8XwM9H294iRnB7wNjF0SnY93si5HPziIJn+qqvUR5QOff/4L0gCD6SShdR/GtVQ== + dependencies: + bson "^5.3.0" + mongodb-connection-string-url "^2.6.0" + socks "^2.7.1" + optionalDependencies: + saslprep "^1.0.3" + +punycode@^2.1.1: + version "2.3.0" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks@^2.7.1: + version "2.7.1" + resolved "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +typescript@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" + integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==