From ebe52b3aade134be491aebbea9888e8d619b2f85 Mon Sep 17 00:00:00 2001 From: topchetoeu <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 30 Jun 2023 13:19:21 +0300 Subject: [PATCH] start frontend --- backend/main.ts | 2 +- backend/models/Image.ts | 1 + backend/routers/ImageRouter.ts | 31 +++++--- backend/routers/RootRouter.ts | 4 + backend/server/Router.ts | 7 +- backend/server/decorators/jwt.ts | 5 +- backend/server/decorators/rest.ts | 2 +- backend/server/serialize.ts | 1 - frontend/.vscode/launch.json | 18 +++++ frontend/.vscode/tasks.json | 42 +++++++++++ frontend/src/app/app-routing.module.ts | 17 ++++- frontend/src/app/app.component.html | 17 +++++ frontend/src/app/app.component.scss | 41 ++++++++++ frontend/src/app/app.component.ts | 3 +- frontend/src/app/app.module.ts | 18 ++++- .../app/page-home/page-home.component.html | 10 +++ .../app/page-home/page-home.component.scss | 75 +++++++++++++++++++ .../src/app/page-home/page-home.component.ts | 21 ++++++ .../app/page-login/page-login.component.html | 5 ++ .../app/page-login/page-login.component.scss | 22 ++++++ .../app/page-login/page-login.component.ts | 26 +++++++ .../page-signup/page-signup.component.html | 5 ++ .../page-signup/page-signup.component.scss | 22 ++++++ .../app/page-signup/page-signup.component.ts | 29 +++++++ .../page-upload/page-upload.component.html | 10 +++ .../page-upload/page-upload.component.scss | 28 +++++++ .../app/page-upload/page-upload.component.ts | 33 ++++++++ .../app/page-user/page-user.component.html | 1 + .../app/page-user/page-user.component.scss | 0 .../src/app/page-user/page-user.component.ts | 21 ++++++ frontend/src/app/services/images.service.ts | 56 ++++++++++++++ frontend/src/app/services/users.service.ts | 58 ++++++-------- frontend/src/environments/environment.prod.ts | 2 - frontend/src/environments/environment.ts | 1 - frontend/src/styles.scss | 40 +++++++++- 35 files changed, 614 insertions(+), 60 deletions(-) create mode 100644 frontend/.vscode/launch.json create mode 100644 frontend/.vscode/tasks.json create mode 100644 frontend/src/app/page-home/page-home.component.html create mode 100644 frontend/src/app/page-home/page-home.component.scss create mode 100644 frontend/src/app/page-home/page-home.component.ts create mode 100644 frontend/src/app/page-login/page-login.component.html create mode 100644 frontend/src/app/page-login/page-login.component.scss create mode 100644 frontend/src/app/page-login/page-login.component.ts create mode 100644 frontend/src/app/page-signup/page-signup.component.html create mode 100644 frontend/src/app/page-signup/page-signup.component.scss create mode 100644 frontend/src/app/page-signup/page-signup.component.ts create mode 100644 frontend/src/app/page-upload/page-upload.component.html create mode 100644 frontend/src/app/page-upload/page-upload.component.scss create mode 100644 frontend/src/app/page-upload/page-upload.component.ts create mode 100644 frontend/src/app/page-user/page-user.component.html create mode 100644 frontend/src/app/page-user/page-user.component.scss create mode 100644 frontend/src/app/page-user/page-user.component.ts create mode 100644 frontend/src/app/services/images.service.ts diff --git a/backend/main.ts b/backend/main.ts index 6496e2e..bba0e5e 100644 --- a/backend/main.ts +++ b/backend/main.ts @@ -1,5 +1,5 @@ import clonegur from "./clonegur.ts"; const app = await clonegur(); -const server = Deno.listen({ port: 4000, hostname: 'localhost' }); +const server = Deno.listen({ port: 80, hostname: '127.0.0.1' }); app.attach(server); diff --git a/backend/models/Image.ts b/backend/models/Image.ts index ff12467..c4345e5 100644 --- a/backend/models/Image.ts +++ b/backend/models/Image.ts @@ -12,4 +12,5 @@ export default interface Image { name: string; visibility: Visibility; created: number; + file: string; } \ No newline at end of file diff --git a/backend/routers/ImageRouter.ts b/backend/routers/ImageRouter.ts index b60a2eb..08eecd7 100644 --- a/backend/routers/ImageRouter.ts +++ b/backend/routers/ImageRouter.ts @@ -5,7 +5,7 @@ import { JWTPayload } from "../utils/JWT.ts"; import AppRouter from "./AppRouter.ts"; import HttpError from "../server/HttpError.ts"; import Image, { Visibility } from "../models/Image.ts"; -import { Page } from "../server/decorators/page.ts"; +import uuid, { 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"; @@ -20,6 +20,7 @@ export default class ImageRouter extends AppRouter { name: image.name, visibility: image.visibility, id: image._id, + file: image.file, }; } @@ -53,7 +54,7 @@ export default class ImageRouter extends AppRouter { } @rest('GET', '/feed') async self(@page() page: Page) { - const res = await page.apply(this.db.images.find({})).toArray(); + const res = await page.apply(this.db.images.find({ visibility: Visibility.Public })).sort({ created: -1 }).toArray(); if (!res) throw new HttpError('User not found.'); return res.map(v => ImageRouter.deserialize(v)); } @@ -95,11 +96,18 @@ export default class ImageRouter extends AppRouter { req.name ??= new UUID().toString(); req.visibility = 0; if (req.visibility < 0 || req.visibility > 2) throw new HttpError("body.visibility: Must be 0, 1, or 2"); - + const id = new UUID(); // Create file - const img: Image = { _id: new UUID(), author: user.username, created: now(), name: req.name!, visibility: req.visibility }; + const img: Image = { + _id: id, + author: user.username, + created: now(), + name: req.name!, + visibility: req.visibility, + file: `${id}.${ext}` + }; await Deno.mkdir('images', { recursive: true }); - const out = await Deno.open(`images/${img._id}.${ext}`, { write: true, create: true }); + const out = await Deno.open(`images/${id}.${ext}`, { write: true, create: true }); for await (const bit of rawFile.stream()) out.write(bit); out.close(); @@ -107,29 +115,30 @@ export default class ImageRouter extends AppRouter { // Write to DB try { await this.db.images.insertOne(img); - await this.db.users.updateOne({ username: user.username }, { $push: { images: img._id } }); + await this.db.users.updateOne({ username: user.username }, { $push: { images: id } }); return ImageRouter.deserialize(img); } catch (e) { - await Deno.remove(`images/${img._id}.${ext}`); + await Deno.remove(`images/${id}.${ext}`); throw e; } } @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?' }); + async change(@uuid() id: UUID, @body() raw: unknown, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) { + const body = await convert(raw, { name: 'string?', visibility: 'number?' }); const user = await this.db.users.findOne({ username: jwt.name }); if (!user) throw new HttpError("You don't exist."); - const img = await this.db.images.findOne({ _id: body.id }); + const img = await this.db.images.findOne({ _id: 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.db.images.updateOne({ _id: body.id }, { $set: { name: body.name, visibility: body.visibility } }); + await this.db.images.updateOne({ _id: id }, { $set: { name: body.name, visibility: body.visibility } }); return ImageRouter.deserialize(img); } public constructor(private db: AppDatabase) { super(); + db.images.createIndexes({ indexes: [ { key: { created: -1 }, name: 'Image Order' } ] }); } } \ No newline at end of file diff --git a/backend/routers/RootRouter.ts b/backend/routers/RootRouter.ts index f8a1b15..c1c8d74 100644 --- a/backend/routers/RootRouter.ts +++ b/backend/routers/RootRouter.ts @@ -19,6 +19,10 @@ export class RootRouter extends AppRouter { } @route('/*') static; + @rest('OPTIONS', '*') + options() { + return new RestResponse(); + } @rest('*', '*') default() { return new RestResponse().body(stream('Page not found :/')).status(404); diff --git a/backend/server/Router.ts b/backend/server/Router.ts index b1c827a..cd8cfb0 100644 --- a/backend/server/Router.ts +++ b/backend/server/Router.ts @@ -65,7 +65,12 @@ export default class Router { if (_req) { try { const res = await hnd.handler.handle(_req); - if (res) return res; + if (res) { + // damn you cors + res.header('Access-Control-Allow-Origin', '*') + res.header('Access-Control-Allow-Headers', '*') + return res; + } } catch (e) { const res = await this.onError(_req, e); diff --git a/backend/server/decorators/jwt.ts b/backend/server/decorators/jwt.ts index 8c9986e..e5f4908 100644 --- a/backend/server/decorators/jwt.ts +++ b/backend/server/decorators/jwt.ts @@ -6,7 +6,10 @@ import Router, { makeParameterModifier } from "../Router.ts"; export default function jwt(salt: ((self: T) => string) | string, required = false) { return makeParameterModifier(function (_req, val?: string) { - if (val === undefined) return undefined; + if (val === undefined) { + if (required) throw new HttpError('You are not logged in.'); + else return undefined; + } const s = typeof salt === 'function' ? salt(this) : (this as any)[salt] as string; try { const res = JWT.decode(val, s); diff --git a/backend/server/decorators/rest.ts b/backend/server/decorators/rest.ts index 7c04706..7b9c6da 100644 --- a/backend/server/decorators/rest.ts +++ b/backend/server/decorators/rest.ts @@ -5,7 +5,7 @@ import RestResponse from "../RestResponse.ts"; import Router, { ProcessFunc, addMetaQuery } from "../Router.ts"; import serialize from "../serialize.ts"; -export type HttpMethod = '*' | 'GET' | 'POST' | 'CHANGE' | 'DELETE' | 'PUT' | 'UPDATE'; +export type HttpMethod = '*' | 'GET' | 'POST' | 'CHANGE' | 'DELETE' | 'PUT' | 'UPDATE' | 'OPTIONS'; type Base = Router & { [X in KeyT]: Function; }; type ModArray = ([ProcessFunc, ...ProcessFunc[]] | undefined)[]; diff --git a/backend/server/serialize.ts b/backend/server/serialize.ts index 44699a6..ee2cd33 100644 --- a/backend/server/serialize.ts +++ b/backend/server/serialize.ts @@ -19,7 +19,6 @@ export default async function serialize(val: unknown, depth = 16): Promise/**" + ] + } + ] +} \ No newline at end of file diff --git a/frontend/.vscode/tasks.json b/frontend/.vscode/tasks.json new file mode 100644 index 0000000..71c730c --- /dev/null +++ b/frontend/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} \ No newline at end of file diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 0297262..b32fdaf 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,10 +1,21 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { PageHomeComponent } from './page-home/page-home.component'; +import { PageLoginComponent } from './page-login/page-login.component'; +import { PageSignupComponent } from './page-signup/page-signup.component'; +import { PageUploadComponent } from './page-upload/page-upload.component'; +import { PageUserComponent } from './page-user/page-user.component'; -const routes: Routes = []; +const routes: Routes = [ + { path: '', component: PageHomeComponent }, + { path: 'login', component: PageLoginComponent }, + { path: 'signup', component: PageSignupComponent }, + { path: 'upload', component: PageUploadComponent }, + { path: 'user/:name', component: PageUserComponent }, +]; @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index e69de29..fa9c656 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -0,0 +1,17 @@ + +
+ +
\ No newline at end of file diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index e69de29..9a33dd8 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -0,0 +1,41 @@ +:host { + display: grid; + grid-template-rows: max-content auto; + flex-direction: column; + width: 100vw; + height: 100vh; +} +.navbar-container { + z-index: 100; + background-color: #fff; + box-shadow: #666 0px -10px 10px 10px; + + .navbar { + width: 100%; + // width: 1000px; + justify-content: space-around; + display: flex; + + box-sizing: border-box; + + div { + a { + user-select: none; + font-size: 1.2rem; + color: black; + text-decoration: none; + padding: .75rem 1.5rem; + display: inline-block; + } + + } + } +} +.content { + width: 100%; + height: 100%; + box-sizing: border-box; + position: relative; + overflow: hidden; + overflow-y: auto; +} diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 0801cb5..57fe591 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { UsersService } from './services/users.service'; @Component({ selector: 'app-root', @@ -6,5 +7,5 @@ import { Component } from '@angular/core'; styleUrls: ['./app.component.scss'] }) export class AppComponent { - title = 'frontend'; + constructor(public users: UsersService) {} } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index e8a75a7..6b3f32e 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -3,14 +3,28 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import { PageHomeComponent } from './page-home/page-home.component'; +import { HttpClientModule } from '@angular/common/http'; +import { PageLoginComponent } from './page-login/page-login.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { PageSignupComponent } from './page-signup/page-signup.component'; +import { PageUploadComponent } from './page-upload/page-upload.component'; +import { PageUserComponent } from './page-user/page-user.component'; @NgModule({ declarations: [ - AppComponent + AppComponent, + PageHomeComponent, + PageLoginComponent, + PageSignupComponent, + PageUploadComponent, + PageUserComponent ], imports: [ BrowserModule, - AppRoutingModule + AppRoutingModule, + ReactiveFormsModule, + HttpClientModule, ], providers: [], bootstrap: [AppComponent] diff --git a/frontend/src/app/page-home/page-home.component.html b/frontend/src/app/page-home/page-home.component.html new file mode 100644 index 0000000..bfed480 --- /dev/null +++ b/frontend/src/app/page-home/page-home.component.html @@ -0,0 +1,10 @@ + + + +
+
+ +

{{img.name}}

+
By {{img.author}}
+
+
diff --git a/frontend/src/app/page-home/page-home.component.scss b/frontend/src/app/page-home/page-home.component.scss new file mode 100644 index 0000000..21cbc71 --- /dev/null +++ b/frontend/src/app/page-home/page-home.component.scss @@ -0,0 +1,75 @@ +.title { + display: flex; + flex-direction: column; + gap: 1rem; + h1 { + text-align: center; + margin: 0; + } + h2 { + text-align: center; + font-weight: normal; + margin: 0; + } +} +.images { + margin-top: 5rem; + width: 85rem; + max-width: 100%; + gap: 5rem; + align-items: stretch; + box-sizing: border-box; + padding: 0 1rem; + text-align: center; + .image { + user-select: none; + + img { + pointer-events: none; + height: 15rem; + display: block; + } + + width: min-content; + border-radius: 1rem; + box-shadow: .25rem .25rem 1rem -.5rem #000; + padding: 1rem; + box-sizing: border-box; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 1rem; + margin: 1rem; + + p, h3, h5 { + padding: 0; + margin: 0; + } + h3, h5 { + text-align: center; + } + p { + text-align: justify; + } + } +} +@media screen and (width < 50rem) { + .images { + margin-top: 5rem; + display: flex; + flex-direction: column; + gap: 5rem; + align-items: stretch; + .image { + width: 100%; + } + } +} + +:host { + margin-top: 5rem; + display: flex; + flex-direction: column; + gap: 2rem; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/app/page-home/page-home.component.ts b/frontend/src/app/page-home/page-home.component.ts new file mode 100644 index 0000000..7fb35cc --- /dev/null +++ b/frontend/src/app/page-home/page-home.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { UsersService } from '../services/users.service'; +import { Image, ImagesService } from '../services/images.service'; +import { environment } from 'src/environments/environment'; + +@Component({ + selector: 'app-page-home', + templateUrl: './page-home.component.html', + styleUrls: ['./page-home.component.scss'] +}) +export class PageHomeComponent { + public images: Image[] = []; + public environment = environment; + + public constructor( + images: ImagesService, + public users: UsersService, + ) { + images.feed().then(v => this.images = v); + } +} diff --git a/frontend/src/app/page-login/page-login.component.html b/frontend/src/app/page-login/page-login.component.html new file mode 100644 index 0000000..a2b3611 --- /dev/null +++ b/frontend/src/app/page-login/page-login.component.html @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file diff --git a/frontend/src/app/page-login/page-login.component.scss b/frontend/src/app/page-login/page-login.component.scss new file mode 100644 index 0000000..9fa1d1c --- /dev/null +++ b/frontend/src/app/page-login/page-login.component.scss @@ -0,0 +1,22 @@ +.content { + display: flex; + flex-direction: column; + gap: 1rem; + box-sizing: border-box; + max-width: 100%; + width: 20rem; + + input { + box-sizing: border-box; + width: 100%; + } + button { + margin: auto + } +} +:host { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} \ No newline at end of file diff --git a/frontend/src/app/page-login/page-login.component.ts b/frontend/src/app/page-login/page-login.component.ts new file mode 100644 index 0000000..f5cd9f2 --- /dev/null +++ b/frontend/src/app/page-login/page-login.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { UsersService } from '../services/users.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-page-login', + templateUrl: './page-login.component.html', + styleUrls: ['./page-login.component.scss'] +}) +export class PageLoginComponent { + public form = new FormGroup({ + username: new FormControl(''), + password: new FormControl(''), + }); + + public async login() { + await this.users.login(this.form.value.username ?? '', this.form.value.password ?? ''); + this.router.navigateByUrl('/'); + } + + public constructor( + private users: UsersService, + private router: Router + ) { } +} diff --git a/frontend/src/app/page-signup/page-signup.component.html b/frontend/src/app/page-signup/page-signup.component.html new file mode 100644 index 0000000..c2ec44c --- /dev/null +++ b/frontend/src/app/page-signup/page-signup.component.html @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/frontend/src/app/page-signup/page-signup.component.scss b/frontend/src/app/page-signup/page-signup.component.scss new file mode 100644 index 0000000..9fa1d1c --- /dev/null +++ b/frontend/src/app/page-signup/page-signup.component.scss @@ -0,0 +1,22 @@ +.content { + display: flex; + flex-direction: column; + gap: 1rem; + box-sizing: border-box; + max-width: 100%; + width: 20rem; + + input { + box-sizing: border-box; + width: 100%; + } + button { + margin: auto + } +} +:host { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} \ No newline at end of file diff --git a/frontend/src/app/page-signup/page-signup.component.ts b/frontend/src/app/page-signup/page-signup.component.ts new file mode 100644 index 0000000..a76719a --- /dev/null +++ b/frontend/src/app/page-signup/page-signup.component.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { UsersService } from '../services/users.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-page-signup', + templateUrl: './page-signup.component.html', + styleUrls: ['./page-signup.component.scss'] +}) +export class PageSignupComponent { + public form = new FormGroup({ + username: new FormControl(''), + password: new FormControl(''), + }); + + public async signup() { + await this.users.signup( + this.form.value.username ?? '', + this.form.value.password ?? '' + ); + this.router.navigateByUrl('/'); + } + + public constructor( + private users: UsersService, + private router: Router + ) { } +} diff --git a/frontend/src/app/page-upload/page-upload.component.html b/frontend/src/app/page-upload/page-upload.component.html new file mode 100644 index 0000000..16dd962 --- /dev/null +++ b/frontend/src/app/page-upload/page-upload.component.html @@ -0,0 +1,10 @@ +
+ + + + +
diff --git a/frontend/src/app/page-upload/page-upload.component.scss b/frontend/src/app/page-upload/page-upload.component.scss new file mode 100644 index 0000000..0199185 --- /dev/null +++ b/frontend/src/app/page-upload/page-upload.component.scss @@ -0,0 +1,28 @@ +.content { + display: flex; + flex-direction: column; + gap: 1rem; + box-sizing: border-box; + max-width: 100%; + width: 30rem; + + input { + box-sizing: border-box; + width: 100%; + } + textarea { + resize: none; + height: 10rem; + box-sizing: border-box; + width: 100%; + } + button { + margin: auto + } +} +:host { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} \ No newline at end of file diff --git a/frontend/src/app/page-upload/page-upload.component.ts b/frontend/src/app/page-upload/page-upload.component.ts new file mode 100644 index 0000000..88a21a1 --- /dev/null +++ b/frontend/src/app/page-upload/page-upload.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { ImagesService, Visibility } from '../services/images.service'; + +@Component({ + selector: 'app-page-upload', + templateUrl: './page-upload.component.html', + styleUrls: ['./page-upload.component.scss'] +}) +export class PageUploadComponent { + public form = new FormGroup({ + name: new FormControl(null, [ Validators.required ]), + file: new FormControl(null, [ Validators.required ]), + visibility: new FormControl(Visibility.Unlisted, [ Validators.required ]), + }); + + public setFile($event: any) { + this.form.patchValue({ file: $event.target.files[0] }); + } + + public async submit() { + await this.images.upload({ + name: this.form.value.name ?? '' + }, this.form.value.file!); + this.router.navigateByUrl('/'); + } + + public constructor( + private images: ImagesService, + private router: Router + ) {} +} diff --git a/frontend/src/app/page-user/page-user.component.html b/frontend/src/app/page-user/page-user.component.html new file mode 100644 index 0000000..241a251 --- /dev/null +++ b/frontend/src/app/page-user/page-user.component.html @@ -0,0 +1 @@ +

page-user works!

diff --git a/frontend/src/app/page-user/page-user.component.scss b/frontend/src/app/page-user/page-user.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/page-user/page-user.component.ts b/frontend/src/app/page-user/page-user.component.ts new file mode 100644 index 0000000..5582ee3 --- /dev/null +++ b/frontend/src/app/page-user/page-user.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { User, UsersService } from '../services/users.service'; + +@Component({ + selector: 'app-page-user', + templateUrl: './page-user.component.html', + styleUrls: ['./page-user.component.scss'] +}) +export class PageUserComponent { + public user: User = { images: [], username: 'loading...' }; + + public constructor( + private route: ActivatedRoute, + private users: UsersService + ) { + route.paramMap.subscribe(async v => { + this.user = await users.get(v.get('name')!); + }); + } +} diff --git a/frontend/src/app/services/images.service.ts b/frontend/src/app/services/images.service.ts new file mode 100644 index 0000000..2b3b86d --- /dev/null +++ b/frontend/src/app/services/images.service.ts @@ -0,0 +1,56 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { firstValueFrom } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { UsersService } from './users.service'; + +export enum Visibility { + Public, + Unlisted, + Private, +} +export interface Image { + id: string; + author: string; + name: string; + visibility: Visibility; + created: number; + file: string; +} +export interface CreateImageBody { + name?: string; + visibility?: Visibility; +} + +@Injectable({ + providedIn: 'root' +}) +export class ImagesService { + private readonly url = environment.apiURL + '/images'; + + public async get(id: string) { + return await firstValueFrom(this.http.get(`${this.url}`, { + ...this.users.httpOptions, + params: { id } + })); + } + public async feed(n?: number, i?: number) { + return await firstValueFrom(this.http.get(`${this.url}/feed`, { + ...this.users.httpOptions, + // params: { n, i } + })); + } + + public async upload(proto: CreateImageBody, file: File) { + const data = new FormData(); + data.append('file', file, file.name); + data.append('body', JSON.stringify(proto)); + return await firstValueFrom(this.http.post(`${this.url}/upload`, data, this.users.httpOptions({}))); + } + public async change(id: string, proto: CreateImageBody) { + return await firstValueFrom(this.http.post(`${this.url}/change?id=${id}`, proto, this.users.httpOptions({}))); + } + + public constructor(private http: HttpClient, private users: UsersService) { + } +} diff --git a/frontend/src/app/services/users.service.ts b/frontend/src/app/services/users.service.ts index 7e1aa7c..a410a87 100644 --- a/frontend/src/app/services/users.service.ts +++ b/frontend/src/app/services/users.service.ts @@ -1,24 +1,11 @@ -import { Injectable, WritableSignal, signal } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { Injectable, signal } from '@angular/core'; +import { HttpClient, HttpHeaders } 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, -} +import { firstValueFrom } from 'rxjs'; export interface User { username: string; - role: Role; - projects: string[]; -} -export interface ThisUser extends User { - chats: string[]; - email: string; + images: string[]; } @Injectable({ @@ -26,7 +13,7 @@ export interface ThisUser extends User { }) export class UsersService { private readonly url = environment.apiURL + '/users'; - public $user = signal(undefined); + public $user = signal(undefined); public get token() { return localStorage.getItem('token') ?? undefined; @@ -36,43 +23,46 @@ export class UsersService { else localStorage.setItem('token', token); } + public httpOptions(other: T): T & { headers?: HttpHeaders } { + if (this.token) { + (other as any).headers ??= {}; + (other as any).headers.authorization = 'Bearer ' + this.token; + } + + return other; + } + public async updateUser() { if (this.token) { - const user = await firstValueFrom(this.http.get(`${this.url}/get?token=${this.token}`)); + const user = await firstValueFrom(this.http.get(`${this.url}/self`, { + headers: { authorization: 'Bearer ' + this.token } + })); this.$user.set(user); } else this.$user.set(undefined); } + public async get(username: string) { + const user = await firstValueFrom(this.http.get(`${this.url}?username=${username}`, this.httpOptions({}))); + return user; + } 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; + const token = await firstValueFrom(this.http.post(`${this.url}/login`, { username, password })); this.token = token; await this.updateUser(); } - public async signup(username: string, password: string, email: string) { + public async signup(username: string, password: string) { await this.logoff(); - await firstValueFrom(this.http.post(this.url + `/signup`, { username, password, email })); + await firstValueFrom(this.http.post(`${this.url}/signup`, { username, password })); 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 index 4f5dfb8..1632d7c 100644 --- a/frontend/src/environments/environment.prod.ts +++ b/frontend/src/environments/environment.prod.ts @@ -1,8 +1,6 @@ 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 index b92ae33..cdc440f 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -1,5 +1,4 @@ export const environment = { production: false, apiURL: 'http://127.0.0.1/api', - wsURL: 'ws://127.0.0.1/api' }; diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 90d4ee0..402f433 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -1 +1,39 @@ -/* You can add global styles to this file, and also import other style files */ +body, html { + margin: 0; + padding: 0; + height: 100vh; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +button { + border: 1px solid #777; + background-color: #fff; + padding: .5rem 1rem; + border-radius: 100rem; + transition: 100ms; +} +input, textarea { + font-family: inherit; + border: 1px solid #777; + background-color: #fff; + padding: .5rem 1rem; + border-radius: .5rem; + transition: 100ms; + outline: none; +} + +button, .hoverable, .selectable { + transition: 250ms background-color; + cursor: pointer; +} + +button, .hoverable, .selectable { + &:hover { + background-color: #f0f0f0; + } +} +button:enabled, .selectable { + &:active { + background-color: #aaa; + } +} \ No newline at end of file