diff --git a/backend/routers/ImageRouter.ts b/backend/routers/ImageRouter.ts
index 08eecd7..990ab03 100644
--- a/backend/routers/ImageRouter.ts
+++ b/backend/routers/ImageRouter.ts
@@ -25,7 +25,7 @@ export default class ImageRouter extends AppRouter {
}
@rest('GET', '/')
- async get(@schema('uuid') id: UUID, @jwt(v => v.salt, false) @auth() jwt?: JWTPayload) {
+ async get(@schema('uuid') id: UUID, @jwt('salt', false) @auth() jwt?: JWTPayload) {
const image = await this.db.images.findOne({ _id: new UUID(id) });
if (
@@ -36,7 +36,7 @@ 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) {
+ async file(id: string, @jwt('salt', false) @auth() jwt?: JWTPayload) {
try {
const start = await Deno.realPath("images");
const file = await Deno.realPath(`images/${id}`);
@@ -52,12 +52,23 @@ export default class ImageRouter extends AppRouter {
.body((await Deno.open(`images/${id}`)).readable)
.contentType(id.split('.')[1]);
}
- @rest('GET', '/feed')
- async self(@page() page: Page) {
+ @rest('GET')
+ async feed(@page() page: Page) {
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));
}
+ @rest('GET', '/feed/:username')
+ async userFeed(username: string, @page() page: Page, @jwt('salt', false) @auth() jwt?: JWTPayload) {
+ const user = await this.db.users.findOne({ username });
+ if (user === undefined) throw new HttpError("User not found.");
+
+ let cursor;
+ 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));
+ }
@rest('POST', '/upload')
async upload(@body() body: Blob, @headers() headers: Headers, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {
@@ -94,7 +105,7 @@ export default class ImageRouter extends AppRouter {
// Clean up request
const req = await convert(rawReq, { name: 'string?', visibility: 'number?' });
req.name ??= new UUID().toString();
- req.visibility = 0;
+ 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
diff --git a/backend/routers/UserRouter.ts b/backend/routers/UserRouter.ts
index 6102229..52e4e6e 100644
--- a/backend/routers/UserRouter.ts
+++ b/backend/routers/UserRouter.ts
@@ -31,11 +31,11 @@ export default class UserRouter extends AppRouter {
}
@rest('GET', '/')
- async get(@schema('string') username: string, @jwt('salt', true) @auth() jwt: JWTPayload) {
+ async get(@schema('string') username: string, @jwt('salt', false) @auth() jwt?: JWTPayload) {
const res = await this.db.users.findOne({ username });
if (res === undefined) throw new HttpError('User not found.');
- return this.deserialize(res, jwt.name === username);
+ return this.deserialize(res, jwt?.name === username);
}
@rest('GET', '/self')
async self(@jwt('salt', true) @auth() auth: JWTPayload) {
diff --git a/backend/server/Router.ts b/backend/server/Router.ts
index cd8cfb0..4ae3eab 100644
--- a/backend/server/Router.ts
+++ b/backend/server/Router.ts
@@ -109,7 +109,6 @@ export default class Router {
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/frontend/.vscode/settings.json b/frontend/.vscode/settings.json
new file mode 100644
index 0000000..1383829
--- /dev/null
+++ b/frontend/.vscode/settings.json
@@ -0,0 +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/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index fa9c656..b2012da 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 57fe591..38bdca6 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { UsersService } from './services/users.service';
+import { ScrollService } from './services/scroll.service';
@Component({
selector: 'app-root',
@@ -7,5 +8,12 @@ import { UsersService } from './services/users.service';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
- constructor(public users: UsersService) {}
+ public onScroll(el: HTMLDivElement) {
+ if (el.scrollTop + el.clientHeight * 2 >= el.scrollHeight) this.scroll.signal(el.scrollHeight);
+ }
+
+ constructor(
+ public users: UsersService,
+ public scroll: ScrollService
+ ) { }
}
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 6b3f32e..f38a054 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -10,6 +10,7 @@ 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';
+import { ImagesComponent } from './images/images.component';
@NgModule({
declarations: [
@@ -18,7 +19,8 @@ import { PageUserComponent } from './page-user/page-user.component';
PageLoginComponent,
PageSignupComponent,
PageUploadComponent,
- PageUserComponent
+ PageUserComponent,
+ ImagesComponent
],
imports: [
BrowserModule,
diff --git a/frontend/src/app/images/images.component.html b/frontend/src/app/images/images.component.html
new file mode 100644
index 0000000..d11f564
--- /dev/null
+++ b/frontend/src/app/images/images.component.html
@@ -0,0 +1,9 @@
+
+
+
![]()
+
{{img.name}}
+
By {{img.author}}
+
+
+
+
No more photos :(
\ No newline at end of file
diff --git a/frontend/src/app/images/images.component.scss b/frontend/src/app/images/images.component.scss
new file mode 100644
index 0000000..ca5bbb3
--- /dev/null
+++ b/frontend/src/app/images/images.component.scss
@@ -0,0 +1,55 @@
+
+.images {
+ display: block;
+ width: min(100%, 85rem);
+ margin: auto;
+ 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 {
+ display: flex;
+ flex-direction: column;
+ gap: 5rem;
+ align-items: stretch;
+
+ .image {
+ width: 100%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/images/images.component.ts b/frontend/src/app/images/images.component.ts
new file mode 100644
index 0000000..0346abc
--- /dev/null
+++ b/frontend/src/app/images/images.component.ts
@@ -0,0 +1,42 @@
+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';
+
+@Component({
+ selector: 'app-images',
+ templateUrl: './images.component.html',
+ styleUrls: ['./images.component.scss']
+})
+export class ImagesComponent {
+ @Input() public n = 10;
+ @Input() public feedFunc: (n: number, i: number) => Promise
= async () => [];
+
+ 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;
+ }
+}
diff --git a/frontend/src/app/page-home/page-home.component.html b/frontend/src/app/page-home/page-home.component.html
index bfed480..e4083aa 100644
--- a/frontend/src/app/page-home/page-home.component.html
+++ b/frontend/src/app/page-home/page-home.component.html
@@ -1,10 +1,6 @@
-
-
-
![]()
-
{{img.name}}
-
By {{img.author}}
-
-
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/page-home/page-home.component.scss b/frontend/src/app/page-home/page-home.component.scss
index 21cbc71..3fa2e16 100644
--- a/frontend/src/app/page-home/page-home.component.scss
+++ b/frontend/src/app/page-home/page-home.component.scss
@@ -12,59 +12,6 @@
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;
diff --git a/frontend/src/app/page-home/page-home.component.ts b/frontend/src/app/page-home/page-home.component.ts
index 7fb35cc..f5bb2ee 100644
--- a/frontend/src/app/page-home/page-home.component.ts
+++ b/frontend/src/app/page-home/page-home.component.ts
@@ -1,21 +1,31 @@
-import { Component } from '@angular/core';
+import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { 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';
@Component({
selector: 'app-page-home',
templateUrl: './page-home.component.html',
styleUrls: ['./page-home.component.scss']
})
-export class PageHomeComponent {
- public images: Image[] = [];
- public environment = environment;
+export class PageHomeComponent implements AfterViewInit {
+ @ViewChild("imgs")
+ public imagesEl!: ImagesComponent;
+
+ public feed(n: number, i: number) {
+ return this.images.feed({ n, i });
+ }
+
+ public ngAfterViewInit(): void {
+ this.imagesEl.next();
+ }
+
public constructor(
- images: ImagesService,
public users: UsersService,
+ public images: ImagesService,
) {
- images.feed().then(v => this.images = v);
}
}
diff --git a/frontend/src/app/page-upload/page-upload.component.ts b/frontend/src/app/page-upload/page-upload.component.ts
index 88a21a1..813ca75 100644
--- a/frontend/src/app/page-upload/page-upload.component.ts
+++ b/frontend/src/app/page-upload/page-upload.component.ts
@@ -21,7 +21,8 @@ export class PageUploadComponent {
public async submit() {
await this.images.upload({
- name: this.form.value.name ?? ''
+ name: this.form.value.name ?? undefined,
+ visibility: this.form.value.visibility ?? undefined,
}, this.form.value.file!);
this.router.navigateByUrl('/');
}
diff --git a/frontend/src/app/page-user/page-user.component.html b/frontend/src/app/page-user/page-user.component.html
index 241a251..07c321d 100644
--- a/frontend/src/app/page-user/page-user.component.html
+++ b/frontend/src/app/page-user/page-user.component.html
@@ -1 +1,5 @@
-page-user works!
+{{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 5582ee3..a9e91c8 100644
--- a/frontend/src/app/page-user/page-user.component.ts
+++ b/frontend/src/app/page-user/page-user.component.ts
@@ -1,6 +1,9 @@
-import { Component } from '@angular/core';
+import { 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';
@Component({
selector: 'app-page-user',
@@ -8,14 +11,22 @@ import { User, UsersService } from '../services/users.service';
styleUrls: ['./page-user.component.scss']
})
export class PageUserComponent {
- public user: User = { images: [], username: 'loading...' };
+ @ViewChild("imgs")
+ public imagesEl!: ImagesComponent;
+ public user: User = { username: 'loading...' };
+
+ public feed(n: number, i: number) {
+ return this.images.userFeed(this.user.username, { n, i });
+ }
public constructor(
- private route: ActivatedRoute,
- private users: UsersService
+ route: ActivatedRoute,
+ users: UsersService,
+ private images: ImagesService,
) {
route.paramMap.subscribe(async v => {
this.user = await users.get(v.get('name')!);
+ this.imagesEl.next();
});
}
}
diff --git a/frontend/src/app/services/images.service.ts b/frontend/src/app/services/images.service.ts
index 2b3b86d..077a9ef 100644
--- a/frontend/src/app/services/images.service.ts
+++ b/frontend/src/app/services/images.service.ts
@@ -21,6 +21,7 @@ export interface CreateImageBody {
name?: string;
visibility?: Visibility;
}
+export type Page = {} | { n: number; } | { n: number; i: number; };
@Injectable({
providedIn: 'root'
@@ -34,11 +35,15 @@ export class ImagesService {
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 feed(page: Page = {}) {
+ return await firstValueFrom(this.http.get(`${this.url}/feed`,
+ this.users.httpOptions({ params: page })
+ ));
+ }
+ public async userFeed(username: string, page: Page = {}) {
+ return await firstValueFrom(this.http.get(`${this.url}/feed/${username}`,
+ this.users.httpOptions({ params: page })
+ ));
}
public async upload(proto: CreateImageBody, file: File) {
diff --git a/frontend/src/app/services/scroll.service.ts b/frontend/src/app/services/scroll.service.ts
new file mode 100644
index 0000000..5a410e3
--- /dev/null
+++ b/frontend/src/app/services/scroll.service.ts
@@ -0,0 +1,16 @@
+import { EventEmitter, Injectable } from '@angular/core';
+
+@Injectable({
+ 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;
+ }
+
+ constructor() { }
+}
diff --git a/frontend/src/app/services/users.service.ts b/frontend/src/app/services/users.service.ts
index a410a87..c1f18fd 100644
--- a/frontend/src/app/services/users.service.ts
+++ b/frontend/src/app/services/users.service.ts
@@ -5,7 +5,6 @@ import { firstValueFrom } from 'rxjs';
export interface User {
username: string;
- images: string[];
}
@Injectable({