diff --git a/src/AppDatabase.ts b/src/AppDatabase.ts new file mode 100644 index 0000000..e281539 --- /dev/null +++ b/src/AppDatabase.ts @@ -0,0 +1,15 @@ +import { Collection, Database } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; +import Image from "./models/Image.ts"; +import User from "./models/User.ts"; + +export default class AppDatabase { + public readonly db: Database; + public readonly users: Collection; + public readonly images: Collection; + + public constructor(db: Database) { + this.db = db; + this.users = db.collection('users'); + this.images = db.collection('images'); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 77842e9..2c55d88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { MongoClient } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; import { RootRouter } from "./routers/RootRouter.ts"; import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"; +import AppDatabase from "./AppDatabase.ts"; export default async function clonegur() { let salt; @@ -17,7 +18,7 @@ export default async function clonegur() { db: 'clonegur', servers: [ { host: '127.0.0.1', port: 27017 } ] }); - await new RootRouter(salt, db).attach(Deno.listen({ port: 4000, hostname: 'localhost' })); + return new RootRouter(salt, new AppDatabase(db)); } -await clonegur(); +(await clonegur()).attach(Deno.listen({ port: 4000, hostname: 'localhost' })); diff --git a/src/routers/ImageRouter.ts b/src/routers/ImageRouter.ts index a716850..c94ff20 100644 --- a/src/routers/ImageRouter.ts +++ b/src/routers/ImageRouter.ts @@ -1,16 +1,15 @@ -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, body, headers, 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"; import { Headers } from "../server/RestRequest.ts"; import { convert } from "../server/decorators/schema.ts"; import { now } from "../utils/utils.ts"; +import AppDatabase from "../AppDatabase.ts"; export default class ImageRouter extends AppRouter { public static deserialize(image: Image) { @@ -25,7 +24,7 @@ export default class ImageRouter extends AppRouter { @rest('GET', '/') async get(@schema('uuid') id: UUID, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) { - const image = await this.images.findOne({ _id: new UUID(id) }); + const image = await this.db.images.findOne({ _id: new UUID(id) }); if ( !image || @@ -36,14 +35,14 @@ export default class ImageRouter extends AppRouter { } @rest('GET', '/feed') async self(@page() page: Page) { - const res = await page.apply(this.images.find({})).toArray(); + const res = await page.apply(this.db.images.find({})).toArray(); if (!res) throw new HttpError('User not found.'); return res.map(v => ImageRouter.deserialize(v)); } @rest('POST', '/upload') async upload(@body() body: Blob, @headers() headers: Headers, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) { - const user = await this.users.findOne({ username: jwt.name }); + const user = await this.db.users.findOne({ username: jwt.name }); if (!user) throw new HttpError("You don't exist."); // Parse body @@ -89,8 +88,8 @@ export default class ImageRouter extends AppRouter { // Write to DB try { - await this.images.insertOne(img); - await this.users.updateOne({ username: user.username }, { $push: { images: img._id } }); + await this.db.images.insertOne(img); + await this.db.users.updateOne({ username: user.username }, { $push: { images: img._id } }); return ImageRouter.deserialize(img); } catch (e) { @@ -101,18 +100,18 @@ export default class ImageRouter extends AppRouter { @rest('POST', '/change') async change(@body() raw: unknown, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) { const body = await convert(raw, { id: 'uuid', name: 'string?', visibility: 'number?' }); - const user = await this.users.findOne({ username: jwt.name }); + const user = await this.db.users.findOne({ username: jwt.name }); if (!user) throw new HttpError("You don't exist."); - const img = await this.images.findOne({ _id: body.id }); + const img = await this.db.images.findOne({ _id: body.id }); if (!img) throw new HttpError("Image doesn't exist."); if (user.username !== img.author) throw new HttpError("You don't own the image."); - await this.images.updateOne({ _id: body.id }, { $set: { name: body.name, visibility: body.visibility } }); + await this.db.images.updateOne({ _id: body.id }, { $set: { name: body.name, visibility: body.visibility } }); return ImageRouter.deserialize(img); } - public constructor(private images: Collection, private users: Collection) { + public constructor(private db: AppDatabase) { super(); } } \ No newline at end of file diff --git a/src/routers/RootRouter.ts b/src/routers/RootRouter.ts index fde9c05..3b17667 100644 --- a/src/routers/RootRouter.ts +++ b/src/routers/RootRouter.ts @@ -1,12 +1,10 @@ -import { Database } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; import UserRouter from "../routers/UserRouter.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"; import { stream } from "../utils/utils.ts"; +import AppDatabase from "../AppDatabase.ts"; export class RootRouter extends AppRouter { @route('users/*') users; @@ -17,13 +15,10 @@ export class RootRouter extends AppRouter { return new RestResponse().body(stream('Page not found :/')).status(404); } - constructor(salt: string, db: Database) { + constructor(salt: string, db: AppDatabase) { super(); - const users = db.collection('users'); - const images = db.collection('images'); - - this.users = new UserRouter(salt, users); - this.images = new ImageRouter(images, users); + this.users = new UserRouter(salt, db); + this.images = new ImageRouter(db); } } diff --git a/src/routers/UserRouter.ts b/src/routers/UserRouter.ts index 2f60f20..2a1b99c 100644 --- a/src/routers/UserRouter.ts +++ b/src/routers/UserRouter.ts @@ -1,4 +1,3 @@ -import { Collection } from "https://deno.land/x/mongo@v0.31.2/mod.ts"; import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"; import { auth, body, rest, schema } from "../server/decorators.ts"; @@ -8,53 +7,60 @@ import AppRouter from "./AppRouter.ts"; import jwt from "../server/decorators/jwt.ts"; import JWT, { JWTPayload } from "../utils/JWT.ts"; import { now } from "../utils/utils.ts"; +import { convert } from "../server/decorators/schema.ts"; +import AppDatabase from "../AppDatabase.ts"; +import { Visibility } from "../models/Image.ts"; -export interface SignupRequest { - username: string; - password: string; -} export interface LoginRequest { username: string; password: string; } export default class UserRouter extends AppRouter { - public static deserialize(user: User) { - return { username: user.username, images: user.images }; + public deserialize(user: User, self = false) { + let images = user.images; + if (!self) { + images = []; + Promise.all(user.images.map(async v => { + if ((await this.db.images.findOne({ _id: v }))?.visibility === Visibility.Public) { + images.push(v); + } + })); + } + return { username: user.username, images }; } @rest('GET', '/') - async get(@schema('string') username: string) { - const res = await this.users.findOne({ username }); - + async get(@schema('string') username: string, @jwt('salt', true) @auth() jwt: JWTPayload) { + const res = await this.db.users.findOne({ username }); if (res === undefined) throw new HttpError('User not found.'); - return UserRouter.deserialize(res); + return this.deserialize(res, jwt.name === username); } @rest('GET', '/self') - async self(@jwt(self => self.salt, true) @auth() auth: JWTPayload) { + async self(@jwt('salt', true) @auth() auth: JWTPayload) { if (auth === undefined) throw new HttpError('You are not logged in.'); - const res = await this.users.findOne({ username: auth.name }); + const res = await this.db.users.findOne({ username: auth.name }); if (res === undefined) throw new HttpError('User not found.'); - return UserRouter.deserialize(res); + return this.deserialize(res); } @rest('POST', '/signup') - async signup( - @schema({ + async signup(@body() raw: unknown) { + const body = await convert(raw, { username: 'string', password: 'string', - }) @body() body: SignupRequest - ) { - if (await this.users.countDocuments({ username: body.username }) > 0) { + }); + + if (await this.db.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({ + await this.db.users.insertOne({ username: body.username, password: password, images: [], @@ -63,13 +69,12 @@ export default class UserRouter extends AppRouter { return {}; } @rest('POST', '/login') - async login( - @schema({ + async login(@body() raw: unknown) { + const body = await convert(raw, { username: 'string', password: 'string' - }) @body() body: LoginRequest - ) { - const res = await this.users.findOne({ username: body.username }); + }); + const res = await this.db.users.findOne({ username: body.username }); if (!res) throw new HttpError('Incorrect username or password.'); const hashed = await bcrypt.hash(body.password, this.salt); @@ -83,8 +88,8 @@ export default class UserRouter extends AppRouter { }, this.salt); } - public constructor(private salt: string, private users: Collection) { + public constructor(private salt: string, private db: AppDatabase) { super(); - users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] }); + db.users.createIndexes({ indexes: [ { key: { username: 1 }, name: 'Username Index' } ] }); } } \ No newline at end of file