started with post editor
This commit is contained in:
@ -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
|
||||
}
|
||||
|
BIN
backend/blog.db
BIN
backend/blog.db
Binary file not shown.
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -4,8 +4,19 @@
|
||||
<a routerLink="/">
|
||||
<h1 class="text-2xl">My Blog</h1>
|
||||
</a>
|
||||
<nav></nav>
|
||||
<nav class="flex gap-5">
|
||||
<a routerLink="/admin" *ngIf="loggedIn()">admin</a>
|
||||
<button *ngIf="!loggedIn()" (click)="toggleLogin()">login</button>
|
||||
<button *ngIf="loggedIn()" (click)="logOut()">logout</button>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="w-screen h-full px-5 sm:px-20 xl:px-96 pt-5">
|
||||
|
||||
<app-modal [open]="loginOpen" (openChange)="loginOpen = $event">
|
||||
<app-login />
|
||||
</app-modal>
|
||||
|
||||
<main
|
||||
class="w-screen h-full max-h-full px-5 sm:px-20 xl:px-44 3xl:px-96 pt-5 overflow-y-scroll"
|
||||
>
|
||||
<router-outlet />
|
||||
</main>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 },
|
||||
];
|
||||
|
22
frontend/src/app/components/login/login.component.html
Normal file
22
frontend/src/app/components/login/login.component.html
Normal file
@ -0,0 +1,22 @@
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" class="grid grid-cols-3 gap-5">
|
||||
<label>Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
formControlName="name"
|
||||
class="p-1 border-2 rounded-md col-span-2"
|
||||
/>
|
||||
<label>Password:</label>
|
||||
<input
|
||||
type="password"
|
||||
formControlName="password"
|
||||
required
|
||||
class="p-1 border-2 rounded-md col-span-2"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
required
|
||||
class="bg-blue-300 p-2 rounded-md drop-shadow-md hover:drop-shadow-xl active:bg-blue-600"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
23
frontend/src/app/components/login/login.component.spec.ts
Normal file
23
frontend/src/app/components/login/login.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
39
frontend/src/app/components/login/login.component.ts
Normal file
39
frontend/src/app/components/login/login.component.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
9
frontend/src/app/components/modal/modal.component.html
Normal file
9
frontend/src/app/components/modal/modal.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div
|
||||
*ngIf="open === true"
|
||||
class="fixed flex top-0 justify-center items-center w-screen h-screen z-50 backdrop-blur-md bg-black/25"
|
||||
>
|
||||
<div class="p-10 bg-white drop-shadow-md rounded-md">
|
||||
<button (click)="toggleOpen()" class="absolute top-0 right-1 p-3">X</button>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
23
frontend/src/app/components/modal/modal.component.spec.ts
Normal file
23
frontend/src/app/components/modal/modal.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModalComponent } from './modal.component';
|
||||
|
||||
describe('ModalComponent', () => {
|
||||
let component: ModalComponent;
|
||||
let fixture: ComponentFixture<ModalComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ModalComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
18
frontend/src/app/components/modal/modal.component.ts
Normal file
18
frontend/src/app/components/modal/modal.component.ts
Normal file
@ -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<boolean>();
|
||||
|
||||
toggleOpen() {
|
||||
this.open = !this.open;
|
||||
this.openChange.emit(this.open);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<div class="grid grid-cols-3">
|
||||
<label>Title:</label>
|
||||
<input
|
||||
type="text"
|
||||
[(ngModel)]="data().title"
|
||||
class="p-1 border-2 rounded-md col-span-2"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-3xl">{{ data().title }}</p>
|
||||
<div class="grid grid-cols-3">
|
||||
<label>TL;DR;</label>
|
||||
<input
|
||||
type="text"
|
||||
[(ngModel)]="data().tldr"
|
||||
class="p-1 border-2 rounded-md col-span-2"
|
||||
/>
|
||||
</div>
|
||||
<p class="mb-5 italic text-sm">TL;DR; {{ data().tldr }}</p>
|
||||
|
||||
<textarea
|
||||
[(ngModel)]="data().content"
|
||||
class="border-2 rounded-md"
|
||||
rows="20"
|
||||
></textarea>
|
||||
<app-markdown [markdown]="data().content" />
|
||||
</div>
|
@ -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<PostEditorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PostEditorComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PostEditorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -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<Post>({
|
||||
id: 0,
|
||||
title: '',
|
||||
tldr: '',
|
||||
content: '',
|
||||
});
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
console.log(this.data());
|
||||
});
|
||||
}
|
||||
}
|
1
frontend/src/app/routes/admin/admin.component.html
Normal file
1
frontend/src/app/routes/admin/admin.component.html
Normal file
@ -0,0 +1 @@
|
||||
<app-post-editor />
|
23
frontend/src/app/routes/admin/admin.component.spec.ts
Normal file
23
frontend/src/app/routes/admin/admin.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdminComponent } from './admin.component';
|
||||
|
||||
describe('AdminComponent', () => {
|
||||
let component: AdminComponent;
|
||||
let fixture: ComponentFixture<AdminComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AdminComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AdminComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
10
frontend/src/app/routes/admin/admin.component.ts
Normal file
10
frontend/src/app/routes/admin/admin.component.ts
Normal file
@ -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 {}
|
@ -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',
|
||||
|
16
frontend/src/app/shared/guards/logged-in.guard.spec.ts
Normal file
16
frontend/src/app/shared/guards/logged-in.guard.spec.ts
Normal file
@ -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();
|
||||
});
|
||||
});
|
13
frontend/src/app/shared/guards/logged-in.guard.ts
Normal file
13
frontend/src/app/shared/guards/logged-in.guard.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
7
frontend/src/app/shared/interfaces/auth.ts
Normal file
7
frontend/src/app/shared/interfaces/auth.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface User {
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
}
|
16
frontend/src/app/shared/services/auth.service.spec.ts
Normal file
16
frontend/src/app/shared/services/auth.service.spec.ts
Normal file
@ -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();
|
||||
});
|
||||
});
|
20
frontend/src/app/shared/services/auth.service.ts
Normal file
20
frontend/src/app/shared/services/auth.service.ts
Normal file
@ -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<string | null> = signal(null);
|
||||
|
||||
login(user: User) {
|
||||
console.log(user);
|
||||
|
||||
this.http
|
||||
.post<LoginResponse>(`${environment.apiRoot}/login`, user)
|
||||
.subscribe((res) => this.jwt.set(res.token));
|
||||
}
|
||||
}
|
@ -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({
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user