diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index b009cd4..fe855b6 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -16,7 +16,7 @@
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index 0148fc8..a32cbff 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -4,10 +4,17 @@ import { PostComponent } from './routes/post/post.component';
import { AdminComponent } from './routes/admin/admin.component';
import { LoggedInGuard } from './shared/guards/logged-in.guard';
import { PostEditorComponent } from './components/post-editor/post-editor.component';
+import { CreatePostComponent } from './routes/post/create-post/create-post.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
- { path: 'post', children: [{ path: ':id', component: PostComponent }] },
+ {
+ path: 'post',
+ children: [
+ { path: 'new', component: CreatePostComponent },
+ { path: ':id', component: PostComponent },
+ ],
+ },
{ path: 'admin', component: AdminComponent, canActivate: [LoggedInGuard] },
{ path: 'tst', component: PostEditorComponent },
];
diff --git a/frontend/src/app/components/post-editor/post-editor.component.css b/frontend/src/app/components/post-editor/post-editor.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/src/app/components/post-editor/post-editor.component.html b/frontend/src/app/components/post-editor/post-editor.component.html
index a465289..3fd9509 100644
--- a/frontend/src/app/components/post-editor/post-editor.component.html
+++ b/frontend/src/app/components/post-editor/post-editor.component.html
@@ -3,7 +3,8 @@
@@ -12,15 +13,17 @@
TL;DR; {{ data().tldr }}
diff --git a/frontend/src/app/components/post-editor/post-editor.component.ts b/frontend/src/app/components/post-editor/post-editor.component.ts
index 967618a..4a4b1e0 100644
--- a/frontend/src/app/components/post-editor/post-editor.component.ts
+++ b/frontend/src/app/components/post-editor/post-editor.component.ts
@@ -1,4 +1,11 @@
-import { Component, effect, signal } from '@angular/core';
+import {
+ Component,
+ effect,
+ EventEmitter,
+ Input,
+ Output,
+ signal,
+} from '@angular/core';
import { MarkdownComponent } from '../markdown/markdown.component';
import { FormsModule } from '@angular/forms';
import { Post } from '../../shared/interfaces/post';
@@ -8,19 +15,25 @@ import { Post } from '../../shared/interfaces/post';
imports: [MarkdownComponent, FormsModule],
standalone: true,
templateUrl: './post-editor.component.html',
- styleUrl: './post-editor.component.css',
})
export class PostEditorComponent {
- data = signal({
+ @Input('post') data = signal({
id: 0,
title: '',
tldr: '',
content: '',
});
+ @Output('postChange') dataChange = new EventEmitter();
- constructor() {
- effect(() => {
- console.log(this.data());
- });
+ set title(val: string) {
+ this.data.update((d) => ({ ...d, title: val }));
+ }
+
+ set tldr(val: string) {
+ this.data.update((d) => ({ ...d, tldr: val }));
+ }
+
+ set content(val: string) {
+ this.data.update((d) => ({ ...d, content: val }));
}
}
diff --git a/frontend/src/app/routes/admin/admin.component.html b/frontend/src/app/routes/admin/admin.component.html
index 95c91bd..3a12edd 100644
--- a/frontend/src/app/routes/admin/admin.component.html
+++ b/frontend/src/app/routes/admin/admin.component.html
@@ -1 +1,20 @@
-
+
+
+
+
+
+ {{ post.title }}
+ TL;DR; {{ post.tldr }}
+
+
diff --git a/frontend/src/app/routes/admin/admin.component.ts b/frontend/src/app/routes/admin/admin.component.ts
index 2610029..f326132 100644
--- a/frontend/src/app/routes/admin/admin.component.ts
+++ b/frontend/src/app/routes/admin/admin.component.ts
@@ -1,10 +1,15 @@
-import { Component } from '@angular/core';
+import { Component, inject } from '@angular/core';
import { PostEditorComponent } from '../../components/post-editor/post-editor.component';
+import { NgFor } from '@angular/common';
+import { PostsService } from '../../shared/services/posts.service';
+import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-admin',
- imports: [PostEditorComponent],
+ imports: [NgFor, RouterLink],
standalone: true,
templateUrl: './admin.component.html',
})
-export class AdminComponent {}
+export class AdminComponent {
+ posts = inject(PostsService).getPosts();
+}
diff --git a/frontend/src/app/routes/home/home.component.ts b/frontend/src/app/routes/home/home.component.ts
index 37f2357..01847c1 100644
--- a/frontend/src/app/routes/home/home.component.ts
+++ b/frontend/src/app/routes/home/home.component.ts
@@ -10,9 +10,5 @@ import { RouterLink } from '@angular/router';
templateUrl: './home.component.html',
})
export class HomeComponent {
- private postsService = inject(PostsService);
-
- get posts() {
- return this.postsService.getPosts();
- }
+ posts = inject(PostsService).getPosts();
}
diff --git a/frontend/src/app/routes/post/create-post/create-post.component.html b/frontend/src/app/routes/post/create-post/create-post.component.html
new file mode 100644
index 0000000..f0ed7d7
--- /dev/null
+++ b/frontend/src/app/routes/post/create-post/create-post.component.html
@@ -0,0 +1,10 @@
+
+
Create a new Post
+
+
+
diff --git a/frontend/src/app/routes/post/create-post/create-post.component.spec.ts b/frontend/src/app/routes/post/create-post/create-post.component.spec.ts
new file mode 100644
index 0000000..f6b9a8c
--- /dev/null
+++ b/frontend/src/app/routes/post/create-post/create-post.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CreatePostComponent } from './create-post.component';
+
+describe('CreatePostComponent', () => {
+ let component: CreatePostComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CreatePostComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(CreatePostComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/routes/post/create-post/create-post.component.ts b/frontend/src/app/routes/post/create-post/create-post.component.ts
new file mode 100644
index 0000000..b032e08
--- /dev/null
+++ b/frontend/src/app/routes/post/create-post/create-post.component.ts
@@ -0,0 +1,36 @@
+import { Component, effect, inject, signal } from '@angular/core';
+import { PostEditorComponent } from '../../../components/post-editor/post-editor.component';
+import { Post } from '../../../shared/interfaces/post';
+import { PostsService } from '../../../shared/services/posts.service';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-create-post',
+ imports: [PostEditorComponent],
+ templateUrl: './create-post.component.html',
+})
+export class CreatePostComponent {
+ private postsService = inject(PostsService);
+ private location = inject(Location);
+ post = signal({
+ id: 0,
+ title: '',
+ tldr: '',
+ content: '',
+ });
+
+ constructor() {
+ effect(() => {
+ console.log('create', this.post());
+ });
+
+ setTimeout(() => {
+ this.post.set({ ...this.post(), title: 'adf' });
+ }, 1000);
+ }
+
+ publish() {
+ this.postsService.createPost(this.post());
+ this.location.back();
+ }
+}
diff --git a/frontend/src/app/routes/post/post.component.ts b/frontend/src/app/routes/post/post.component.ts
index d26446f..7a54e8c 100644
--- a/frontend/src/app/routes/post/post.component.ts
+++ b/frontend/src/app/routes/post/post.component.ts
@@ -10,10 +10,10 @@ import { MarkdownComponent } from '../../components/markdown/markdown.component'
templateUrl: './post.component.html',
})
export class PostComponent {
- private posts = inject(PostsService);
@Input() id!: string;
+ private postsService = inject(PostsService);
get post() {
- return this.posts.getPost(parseInt(this.id));
+ return this.postsService.getPost(parseInt(this.id));
}
}
diff --git a/frontend/src/app/shared/interfaces/auth.ts b/frontend/src/app/shared/interfaces/auth.ts
index 3676e97..58d2c79 100644
--- a/frontend/src/app/shared/interfaces/auth.ts
+++ b/frontend/src/app/shared/interfaces/auth.ts
@@ -5,3 +5,6 @@ export interface User {
export interface LoginResponse {
token: string;
}
+export interface Token {
+ exp: number;
+}
diff --git a/frontend/src/app/shared/services/auth.service.ts b/frontend/src/app/shared/services/auth.service.ts
index ec30409..a0daa81 100644
--- a/frontend/src/app/shared/services/auth.service.ts
+++ b/frontend/src/app/shared/services/auth.service.ts
@@ -1,14 +1,64 @@
import { HttpClient } from '@angular/common/http';
-import { inject, Injectable, signal, WritableSignal } from '@angular/core';
-import { LoginResponse, User } from '../interfaces/auth';
+import {
+ effect,
+ inject,
+ Injectable,
+ signal,
+ WritableSignal,
+} from '@angular/core';
+import { LoginResponse, User, Token } from '../interfaces/auth';
import { environment } from '../../../environments/environment';
+const JWT_KEY = 'token';
+
@Injectable({
providedIn: 'root',
})
export class AuthService {
private http = inject(HttpClient);
jwt: WritableSignal = signal(null);
+ timeout: any | null = null;
+
+ constructor() {
+ // read from localStorage
+ const token = window.localStorage.getItem(JWT_KEY);
+ if (token) {
+ this.jwt.set(token);
+ }
+
+ // set localStorage when this.jwt changes
+ effect(() => {
+ const token = this.jwt();
+ if (token) {
+ window.localStorage.setItem(JWT_KEY, token);
+ return;
+ }
+ window.localStorage.removeItem(JWT_KEY);
+ });
+
+ // if token expired -> set this.jwt to null
+ effect(() => {
+ const token = this.jwt();
+ if (!token) {
+ return;
+ }
+
+ const remainingMilli =
+ extractExpiration(token).valueOf() - new Date().valueOf();
+
+ // if expired set to null
+ if (remainingMilli < 0) {
+ this.clearTimeout();
+ this.jwt.set(null);
+ return;
+ }
+
+ this.clearTimeout();
+ this.timeout = setTimeout(() => {
+ this.jwt.set(null);
+ }, remainingMilli);
+ });
+ }
login(user: User) {
console.log(user);
@@ -17,4 +67,33 @@ export class AuthService {
.post(`${environment.apiRoot}/login`, user)
.subscribe((res) => this.jwt.set(res.token));
}
+
+ private clearTimeout() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ }
+ }
+}
+
+// https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
+function parseJwt(token: string): Token {
+ var base64Url = token.split('.')[1];
+ var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+ var jsonPayload = decodeURIComponent(
+ window
+ .atob(base64)
+ .split('')
+ .map(function (c) {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ })
+ .join(''),
+ );
+
+ return JSON.parse(jsonPayload);
+}
+
+function extractExpiration(token: string): Date {
+ const jwt = parseJwt(token);
+ return new Date(jwt.exp * 1000);
}
diff --git a/frontend/src/app/shared/services/posts.service.ts b/frontend/src/app/shared/services/posts.service.ts
index 4ef2e6e..79e96c4 100644
--- a/frontend/src/app/shared/services/posts.service.ts
+++ b/frontend/src/app/shared/services/posts.service.ts
@@ -31,10 +31,14 @@ export class PostsService {
}
getPost(id: number): Signal {
- console.log(typeof id);
- console.log(this.posts().has(id));
- console.log(this.posts().has(2));
-
return computed(() => this.posts().get(id));
}
+
+ createPost(post: Post) {
+ this.http
+ .post(`${environment.apiRoot}/posts`, post)
+ .subscribe((res) => {
+ this.posts.set(this.posts().set(res.id, res));
+ });
+ }
}
diff --git a/frontend/¨:w b/frontend/¨:w
new file mode 100644
index 0000000..01847c1
--- /dev/null
+++ b/frontend/¨:w
@@ -0,0 +1,14 @@
+import { Component, inject, OnInit, Signal } from '@angular/core';
+import { PostsService } from '../../shared/services/posts.service';
+import { NgForOf } from '@angular/common';
+import { RouterLink } from '@angular/router';
+
+@Component({
+ selector: 'app-home',
+ imports: [NgForOf, RouterLink],
+ standalone: true,
+ templateUrl: './home.component.html',
+})
+export class HomeComponent {
+ posts = inject(PostsService).getPosts();
+}