From b54241a49517d1eab8280a999b87bf8743b3e344 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:34:28 +0300 Subject: [PATCH] cant remember --- src/index.ts | 2 +- src/models/User.ts | 3 ++ src/routers/ImageRouter.ts | 13 +++++- src/routers/RootRouter.ts | 3 +- src/routers/UserRouter.ts | 3 +- src/server/RestRequest.ts | 12 +----- src/server/RestResponse.ts | 7 ++-- src/server/Router.ts | 2 +- src/server/decorators.ts | 4 +- src/server/decorators/body.ts | 17 ++------ src/server/decorators/page.ts | 39 ++++++++++++++++++ src/server/decorators/schema.ts | 73 +++++++++++++++++++++++++++------ src/server/decorators/uuid.ts | 14 ------- src/server/serialize.ts | 16 +++++--- src/utils/now.ts | 3 -- src/utils/utils.ts | 6 +++ 16 files changed, 147 insertions(+), 70 deletions(-) create mode 100644 src/server/decorators/page.ts delete mode 100644 src/server/decorators/uuid.ts delete mode 100644 src/utils/now.ts create mode 100644 src/utils/utils.ts diff --git a/src/index.ts b/src/index.ts index bcffa0b..77842e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ export default async function clonegur() { db: 'clonegur', servers: [ { host: '127.0.0.1', port: 27017 } ] }); - await new RootRouter(salt, db.collection('users')).attach(Deno.listen({ port: 4000, hostname: 'localhost' })); + await new RootRouter(salt, db).attach(Deno.listen({ port: 4000, hostname: 'localhost' })); } await clonegur(); diff --git a/src/models/User.ts b/src/models/User.ts index 08282db..58a14b3 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,5 +1,8 @@ +import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; + export default interface User { _id: string; username: string; password: string; + images: UUID[]; } diff --git a/src/routers/ImageRouter.ts b/src/routers/ImageRouter.ts index 002f7ec..5d76747 100644 --- a/src/routers/ImageRouter.ts +++ b/src/routers/ImageRouter.ts @@ -1,12 +1,13 @@ import { Collection } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; import { UUID } from "https://deno.land/x/web_bson@v0.3.0/mod.js"; -import { auth, jwt, rest, schema, uuid } from "../server/decorators.ts"; +import { auth, body, jwt, page, rest, schema } from "../server/decorators.ts"; import { JWTPayload } from "../utils/JWT.ts"; import AppRouter from "./AppRouter.ts"; import HttpError from "../server/HttpError.ts"; import User from "../models/User.ts"; import Image, { Visibility } from "../models/Image.ts"; +import { Page } from "../server/decorators/page.ts"; export default class ImageRouter extends AppRouter { public static serialize(image: Image) { @@ -20,7 +21,7 @@ export default class ImageRouter extends AppRouter { } @rest('GET', '/') - async get(@uuid() @schema('string') id: string, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) { + async get(@schema('uuid') id: UUID, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) { const image = await this.images.findOne({ _id: new UUID(id) }); if ( @@ -31,6 +32,14 @@ export default class ImageRouter extends AppRouter { return ImageRouter.serialize(image); } + @rest('GET', '/mine') + async mine(@page() page: Page, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) { + const user = await this.users.findOne({ username: jwt.name }); + if (!user) throw new HttpError("You don't exist."); + const res = await page.apply(this.images.find({ _id: { $in: user.images } })).toArray(); + return res.map(v => ImageRouter.serialize(v)); + } + public constructor(private salt: string, private images: Collection, private users: Collection) { super(); users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] }); diff --git a/src/routers/RootRouter.ts b/src/routers/RootRouter.ts index 15bfab8..e7437e2 100644 --- a/src/routers/RootRouter.ts +++ b/src/routers/RootRouter.ts @@ -6,6 +6,7 @@ import RestResponse from "../server/RestResponse.ts"; import { rest, route } from "../server/decorators.ts"; import User from "../models/User.ts"; import Image from "../models/Image.ts"; +import { stream } from "../utils/utils.ts"; export class RootRouter extends AppRouter { @route('users/*') users; @@ -13,7 +14,7 @@ export class RootRouter extends AppRouter { @rest('*', '*') default() { - return new RestResponse().body(new Blob(['Page not found :/'])).status(404); + return new RestResponse().body(stream('Page not found :/')).status(404); } constructor(salt: string, db: Database) { diff --git a/src/routers/UserRouter.ts b/src/routers/UserRouter.ts index 009969a..11017b8 100644 --- a/src/routers/UserRouter.ts +++ b/src/routers/UserRouter.ts @@ -7,7 +7,7 @@ import User from "../models/User.ts"; import AppRouter from "./AppRouter.ts"; import jwt from "../server/decorators/jwt.ts"; import JWT, { JWTPayload } from "../utils/JWT.ts"; -import now from "../utils/now.ts"; +import { now } from "../utils/utils.ts"; export interface SignupRequest { username: string; @@ -53,6 +53,7 @@ export default class UserRouter extends AppRouter { await this.users.insertOne({ username: body.username, password: password, + images: [], }); return {}; diff --git a/src/server/RestRequest.ts b/src/server/RestRequest.ts index ba46caf..1bb566b 100644 --- a/src/server/RestRequest.ts +++ b/src/server/RestRequest.ts @@ -95,7 +95,7 @@ export default class RestRequest { ); } - public static async fromMessage(msg: Deno.RequestEvent) { + public static fromMessage(msg: Deno.RequestEvent) { const raw = msg.request.body; const headers = {} as Headers; @@ -103,18 +103,10 @@ export default class RestRequest { 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); + return new RestRequest(raw, headers, msg.request.method, url.pathname, {}, params); } } diff --git a/src/server/RestResponse.ts b/src/server/RestResponse.ts index 85aa78b..03f6647 100644 --- a/src/server/RestResponse.ts +++ b/src/server/RestResponse.ts @@ -1,9 +1,10 @@ +import { stream } from "../utils/utils.ts"; import { Headers } from "./RestRequest.ts"; export default class RestResponse { #status = 200; #statusMsg = ''; - #body?: Blob; + #body?: ReadableStream; headers: Headers = {}; @@ -22,8 +23,8 @@ export default class RestResponse { this.#statusMsg = message; return this; } - public body(val: string | Blob) { - if (typeof val === 'string') val = new Blob([val]); + public body(val: string | ReadableStream) { + if (typeof val === 'string') val = stream(val); this.#body = val; return this; } diff --git a/src/server/Router.ts b/src/server/Router.ts index d11271f..661d74e 100644 --- a/src/server/Router.ts +++ b/src/server/Router.ts @@ -100,7 +100,7 @@ export default class Router { 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)); + const r = await this.handle(RestRequest.fromMessage(req)); if (r) req.respondWith(r.toFetchResponse()); } } diff --git a/src/server/decorators.ts b/src/server/decorators.ts index 1a7e36d..93fbb12 100644 --- a/src/server/decorators.ts +++ b/src/server/decorators.ts @@ -4,6 +4,6 @@ import auth from "./decorators/auth.ts"; import route from "./decorators/route.ts"; import schema from "./decorators/schema.ts"; import jwt from "./decorators/jwt.ts"; -import uuid from "./decorators/uuid.ts"; +import page from "./decorators/page.ts"; -export { body, schema, rest, route, auth , jwt, uuid }; \ No newline at end of file +export { body, schema, rest, route, auth , jwt, page }; \ No newline at end of file diff --git a/src/server/decorators/body.ts b/src/server/decorators/body.ts index 8de7f1d..2ddaab5 100644 --- a/src/server/decorators/body.ts +++ b/src/server/decorators/body.ts @@ -1,20 +1,9 @@ -import HttpError from "../HttpError.ts"; import { makeParameterModifier } from "../Router.ts"; export type BodyType = 'raw' | 'json'; -export default 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 default function body() { + return makeParameterModifier(req => { + return req.body; }); } \ No newline at end of file diff --git a/src/server/decorators/page.ts b/src/server/decorators/page.ts new file mode 100644 index 0000000..1418a1b --- /dev/null +++ b/src/server/decorators/page.ts @@ -0,0 +1,39 @@ +import { makeParameterModifier } from "../Router.ts"; +import { FindCursor } from "https://deno.land/x/mongo@v0.31.2/src/collection/commands/find.ts"; + +export class Page { + public size?: number; + public index?: number; + + public apply(cursor: FindCursor) { + let res = cursor; + + if (this.size !== undefined) { + if (this.index !== undefined) res = res.skip(this.index * this.size); + res = res.limit(this.size); + } + + return res; + } + + public constructor(size?: number); + public constructor(size: number, index?: number); + public constructor(size?: number, index?: number) { + this.size = size; + this.index = index; + } +} + +export default function uuid() { + return makeParameterModifier(req => { + let n: number | undefined = Number.parseInt(req.params.n); + let i: number | undefined = Number.parseInt(req.params.i); + + if (isNaN(n) || n < 1) n = undefined; + if (isNaN(i) || i < 0) i = undefined; + + if (n === undefined) return new Page(); + else if (i === undefined) return new Page(n); + else return new Page(n, i); + }); +} \ No newline at end of file diff --git a/src/server/decorators/schema.ts b/src/server/decorators/schema.ts index 3e802bf..9e0f3dc 100644 --- a/src/server/decorators/schema.ts +++ b/src/server/decorators/schema.ts @@ -1,10 +1,11 @@ // deno-lint-ignore-file no-explicit-any +import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; import HttpError from "../HttpError.ts"; import { makeParameterModifier } from "../Router.ts"; -export type PrimitiveSchema = 'string' | 'number' | 'boolean' | 'object'; +export type PrimitiveSchema = 'string' | 'number' | 'boolean' | 'uuid' | 'object'; export type OptionalSchema = `${PrimitiveSchema}?`; -export type Schema = 'any' | PrimitiveSchema | OptionalSchema | Schema[] | ({ $optional?: boolean; } & { [key: string]: Schema }); +export type Schema = 'any' | 'null' | PrimitiveSchema | OptionalSchema | Schema[] | ({ $optional?: boolean; } & { [key: string]: Schema }); export default function schema(desc: Schema) { function stringify(desc: Schema): string { @@ -24,38 +25,86 @@ export default function schema(desc: Schema) { return res; } - function test(path: string[], val: unknown, desc: Schema) { + async function convert(path: string[], val: unknown, desc: Schema): Promise { const _path = path.join('.'); - if (desc === 'any') return; + if (val instanceof Blob) val = await val.text(); + if (val instanceof ReadableStream) { + const res: Uint8Array[] = []; + + for await (const part of val) { + if (!(part instanceof Uint8Array)) throw new Error(`${_path}: Invalid stream given.`); + res.push(part); + } + + val = await new Blob(res).text(); + } + if (desc === 'any') return val; + 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 (opt && val === undefined) return val; - if (typeof val as string !== type) throw new HttpError(`${_path}: Expected a ${type}, got ${typeof val} instead.`); + switch (type) { + case "null": + if (val === null) return null; + throw new HttpError(`${_path}: Expected null.`); + case "string": return val + ""; + case "uuid": + try { + return new UUID(val + ""); + } + catch { + throw new HttpError(`${_path}: Expected an uuid or a value, convertible to an uuid.`); + } + case "number": { + const res = Number.parseFloat(val + ""); + if (isNaN(res)) throw new HttpError(`${_path}: Expected a number or a value, convertible to a number.`); + return res; + } + case "boolean": { + const res = val + ""; + if (res === 'true') return true; + if (res === 'false') return true; + throw new HttpError(`${_path}: Expected a boolean or a value, convertible to a boolean.`); + } + default: + throw new Error(`${_path}: Unknown type ${type}`); + } } else if (desc instanceof Array) { for (const type of desc) { try { - test(path, val, type); - return; + return convert(path, val, type); } catch { /**/ } } throw new HttpError(`${_path}: 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}: Expected an object, got ${typeof val} instead.`); + if (desc.$optional && val === undefined) return val; + if (val === null) throw new HttpError(`${_path}: Expected an object, got null instead.`); + + if (typeof val === 'string') { + try { + val = JSON.parse(val); + } + catch (e) { + if (val === null) throw new HttpError(`${_path}: Invalid JSON given for object: ${e}`); + } + } + else throw new HttpError(`${_path}: Expected an object or a valid json string.`); for (const key in desc) { if (key === '$optional') continue; - test([ ...path, key ], (val as any)[key], desc[key]); + (val as any)[key] = convert([ ...path, key ], (val as any)[key], desc[key]); } + + return val; } } - return makeParameterModifier((_req, val, name) => (test([name], val, desc), val)); + return makeParameterModifier(async (_req, val, name) => await convert([name], val, desc)); } diff --git a/src/server/decorators/uuid.ts b/src/server/decorators/uuid.ts deleted file mode 100644 index 7793a12..0000000 --- a/src/server/decorators/uuid.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; -import { makeParameterModifier } from "../Router.ts"; -import HttpError from "../HttpError.ts"; - -export default function uuid() { - return makeParameterModifier((_req, val) => { - try { - return new UUID(val); - } - catch { - throw new HttpError("Invalid UUID given."); - } - }); -} \ No newline at end of file diff --git a/src/server/serialize.ts b/src/server/serialize.ts index 297806d..ffada28 100644 --- a/src/server/serialize.ts +++ b/src/server/serialize.ts @@ -1,7 +1,9 @@ +import { stream } from "../utils/utils.ts"; + const undefinedBuff = new Blob([new TextEncoder().encode('undefined')]); const nullBuff = new Blob([new TextEncoder().encode('null')]); -export default async function serialize(val: unknown, depth = 16): Promise { +export default async function serialize(val: unknown, depth = 16): Promise> { while (true) { if (depth <= 0) throw new Error("Call depth exceeded limit."); @@ -15,14 +17,16 @@ export default async function serialize(val: unknown, depth = 16): Promise depth--; } - if (val === undefined) return undefinedBuff; - if (val === null) return nullBuff; - if (typeof val === 'string') return new Blob([val]); - if (val instanceof Blob) return val; + if (val === undefined) return undefinedBuff.stream(); + if (val === null) return nullBuff.stream(); + if (typeof val === 'string') return stream(val); + if (val instanceof Blob) return val.stream(); + if (val instanceof ReadableStream) return val; + if (val instanceof Uint8Array) return new Blob([val]).stream(); if (val.toString !== Object.prototype.toString && val.toString instanceof Function) { val = val.toString(); } - return new Blob([JSON.stringify(val)]); + return stream(JSON.stringify(val)); } diff --git a/src/utils/now.ts b/src/utils/now.ts deleted file mode 100644 index 50379df..0000000 --- a/src/utils/now.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function now() { - return new Date().getTime() / 1000; -} \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..7002f99 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,6 @@ +export function now() { + return new Date().getTime() / 1000; +} +export function stream(...text: string[]) { + return new Blob(text).stream(); +} \ No newline at end of file