From 9a63331a9549dad5baedacc88fb41badf7c30ca1 Mon Sep 17 00:00:00 2001 From: topchetoeu <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 30 Jun 2023 03:37:07 +0300 Subject: [PATCH] feat: add angular support to server --- .gitignore | 1 + .vscode/launch.json | 4 +- .vscode/settings.json | 1 + backend/clonegur.ts | 1 + backend/routers/ImageRouter.ts | 18 +++++ backend/routers/RootRouter.ts | 14 +++- backend/routers/UserRouter.ts | 2 +- backend/server/RestResponse.ts | 7 ++ backend/server/Router.ts | 16 ++-- backend/server/decorators/rest.ts | 2 +- backend/server/staticHandler.ts | 15 ++-- frontend/angular.json | 6 ++ frontend/src/app/services/users.service.ts | 79 +++++++++++++++++++ frontend/src/environments/environment.prod.ts | 8 ++ frontend/src/environments/environment.ts | 5 ++ 15 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 frontend/src/app/services/users.service.ts create mode 100644 frontend/src/environments/environment.prod.ts create mode 100644 frontend/src/environments/environment.ts diff --git a/.gitignore b/.gitignore index 76c7240..9e95882 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /backend/keys /backend/images +/backend/static /backend/pacakage.json /frontend/dist diff --git a/.vscode/launch.json b/.vscode/launch.json index 95f171d..d504051 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,8 @@ "name": "Launch Backend", "type": "pwa-node", "request": "launch", - "program": "${workspaceFolder}/main.ts", - "cwd": "${workspaceFolder}/cwd", + "program": "${workspaceFolder}/backend/main.ts", + "cwd": "${workspaceFolder}/backend", "runtimeExecutable": "deno", "runtimeArgs": [ "run", diff --git a/.vscode/settings.json b/.vscode/settings.json index 44aad0e..1383829 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "deno.enable": true, "deno.unstable": true, + "deno.enablePaths": [ "backend" ], "typescript.tsdk": "node_modules\\typescript\\lib" } \ No newline at end of file diff --git a/backend/clonegur.ts b/backend/clonegur.ts index f0d6be0..a8b84d6 100644 --- a/backend/clonegur.ts +++ b/backend/clonegur.ts @@ -11,6 +11,7 @@ export default async function clonegur() { } catch { salt = await bcrypt.genSalt(); + await Deno.mkdir('keys', { recursive: true }); await Deno.writeFile('keys/salt.txt', new TextEncoder().encode(salt)); } diff --git a/backend/routers/ImageRouter.ts b/backend/routers/ImageRouter.ts index c94ff20..b60a2eb 100644 --- a/backend/routers/ImageRouter.ts +++ b/backend/routers/ImageRouter.ts @@ -10,6 +10,7 @@ import { Headers } from "../server/RestRequest.ts"; import { convert } from "../server/decorators/schema.ts"; import { now } from "../utils/utils.ts"; import AppDatabase from "../AppDatabase.ts"; +import RestResponse from "../server/RestResponse.ts"; export default class ImageRouter extends AppRouter { public static deserialize(image: Image) { @@ -33,6 +34,23 @@ export default class ImageRouter extends AppRouter { return ImageRouter.deserialize(image); } + @rest('GET', '/img/:id') + async file(id: string, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) { + try { + const start = await Deno.realPath("images"); + const file = await Deno.realPath(`images/${id}`); + if (!file.startsWith(start)) throw new HttpError("What the fuck are you doing?", 418); + } + catch (e) { + if (!(e instanceof HttpError)) throw new HttpError("File doesn't exist.", 404); + } + + const img = await this.db.images.findOne({ _id: new UUID(id.split('.')[0]) }).catch(() => undefined); + if (img?.visibility === Visibility.Private && img.author !== jwt?.name) throw new HttpError("File doesn't exist.", 404); + return new RestResponse() + .body((await Deno.open(`images/${id}`)).readable) + .contentType(id.split('.')[1]); + } @rest('GET', '/feed') async self(@page() page: Page) { const res = await page.apply(this.db.images.find({})).toArray(); diff --git a/backend/routers/RootRouter.ts b/backend/routers/RootRouter.ts index 3b17667..f8a1b15 100644 --- a/backend/routers/RootRouter.ts +++ b/backend/routers/RootRouter.ts @@ -5,10 +5,19 @@ import RestResponse from "../server/RestResponse.ts"; import { rest, route } from "../server/decorators.ts"; import { stream } from "../utils/utils.ts"; import AppDatabase from "../AppDatabase.ts"; +import staticHandler from "../server/staticHandler.ts"; export class RootRouter extends AppRouter { - @route('users/*') users; - @route('images/*') images; + @route('api/users/*') users; + @route('api/images/*') images; + + @rest('GET', '/') + async index() { + return new RestResponse() + .body((await Deno.open('static/index.html')).readable) + .contentType('html'); + } + @route('/*') static; @rest('*', '*') default() { @@ -18,6 +27,7 @@ export class RootRouter extends AppRouter { constructor(salt: string, db: AppDatabase) { super(); + this.static = staticHandler('static'); this.users = new UserRouter(salt, db); this.images = new ImageRouter(db); } diff --git a/backend/routers/UserRouter.ts b/backend/routers/UserRouter.ts index d1856df..6102229 100644 --- a/backend/routers/UserRouter.ts +++ b/backend/routers/UserRouter.ts @@ -44,7 +44,7 @@ export default class UserRouter extends AppRouter { if (res === undefined) throw new HttpError('User not found.'); - return this.deserialize(res); + return this.deserialize(res, true); } @rest('POST', '/signup') diff --git a/backend/server/RestResponse.ts b/backend/server/RestResponse.ts index d4e694f..a3e2e48 100644 --- a/backend/server/RestResponse.ts +++ b/backend/server/RestResponse.ts @@ -1,3 +1,5 @@ +import { lookup } from "https://deno.land/x/mrmime@v1.0.1/mod.ts" + import { stream } from "../utils/utils.ts"; import { Headers } from "./RestRequest.ts"; @@ -14,6 +16,11 @@ export default class RestResponse { public get statusMessage() { return this.#statusMsg; } public get content() { return this.#body; } + public contentType(name: string) { + const res = lookup(name); + if (res) this.header('content-type', res); + return this; + } public header(name: string, val: string | string[]) { this.headers[name] = val; return this; diff --git a/backend/server/Router.ts b/backend/server/Router.ts index 7b02b8b..b1c827a 100644 --- a/backend/server/Router.ts +++ b/backend/server/Router.ts @@ -97,11 +97,17 @@ export default class Router { } } - public async attach(server: AsyncIterable) { - for await (const conn of server) { - for await (const req of Deno.serveHttp(conn)) { - const r = await this.handle(RestRequest.fromMessage(req)); - if (r) req.respondWith(r.toFetchResponse()); + public async attach(server: Deno.Listener) { + while (true) { + for await (const conn of server) { + (async () => { + for await (const req of Deno.serveHttp(conn)) { + console.log(req.request.url); + const r = await this.handle(RestRequest.fromMessage(req)); + console.log(req.request.url); + if (r) req.respondWith(r.toFetchResponse()); + } + })(); } } } diff --git a/backend/server/decorators/rest.ts b/backend/server/decorators/rest.ts index 703661e..7c04706 100644 --- a/backend/server/decorators/rest.ts +++ b/backend/server/decorators/rest.ts @@ -50,7 +50,7 @@ export default function rest args.push(arg); } - const res = r[key].apply(r, args); + const res = await r[key].apply(r, args); if (res instanceof RestResponse) return res; return new RestResponse().body(await serialize(res)); } diff --git a/backend/server/staticHandler.ts b/backend/server/staticHandler.ts index 1b31ae8..3126a35 100644 --- a/backend/server/staticHandler.ts +++ b/backend/server/staticHandler.ts @@ -5,13 +5,16 @@ export default function staticHandler(path: string): Handler { return { async handle(req) { try { - const stream = await Deno.open(`${req.url}/${path}`); - const res: Uint8Array[] = []; - for await (const bit of stream.readable) res.push(bit); - stream.close(); - return new RestResponse().body(new Blob(res).stream()); + const realPath = await Deno.realPath(`${path}/${req.url}`); + const stream = await Deno.open(realPath); + const i = realPath.lastIndexOf('.'); + const res = new RestResponse().body(stream.readable); + + if (i >= 0) res.contentType(realPath.substring(i + 1)); + return res; } - catch { + catch (e) { + console.log(e); return undefined; } } diff --git a/frontend/angular.json b/frontend/angular.json index 924f72b..446ecc6 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -48,6 +48,12 @@ "maximumError": "4kb" } ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], "outputHashing": "all" }, "development": { diff --git a/frontend/src/app/services/users.service.ts b/frontend/src/app/services/users.service.ts new file mode 100644 index 0000000..7e1aa7c --- /dev/null +++ b/frontend/src/app/services/users.service.ts @@ -0,0 +1,79 @@ +import { Injectable, WritableSignal, signal } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { first, firstValueFrom } from 'rxjs'; + +export enum Role { + Admin = 3, + Employer = 2, + User = 1, + API = 0, + Deactivated = -1, +} + +export interface User { + username: string; + role: Role; + projects: string[]; +} +export interface ThisUser extends User { + chats: string[]; + email: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class UsersService { + private readonly url = environment.apiURL + '/users'; + public $user = signal(undefined); + + public get token() { + return localStorage.getItem('token') ?? undefined; + } + public set token(token: string | undefined) { + if (token === undefined) localStorage.removeItem('token'); + else localStorage.setItem('token', token); + } + + public async updateUser() { + if (this.token) { + const user = await firstValueFrom(this.http.get(`${this.url}/get?token=${this.token}`)); + this.$user.set(user); + } + else this.$user.set(undefined); + } + + public async logoff() { + if (!this.token) return; + await firstValueFrom(this.http.post(this.url + `/logout?token=${this.token}`, {})); + this.token = undefined; + this.$user.set(undefined); + } + public async login(username: string, password: string) { + await this.logoff(); + const token = (await firstValueFrom(this.http.post(this.url + `/login`, { username, password }))).token; + this.token = token; + await this.updateUser(); + } + public async signup(username: string, password: string, email: string) { + await this.logoff(); + await firstValueFrom(this.http.post(this.url + `/signup`, { username, password, email })); + await this.updateUser(); + } + + public async requestCode(username: string) { + await firstValueFrom(this.http.post(this.url + `/requestCode`, { username })); + } + public async confirm(username: string, code: string) { + await firstValueFrom(this.http.post(this.url + `/confirm`, { username, code })); + } + + public async all() { + return await firstValueFrom(this.http.get(this.url + `/all`)); + } + + public constructor(private http: HttpClient) { + this.updateUser(); + } +} diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts new file mode 100644 index 0000000..4f5dfb8 --- /dev/null +++ b/frontend/src/environments/environment.prod.ts @@ -0,0 +1,8 @@ +declare function $__getHTTP(): string; +declare function $__getWS(): string; + +export const environment = { + production: true, + apiURL: $__getHTTP(), + wsURL: $__getWS() +}; diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts new file mode 100644 index 0000000..b92ae33 --- /dev/null +++ b/frontend/src/environments/environment.ts @@ -0,0 +1,5 @@ +export const environment = { + production: false, + apiURL: 'http://127.0.0.1/api', + wsURL: 'ws://127.0.0.1/api' +};