diff --git a/backend/models/Image.ts b/backend/models/Image.ts
index c4345e5..ecda38a 100644
--- a/backend/models/Image.ts
+++ b/backend/models/Image.ts
@@ -13,4 +13,5 @@ export default interface Image {
visibility: Visibility;
created: number;
file: string;
+ likes: string[];
}
\ No newline at end of file
diff --git a/backend/models/User.ts b/backend/models/User.ts
index c6f3049..50cf48f 100644
--- a/backend/models/User.ts
+++ b/backend/models/User.ts
@@ -5,4 +5,5 @@ export default interface User {
username: string;
password: string;
images: UUID[];
+ likes: UUID[];
}
diff --git a/backend/routers/ImageRouter.ts b/backend/routers/ImageRouter.ts
index 990ab03..3434062 100644
--- a/backend/routers/ImageRouter.ts
+++ b/backend/routers/ImageRouter.ts
@@ -13,7 +13,7 @@ import AppDatabase from "../AppDatabase.ts";
import RestResponse from "../server/RestResponse.ts";
export default class ImageRouter extends AppRouter {
- public static deserialize(image: Image) {
+ public static deserialize(image: Image, me?: string) {
return {
author: image.author,
created: image.created,
@@ -21,6 +21,8 @@ export default class ImageRouter extends AppRouter {
visibility: image.visibility,
id: image._id,
file: image.file,
+ likes: image.likes?.length ?? 0,
+ liked: image.likes?.includes(me!) ?? false,
};
}
@@ -33,7 +35,7 @@ export default class ImageRouter extends AppRouter {
image.visibility === Visibility.Private && image.author !== jwt?.name
) throw new HttpError("Image doesn't exist.");
- return ImageRouter.deserialize(image);
+ return ImageRouter.deserialize(image, jwt?.name);
}
@rest('GET', '/img/:id')
async file(id: string, @jwt('salt', false) @auth() jwt?: JWTPayload) {
@@ -53,10 +55,10 @@ export default class ImageRouter extends AppRouter {
.contentType(id.split('.')[1]);
}
@rest('GET')
- async feed(@page() page: Page) {
+ async feed(@page() page: Page, @jwt('salt', false) @auth() jwt?: JWTPayload) {
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));
+ return res.map(v => ImageRouter.deserialize(v, jwt?.name));
}
@rest('GET', '/feed/:username')
async userFeed(username: string, @page() page: Page, @jwt('salt', false) @auth() jwt?: JWTPayload) {
@@ -67,7 +69,7 @@ export default class ImageRouter extends AppRouter {
if (user.username === jwt?.name) cursor = this.db.images.find({ _id: { $in: user.images } });
else cursor = this.db.images.find({ _id: { $in: user.images }, visibility: Visibility.Public });
- return (await page.apply(cursor.sort({ created: -1 })).toArray()).map(v => ImageRouter.deserialize(v));
+ return (await page.apply(cursor.sort({ created: -1 })).toArray()).map(v => ImageRouter.deserialize(v, jwt?.name));
}
@rest('POST', '/upload')
@@ -115,7 +117,8 @@ export default class ImageRouter extends AppRouter {
created: now(),
name: req.name!,
visibility: req.visibility,
- file: `${id}.${ext}`
+ file: `${id}.${ext}`,
+ likes: [],
};
await Deno.mkdir('images', { recursive: true });
const out = await Deno.open(`images/${id}.${ext}`, { write: true, create: true });
@@ -148,6 +151,42 @@ export default class ImageRouter extends AppRouter {
return ImageRouter.deserialize(img);
}
+ @rest('POST', '/like')
+ async like(@uuid() id: UUID, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {
+ const user = await this.db.users.findOne({ username: jwt.name });
+ if (!user) throw new HttpError("You don't exist.");
+
+ const res = await this.db.images.updateOne(
+ { _id: id },
+ { $addToSet: { likes: jwt.name } }
+ );
+ if (res.matchedCount === 0) throw new HttpError("Image doesn't exist.");
+ await this.db.users.updateOne(
+ { username: jwt.name },
+ { $addToSet: { likes: id } }
+ );
+
+
+ return ImageRouter.deserialize((await this.db.images.findOne({ _id: id }))!);
+ }
+ @rest('POST', '/dislike')
+ async dislike(@uuid() id: UUID, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {
+ const user = await this.db.users.findOne({ username: jwt.name });
+ if (!user) throw new HttpError("You don't exist.");
+
+ const res = await this.db.images.updateOne(
+ { _id: id },
+ { $pull: { likes: jwt.name } }
+ );
+ if (res.matchedCount === 0) throw new HttpError("Image doesn't exist.");
+ await this.db.users.updateOne(
+ { username: jwt.name },
+ { $pull: { likes: id } }
+ );
+
+ return ImageRouter.deserialize((await this.db.images.findOne({ _id: id }))!);
+ }
+
public constructor(private db: AppDatabase) {
super();
db.images.createIndexes({ indexes: [ { key: { created: -1 }, name: 'Image Order' } ] });
diff --git a/backend/routers/UserRouter.ts b/backend/routers/UserRouter.ts
index 52e4e6e..d846869 100644
--- a/backend/routers/UserRouter.ts
+++ b/backend/routers/UserRouter.ts
@@ -64,6 +64,7 @@ export default class UserRouter extends AppRouter {
username: body.username,
password: password,
images: [],
+ likes: [],
});
return {};
diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index b2012da..f10f7f5 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -12,6 +12,6 @@
-
+
\ No newline at end of file
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts
index 38bdca6..9af3a9c 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import { UsersService } from './services/users.service';
import { ScrollService } from './services/scroll.service';
@@ -7,9 +7,11 @@ import { ScrollService } from './services/scroll.service';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
-export class AppComponent {
- public onScroll(el: HTMLDivElement) {
- if (el.scrollTop + el.clientHeight * 2 >= el.scrollHeight) this.scroll.signal(el.scrollHeight);
+export class AppComponent implements AfterViewInit {
+ @ViewChild('container') container!: ElementRef
;
+
+ public ngAfterViewInit(): void {
+ this.scroll.scrollHost = this.container.nativeElement;
}
constructor(
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index f38a054..6f9d124 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -11,6 +11,7 @@ import { PageSignupComponent } from './page-signup/page-signup.component';
import { PageUploadComponent } from './page-upload/page-upload.component';
import { PageUserComponent } from './page-user/page-user.component';
import { ImagesComponent } from './images/images.component';
+import { ImageComponent } from './image/image.component';
@NgModule({
declarations: [
@@ -20,7 +21,8 @@ import { ImagesComponent } from './images/images.component';
PageSignupComponent,
PageUploadComponent,
PageUserComponent,
- ImagesComponent
+ ImagesComponent,
+ ImageComponent
],
imports: [
BrowserModule,
diff --git a/frontend/src/app/image/image.component.html b/frontend/src/app/image/image.component.html
new file mode 100644
index 0000000..f0f6943
--- /dev/null
+++ b/frontend/src/app/image/image.component.html
@@ -0,0 +1 @@
+image works!
diff --git a/frontend/src/app/image/image.component.scss b/frontend/src/app/image/image.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/image/image.component.ts b/frontend/src/app/image/image.component.ts
new file mode 100644
index 0000000..65a6812
--- /dev/null
+++ b/frontend/src/app/image/image.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-image',
+ templateUrl: './image.component.html',
+ styleUrls: ['./image.component.scss']
+})
+export class ImageComponent {
+
+}
diff --git a/frontend/src/app/images/images.component.html b/frontend/src/app/images/images.component.html
index d11f564..01f9fff 100644
--- a/frontend/src/app/images/images.component.html
+++ b/frontend/src/app/images/images.component.html
@@ -2,7 +2,7 @@
{{img.name}}
-
By {{img.author}}
+
diff --git a/frontend/src/app/images/images.component.ts b/frontend/src/app/images/images.component.ts
index 0346abc..8c44906 100644
--- a/frontend/src/app/images/images.component.ts
+++ b/frontend/src/app/images/images.component.ts
@@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core
import { environment } from 'src/environments/environment';
import { Image, ImagesService } from '../services/images.service';
import { ScrollService } from '../services/scroll.service';
+import { Observable } from 'rxjs';
@Component({
selector: 'app-images',
@@ -9,34 +10,40 @@ import { ScrollService } from '../services/scroll.service';
styleUrls: ['./images.component.scss']
})
export class ImagesComponent {
- @Input() public n = 10;
- @Input() public feedFunc: (n: number, i: number) => Promise = async () => [];
+ private static nextFrame() {
+ return new Promise(requestAnimationFrame);
+ }
+
+ public static fromFeed(element: HTMLDivElement, n: number, feed: (n: number, i: number) => Promise) {
+ return new Observable(sub => {
+ let done = false;
+ let images: Image[] = [];
+ let i = 0;
+
+ (async () => {
+ while (!done) {
+ await this.nextFrame();
+ if (element.scrollTop + element.clientHeight * 1.5 < element.scrollHeight) continue;
+
+ const els = await feed(n, i++);
+ images.push(...els);
+
+ if (els.length === 0) {
+ sub.complete();
+ return;
+ }
+ else sub.next(images);
+ }
+ })();
+ });
+ }
+
+ @Input() public images: Image[] | null = null;
public ended = false;
- public images: Image[] = [];
public environment = environment;
public i = 0;
- private sub: () => void;
- public async next() {
- const res = await this.feedFunc(this.n, this.i);
- if (res.length === 0) {
- this.ended = true;
- this.sub();
- return false;
- }
- else {
- this.images.push(...res);
- this.i++;
- }
- return true;
- }
-
- public constructor(
- public scroll: ScrollService
- ) {
- this.sub = scroll.endReached.subscribe(() => {
- this.next();
- }).unsubscribe;
+ public constructor() {
}
}
diff --git a/frontend/src/app/page-home/page-home.component.html b/frontend/src/app/page-home/page-home.component.html
index e4083aa..f343456 100644
--- a/frontend/src/app/page-home/page-home.component.html
+++ b/frontend/src/app/page-home/page-home.component.html
@@ -1,6 +1,2 @@
-
-
-
-
-
\ No newline at end of file
+
\ 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
index f5bb2ee..f1b965c 100644
--- a/frontend/src/app/page-home/page-home.component.ts
+++ b/frontend/src/app/page-home/page-home.component.ts
@@ -19,13 +19,13 @@ export class PageHomeComponent implements AfterViewInit {
}
public ngAfterViewInit(): void {
- this.imagesEl.next();
+ ImagesComponent.fromFeed(this.scroll.scrollHost!, 10, this.feed.bind(this)).subscribe(v => this.imagesEl.images = v);
}
-
public constructor(
public users: UsersService,
public images: ImagesService,
+ public scroll: ScrollService,
) {
}
}
diff --git a/frontend/src/app/page-upload/page-upload.component.html b/frontend/src/app/page-upload/page-upload.component.html
index 16dd962..45ad6cb 100644
--- a/frontend/src/app/page-upload/page-upload.component.html
+++ b/frontend/src/app/page-upload/page-upload.component.html
@@ -1,10 +1,10 @@
diff --git a/frontend/src/app/page-user/page-user.component.html b/frontend/src/app/page-user/page-user.component.html
index 07c321d..ce66b99 100644
--- a/frontend/src/app/page-user/page-user.component.html
+++ b/frontend/src/app/page-user/page-user.component.html
@@ -1,5 +1,3 @@
-{{user.username}}
+User {{user.username}}
-
-
-
+
diff --git a/frontend/src/app/page-user/page-user.component.ts b/frontend/src/app/page-user/page-user.component.ts
index a9e91c8..feb4156 100644
--- a/frontend/src/app/page-user/page-user.component.ts
+++ b/frontend/src/app/page-user/page-user.component.ts
@@ -1,32 +1,36 @@
-import { Component, ViewChild } from '@angular/core';
+import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { User, UsersService } from '../services/users.service';
import { Image, ImagesService } from '../services/images.service';
-import { environment } from 'src/environments/environment';
import { ImagesComponent } from '../images/images.component';
+import { ScrollService } from '../services/scroll.service';
+import { Observable } from 'rxjs';
@Component({
- selector: 'app-page-user',
- templateUrl: './page-user.component.html',
- styleUrls: ['./page-user.component.scss']
+ selector: 'app-page-user',
+ templateUrl: './page-user.component.html',
+ styleUrls: ['./page-user.component.scss']
})
export class PageUserComponent {
@ViewChild("imgs")
public imagesEl!: ImagesComponent;
- public user: User = { username: 'loading...' };
+ public images!: Observable;
+ public user: User = { username: 'Loading...' };
public feed(n: number, i: number) {
- return this.images.userFeed(this.user.username, { n, i });
+ return this._images.userFeed(this.user.username, { n, i });
}
public constructor(
route: ActivatedRoute,
users: UsersService,
- private images: ImagesService,
+ private _images: ImagesService,
+ private _scroll: ScrollService,
) {
route.paramMap.subscribe(async v => {
this.user = await users.get(v.get('name')!);
- this.imagesEl.next();
+ this.images = ImagesComponent.fromFeed(this._scroll.scrollHost!, 10, this.feed.bind(this));
+ // this.imagesEl.next();
});
}
}
diff --git a/frontend/src/app/services/scroll.service.ts b/frontend/src/app/services/scroll.service.ts
index 5a410e3..15c74ad 100644
--- a/frontend/src/app/services/scroll.service.ts
+++ b/frontend/src/app/services/scroll.service.ts
@@ -4,13 +4,7 @@ import { EventEmitter, Injectable } from '@angular/core';
providedIn: 'root'
})
export class ScrollService {
- public endReached = new EventEmitter();
- private prevHeight = -1;
-
- public signal(height: number) {
- if (this.prevHeight !== height) this.endReached.emit();
- this.prevHeight = height;
- }
+ public scrollHost?: HTMLDivElement;
constructor() { }
}