feat: add angular support to server
This commit is contained in:
parent
7318cac349
commit
9a63331a95
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@
|
||||
|
||||
/backend/keys
|
||||
/backend/images
|
||||
/backend/static
|
||||
/backend/pacakage.json
|
||||
|
||||
/frontend/dist
|
||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -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",
|
||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.unstable": true,
|
||||
"deno.enablePaths": [ "backend" ],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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')
|
||||
|
@ -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;
|
||||
|
@ -97,11 +97,17 @@ export default class Router {
|
||||
}
|
||||
}
|
||||
|
||||
public async attach(server: AsyncIterable<Deno.Conn>) {
|
||||
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());
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export default function rest<KeyT extends keyof T & string, T extends Base<KeyT>
|
||||
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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,12 @@
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
|
79
frontend/src/app/services/users.service.ts
Normal file
79
frontend/src/app/services/users.service.ts
Normal file
@ -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<ThisUser | undefined>(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<ThisUser>(`${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<any>(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<string>(this.url + `/requestCode`, { username }));
|
||||
}
|
||||
public async confirm(username: string, code: string) {
|
||||
await firstValueFrom(this.http.post<string>(this.url + `/confirm`, { username, code }));
|
||||
}
|
||||
|
||||
public async all() {
|
||||
return await firstValueFrom(this.http.get<User[]>(this.url + `/all`));
|
||||
}
|
||||
|
||||
public constructor(private http: HttpClient) {
|
||||
this.updateUser();
|
||||
}
|
||||
}
|
8
frontend/src/environments/environment.prod.ts
Normal file
8
frontend/src/environments/environment.prod.ts
Normal file
@ -0,0 +1,8 @@
|
||||
declare function $__getHTTP(): string;
|
||||
declare function $__getWS(): string;
|
||||
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiURL: $__getHTTP(),
|
||||
wsURL: $__getWS()
|
||||
};
|
5
frontend/src/environments/environment.ts
Normal file
5
frontend/src/environments/environment.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiURL: 'http://127.0.0.1/api',
|
||||
wsURL: 'ws://127.0.0.1/api'
|
||||
};
|
Loading…
Reference in New Issue
Block a user