From 2b79aff2c767401a7dd4195cf038669f26a26623 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 27 Jun 2023 06:55:56 +0300 Subject: [PATCH] add image router --- src/models/Image.ts | 16 +++++++++++++++ src/routers/ImageRouter.ts | 38 +++++++++++++++++++++++++++++++++++ src/routers/RootRouter.ts | 22 +++++++++++++------- src/routers/UserRouter.ts | 2 +- src/server/decorators.ts | 4 +++- src/server/decorators/uuid.ts | 14 +++++++++++++ src/server/serialize.ts | 19 +++++++++--------- 7 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 src/models/Image.ts create mode 100644 src/routers/ImageRouter.ts create mode 100644 src/server/decorators/uuid.ts diff --git a/src/models/Image.ts b/src/models/Image.ts new file mode 100644 index 0000000..262b429 --- /dev/null +++ b/src/models/Image.ts @@ -0,0 +1,16 @@ +import { UUID } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; + +export enum Visibility { + Public, + Unlisted, + Private, +} + +export default interface Image { + _id: UUID; + backingURL: string; + name: string; + visibility: Visibility; + created: Date; + author: UUID; +} \ No newline at end of file diff --git a/src/routers/ImageRouter.ts b/src/routers/ImageRouter.ts new file mode 100644 index 0000000..002f7ec --- /dev/null +++ b/src/routers/ImageRouter.ts @@ -0,0 +1,38 @@ +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 { 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"; + +export default class ImageRouter extends AppRouter { + public static serialize(image: Image) { + return { + author: image.author, + created: image.created.getTime() / 1000, + name: image.name, + visibility: image.visibility, + id: image._id, + }; + } + + @rest('GET', '/') + async get(@uuid() @schema('string') id: string, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) { + const image = await this.images.findOne({ _id: new UUID(id) }); + + if ( + !image || + image.visibility === Visibility.Private && image.author !== jwt?.name + ) throw new HttpError("Image doesn't exist."); + + return ImageRouter.serialize(image); + } + + public constructor(private salt: string, private images: Collection, private users: Collection) { + super(); + users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] }); + } +} \ No newline at end of file diff --git a/src/routers/RootRouter.ts b/src/routers/RootRouter.ts index d48a63d..15bfab8 100644 --- a/src/routers/RootRouter.ts +++ b/src/routers/RootRouter.ts @@ -1,20 +1,28 @@ -import { Collection } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; +import { Database } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; import UserRouter from "../routers/UserRouter.ts"; -import User from "../models/User.ts"; -import Response from "../server/RestResponse.ts"; -import Router from "../server/Router.ts"; +import ImageRouter from "./ImageRouter.ts"; +import AppRouter from "./AppRouter.ts"; +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"; -export class RootRouter extends Router { +export class RootRouter extends AppRouter { @route('users/*') users; + @route('images/*') images; @rest('*', '*') default() { - return new Response().body(new Blob(['Page not found :/'])).status(404); + return new RestResponse().body(new Blob(['Page not found :/'])).status(404); } - constructor(salt: string, users: Collection) { + constructor(salt: string, db: Database) { super(); + + const users = db.collection('users'); + const images = db.collection('images'); + this.users = new UserRouter(salt, users); + this.images = new ImageRouter(salt, images, users); } } diff --git a/src/routers/UserRouter.ts b/src/routers/UserRouter.ts index 1a5e3e7..009969a 100644 --- a/src/routers/UserRouter.ts +++ b/src/routers/UserRouter.ts @@ -66,7 +66,7 @@ export default class UserRouter extends AppRouter { ) { const res = await this.users.findOne({ username: body.username }); if (!res) throw new HttpError('Incorrect username or password.'); - const hashed = await bcrypt.hash(res.password, this.salt); + const hashed = await bcrypt.hash(body.password, this.salt); if (res.password !== hashed) throw new HttpError('Incorrect username or password.'); diff --git a/src/server/decorators.ts b/src/server/decorators.ts index cf95541..1a7e36d 100644 --- a/src/server/decorators.ts +++ b/src/server/decorators.ts @@ -3,5 +3,7 @@ import rest from "./decorators/rest.ts"; 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"; -export { body, schema, rest, route, auth }; \ No newline at end of file +export { body, schema, rest, route, auth , jwt, uuid }; \ No newline at end of file diff --git a/src/server/decorators/uuid.ts b/src/server/decorators/uuid.ts new file mode 100644 index 0000000..7793a12 --- /dev/null +++ b/src/server/decorators/uuid.ts @@ -0,0 +1,14 @@ +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 a12bc2e..297806d 100644 --- a/src/server/serialize.ts +++ b/src/server/serialize.ts @@ -2,12 +2,12 @@ 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 { - while(true) { - if(depth <= 0) throw new Error("Call depth exceeded limit."); + 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'); + 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; @@ -15,11 +15,12 @@ export default async function serialize(val: unknown, depth = 16): Promise depth--; } - if(val === undefined) return undefinedBuff; - if(val === null) return nullBuff; - if(val instanceof Blob) return val; + if (val === undefined) return undefinedBuff; + if (val === null) return nullBuff; + if (typeof val === 'string') return new Blob([val]); + if (val instanceof Blob) return val; - while(typeof val !== 'string' && val && val.toString !== Object.prototype.toString && val.toString instanceof Function) { + if (val.toString !== Object.prototype.toString && val.toString instanceof Function) { val = val.toString(); }