diff --git a/backend/auth/login.go b/backend/auth/login.go
index 244ebfc..46f641c 100644
--- a/backend/auth/login.go
+++ b/backend/auth/login.go
@@ -14,8 +14,8 @@ import (
func Login(username, password string, secret []byte) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- var login model.Login
- if err := json.NewDecoder(r.Response.Request.Body).Decode(&login); err != nil {
+ login := model.Login{}
+ if err := json.NewDecoder(r.Body).Decode(&login); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
diff --git a/backend/blog.db b/backend/blog.db
index 0222620..0a59c2a 100644
Binary files a/backend/blog.db and b/backend/blog.db differ
diff --git a/backend/cors/cors.go b/backend/cors/cors.go
index 9a8e027..ab611a8 100644
--- a/backend/cors/cors.go
+++ b/backend/cors/cors.go
@@ -5,7 +5,7 @@ import "net/http"
func HandlerForOrigin(origin string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if origin := r.Header.Get("Origin"); origin != "" {
+ if o := r.Header.Get("Origin"); o != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
@@ -13,6 +13,8 @@ func HandlerForOrigin(origin string) func(http.Handler) http.Handler {
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
if r.Method == "OPTIONS" {
+ w.WriteHeader(http.StatusNoContent)
+
return
}
next.ServeHTTP(w, r)
diff --git a/backend/main.go b/backend/main.go
index 68fa234..44b89b8 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -28,11 +28,13 @@ func main() {
db := model.Init()
blg := blog.New(db)
r := mux.NewRouter()
+ r.Use(cors.HandlerForOrigin("*"))
r.Handle("/login", auth.Login(user, password, []byte(secret))).Methods("POST")
r.Handle("/posts", auth.Authenticated([]byte(secret))(blg.CreatePost)).Methods("POST")
r.Handle("/posts", http.HandlerFunc(blg.GetAllPosts)).Methods("GET")
-
- r.Use(cors.HandlerForOrigin("*"))
-
+ r.Methods("OPTIONS").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // The CORS middleware should set up the headers for you
+ w.WriteHeader(http.StatusNoContent)
+ })
http.ListenAndServe(":8080", r)
}
diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index 360b314..b009cd4 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -4,8 +4,19 @@
My Blog
-
+
-
+
+
+
+
+
+
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts
index 57992ad..b10338b 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -1,11 +1,39 @@
-import { Component } from '@angular/core';
+import {
+ Component,
+ computed,
+ effect,
+ inject,
+ OnChanges,
+ SimpleChanges,
+} from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
+import { ModalComponent } from './components/modal/modal.component';
+import { LoginComponent } from './components/login/login.component';
+import { AuthService } from './shared/services/auth.service';
+import { NgIf } from '@angular/common';
@Component({
selector: 'app-root',
- imports: [RouterOutlet, RouterLink],
+ imports: [RouterOutlet, RouterLink, ModalComponent, LoginComponent, NgIf],
templateUrl: './app.component.html',
})
export class AppComponent {
- title = 'frontend';
+ private auth = inject(AuthService);
+ loginOpen: boolean = false;
+ loggedIn = computed(() => this.auth.jwt() !== null);
+
+ constructor() {
+ effect(() => {
+ if (this.auth.jwt()) {
+ this.loginOpen = false;
+ }
+ });
+ }
+
+ toggleLogin() {
+ this.loginOpen = !this.loginOpen;
+ }
+ logOut() {
+ this.auth.jwt.set(null);
+ }
}
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index 143ce72..0148fc8 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -1,8 +1,13 @@
import { Routes } from '@angular/router';
import { HomeComponent } from './routes/home/home.component';
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';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'post', children: [{ path: ':id', component: PostComponent }] },
+ { path: 'admin', component: AdminComponent, canActivate: [LoggedInGuard] },
+ { path: 'tst', component: PostEditorComponent },
];
diff --git a/frontend/src/app/components/login/login.component.html b/frontend/src/app/components/login/login.component.html
new file mode 100644
index 0000000..9563b2c
--- /dev/null
+++ b/frontend/src/app/components/login/login.component.html
@@ -0,0 +1,22 @@
+
diff --git a/frontend/src/app/components/login/login.component.spec.ts b/frontend/src/app/components/login/login.component.spec.ts
new file mode 100644
index 0000000..18f3685
--- /dev/null
+++ b/frontend/src/app/components/login/login.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+ let component: LoginComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LoginComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(LoginComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/components/login/login.component.ts b/frontend/src/app/components/login/login.component.ts
new file mode 100644
index 0000000..df985c9
--- /dev/null
+++ b/frontend/src/app/components/login/login.component.ts
@@ -0,0 +1,39 @@
+import { Component, inject } from '@angular/core';
+import {
+ FormGroup,
+ Validators,
+ ReactiveFormsModule,
+ FormControl,
+} from '@angular/forms';
+import { AuthService } from '../../shared/services/auth.service';
+import { User } from '../../shared/interfaces/auth';
+
+@Component({
+ selector: 'app-login',
+ imports: [ReactiveFormsModule],
+ templateUrl: './login.component.html',
+})
+export class LoginComponent {
+ private auth = inject(AuthService);
+ form = new FormGroup({
+ name: new FormControl('', [Validators.required]),
+ password: new FormControl('', [Validators.required]),
+ });
+
+ get name() {
+ return this.form.controls.name;
+ }
+ get password() {
+ return this.form.controls.password;
+ }
+ submit() {
+ if (this.form.invalid) {
+ return;
+ }
+ const user: User = {
+ name: this.form.controls.name.value!,
+ password: this.form.controls.password.value!,
+ };
+ this.auth.login(user);
+ }
+}
diff --git a/frontend/src/app/components/modal/modal.component.html b/frontend/src/app/components/modal/modal.component.html
new file mode 100644
index 0000000..8d4756f
--- /dev/null
+++ b/frontend/src/app/components/modal/modal.component.html
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/app/components/modal/modal.component.spec.ts b/frontend/src/app/components/modal/modal.component.spec.ts
new file mode 100644
index 0000000..f9bab0f
--- /dev/null
+++ b/frontend/src/app/components/modal/modal.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ModalComponent } from './modal.component';
+
+describe('ModalComponent', () => {
+ let component: ModalComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ModalComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ModalComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/components/modal/modal.component.ts b/frontend/src/app/components/modal/modal.component.ts
new file mode 100644
index 0000000..b002642
--- /dev/null
+++ b/frontend/src/app/components/modal/modal.component.ts
@@ -0,0 +1,18 @@
+import { NgIf } from '@angular/common';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+
+@Component({
+ selector: 'app-modal',
+ imports: [NgIf],
+ standalone: true,
+ templateUrl: './modal.component.html',
+})
+export class ModalComponent {
+ @Input() open: boolean = false;
+ @Output() openChange = new EventEmitter();
+
+ toggleOpen() {
+ this.open = !this.open;
+ this.openChange.emit(this.open);
+ }
+}
diff --git a/frontend/src/app/components/post-editor/post-editor.component.css b/frontend/src/app/components/post-editor/post-editor.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/components/post-editor/post-editor.component.html b/frontend/src/app/components/post-editor/post-editor.component.html
new file mode 100644
index 0000000..a465289
--- /dev/null
+++ b/frontend/src/app/components/post-editor/post-editor.component.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
{{ data().title }}
+
+
+
+
+
TL;DR; {{ data().tldr }}
+
+
+
+
diff --git a/frontend/src/app/components/post-editor/post-editor.component.spec.ts b/frontend/src/app/components/post-editor/post-editor.component.spec.ts
new file mode 100644
index 0000000..5acaa27
--- /dev/null
+++ b/frontend/src/app/components/post-editor/post-editor.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PostEditorComponent } from './post-editor.component';
+
+describe('PostEditorComponent', () => {
+ let component: PostEditorComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [PostEditorComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(PostEditorComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/components/post-editor/post-editor.component.ts b/frontend/src/app/components/post-editor/post-editor.component.ts
new file mode 100644
index 0000000..967618a
--- /dev/null
+++ b/frontend/src/app/components/post-editor/post-editor.component.ts
@@ -0,0 +1,26 @@
+import { Component, effect, signal } from '@angular/core';
+import { MarkdownComponent } from '../markdown/markdown.component';
+import { FormsModule } from '@angular/forms';
+import { Post } from '../../shared/interfaces/post';
+
+@Component({
+ selector: 'app-post-editor',
+ imports: [MarkdownComponent, FormsModule],
+ standalone: true,
+ templateUrl: './post-editor.component.html',
+ styleUrl: './post-editor.component.css',
+})
+export class PostEditorComponent {
+ data = signal({
+ id: 0,
+ title: '',
+ tldr: '',
+ content: '',
+ });
+
+ constructor() {
+ effect(() => {
+ console.log(this.data());
+ });
+ }
+}
diff --git a/frontend/src/app/routes/admin/admin.component.html b/frontend/src/app/routes/admin/admin.component.html
new file mode 100644
index 0000000..95c91bd
--- /dev/null
+++ b/frontend/src/app/routes/admin/admin.component.html
@@ -0,0 +1 @@
+
diff --git a/frontend/src/app/routes/admin/admin.component.spec.ts b/frontend/src/app/routes/admin/admin.component.spec.ts
new file mode 100644
index 0000000..617b55b
--- /dev/null
+++ b/frontend/src/app/routes/admin/admin.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AdminComponent } from './admin.component';
+
+describe('AdminComponent', () => {
+ let component: AdminComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AdminComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(AdminComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/routes/admin/admin.component.ts b/frontend/src/app/routes/admin/admin.component.ts
new file mode 100644
index 0000000..2610029
--- /dev/null
+++ b/frontend/src/app/routes/admin/admin.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+import { PostEditorComponent } from '../../components/post-editor/post-editor.component';
+
+@Component({
+ selector: 'app-admin',
+ imports: [PostEditorComponent],
+ standalone: true,
+ templateUrl: './admin.component.html',
+})
+export class AdminComponent {}
diff --git a/frontend/src/app/routes/home/home.component.ts b/frontend/src/app/routes/home/home.component.ts
index bb87e20..37f2357 100644
--- a/frontend/src/app/routes/home/home.component.ts
+++ b/frontend/src/app/routes/home/home.component.ts
@@ -2,7 +2,6 @@ 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';
-import { Post } from '../../shared/services/interfaces/post';
@Component({
selector: 'app-home',
diff --git a/frontend/src/app/shared/guards/logged-in.guard.spec.ts b/frontend/src/app/shared/guards/logged-in.guard.spec.ts
new file mode 100644
index 0000000..1850959
--- /dev/null
+++ b/frontend/src/app/shared/guards/logged-in.guard.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { LoggedInGuard } from './logged-in.guard';
+
+describe('LoggedInGuard', () => {
+ let guard: LoggedInGuard;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ guard = TestBed.inject(LoggedInGuard);
+ });
+
+ it('should be created', () => {
+ expect(guard).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/shared/guards/logged-in.guard.ts b/frontend/src/app/shared/guards/logged-in.guard.ts
new file mode 100644
index 0000000..f38a679
--- /dev/null
+++ b/frontend/src/app/shared/guards/logged-in.guard.ts
@@ -0,0 +1,13 @@
+import { inject, Injectable } from '@angular/core';
+import { CanActivate } from '@angular/router';
+import { AuthService } from '../services/auth.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class LoggedInGuard implements CanActivate {
+ private auth = inject(AuthService);
+ canActivate(): boolean {
+ return this.auth.jwt() !== null;
+ }
+}
diff --git a/frontend/src/app/shared/interfaces/auth.ts b/frontend/src/app/shared/interfaces/auth.ts
new file mode 100644
index 0000000..3676e97
--- /dev/null
+++ b/frontend/src/app/shared/interfaces/auth.ts
@@ -0,0 +1,7 @@
+export interface User {
+ name: string;
+ password: string;
+}
+export interface LoginResponse {
+ token: string;
+}
diff --git a/frontend/src/app/shared/services/interfaces/post.ts b/frontend/src/app/shared/interfaces/post.ts
similarity index 100%
rename from frontend/src/app/shared/services/interfaces/post.ts
rename to frontend/src/app/shared/interfaces/post.ts
diff --git a/frontend/src/app/shared/services/auth.service.spec.ts b/frontend/src/app/shared/services/auth.service.spec.ts
new file mode 100644
index 0000000..f1251ca
--- /dev/null
+++ b/frontend/src/app/shared/services/auth.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { AuthService } from './auth.service';
+
+describe('AuthService', () => {
+ let service: AuthService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(AuthService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/shared/services/auth.service.ts b/frontend/src/app/shared/services/auth.service.ts
new file mode 100644
index 0000000..ec30409
--- /dev/null
+++ b/frontend/src/app/shared/services/auth.service.ts
@@ -0,0 +1,20 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable, signal, WritableSignal } from '@angular/core';
+import { LoginResponse, User } from '../interfaces/auth';
+import { environment } from '../../../environments/environment';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class AuthService {
+ private http = inject(HttpClient);
+ jwt: WritableSignal = signal(null);
+
+ login(user: User) {
+ console.log(user);
+
+ this.http
+ .post(`${environment.apiRoot}/login`, user)
+ .subscribe((res) => this.jwt.set(res.token));
+ }
+}
diff --git a/frontend/src/app/shared/services/posts.service.ts b/frontend/src/app/shared/services/posts.service.ts
index bca3318..4ef2e6e 100644
--- a/frontend/src/app/shared/services/posts.service.ts
+++ b/frontend/src/app/shared/services/posts.service.ts
@@ -7,7 +7,7 @@ import {
signal,
WritableSignal,
} from '@angular/core';
-import { Post } from './interfaces/post';
+import { Post } from '../interfaces/post';
import { environment } from '../../../environments/environment';
@Injectable({
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index a7b5fc6..7ca046a 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -2,7 +2,7 @@
@import "tailwindcss";
/* apply todo css */
-.todo {
+app-markdown {
@layer base {
body {
@apply bg-gray-50 text-gray-800 font-sans leading-relaxed text-base;
@@ -30,9 +30,11 @@
@apply text-blue-600 hover:text-blue-800 transition-colors underline;
}
- ul,
+ ul {
+ @apply list-disc pl-6 mb-4;
+ }
ol {
- @apply pl-6 mb-4;
+ @apply list-decimal pl-6 mb-4;
}
li {
@@ -55,5 +57,11 @@
tr:nth-child(even) {
@apply bg-gray-50;
}
+ blockquote {
+ @apply italic bg-gray-200 rounded-md backdrop-blur-md p-3;
+ }
+ p {
+ @apply mb-2;
+ }
}
}