register, login, create and manage sites and pages
This commit is contained in:
15
frontend/src/app/core/guards/auth.guard.ts
Normal file
15
frontend/src/app/core/guards/auth.guard.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router, UrlTree } from '@angular/router';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private authService: AuthService, private router: Router) {}
|
||||
|
||||
canActivate(): boolean | UrlTree {
|
||||
if (this.authService.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
return this.router.parseUrl('/login');
|
||||
}
|
||||
}
|
||||
19
frontend/src/app/core/interceptors/jwt.interceptor.ts
Normal file
19
frontend/src/app/core/interceptors/jwt.interceptor.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtInterceptor implements HttpInterceptor {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
const token = this.authService.getAccessToken();
|
||||
if (token) {
|
||||
req = req.clone({
|
||||
setHeaders: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
}
|
||||
return next.handle(req);
|
||||
}
|
||||
}
|
||||
14
frontend/src/app/core/models/auth-response.ts
Normal file
14
frontend/src/app/core/models/auth-response.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface AuthResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
tokenType: string;
|
||||
user: UserInfo;
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
provider: 'LOCAL' | 'GOOGLE' | 'GITHUB' | 'OPENID';
|
||||
}
|
||||
18
frontend/src/app/core/models/page.ts
Normal file
18
frontend/src/app/core/models/page.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface PageResponse {
|
||||
id: string;
|
||||
siteId: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
seoTitle: string;
|
||||
seoDescription: string;
|
||||
orderIndex: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface PageRequest {
|
||||
title: string;
|
||||
slug: string;
|
||||
seoTitle?: string;
|
||||
seoDescription?: string;
|
||||
orderIndex: number;
|
||||
}
|
||||
19
frontend/src/app/core/models/site.ts
Normal file
19
frontend/src/app/core/models/site.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface SiteResponse {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
subdomain: string;
|
||||
themeConfig: Record<string, unknown>;
|
||||
status: 'DRAFT' | 'PUBLISHED';
|
||||
publishedAt: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface SiteRequest {
|
||||
name: string;
|
||||
description: string;
|
||||
subdomain: string;
|
||||
themeConfig?: Record<string, unknown>;
|
||||
}
|
||||
101
frontend/src/app/core/services/auth.service.ts
Normal file
101
frontend/src/app/core/services/auth.service.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable, map, tap } from 'rxjs';
|
||||
import { AuthResponse, UserInfo } from '../models/auth-response';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthService {
|
||||
private readonly ACCESS_TOKEN_KEY = 'indie_access_token';
|
||||
private readonly REFRESH_TOKEN_KEY = 'indie_refresh_token';
|
||||
private readonly USER_KEY = 'indie_user';
|
||||
|
||||
private userSubject = new BehaviorSubject<UserInfo | null>(this.loadUser());
|
||||
user$ = this.userSubject.asObservable();
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
register(name: string, email: string, password: string): Observable<AuthResponse> {
|
||||
return this.http.post<AuthResponse>('/api/auth/register', { name, email, password })
|
||||
.pipe(tap(res => this.setSession(res)));
|
||||
}
|
||||
|
||||
login(email: string, password: string): Observable<AuthResponse> {
|
||||
return this.http.post<AuthResponse>('/api/auth/login', { email, password })
|
||||
.pipe(tap(res => this.setSession(res)));
|
||||
}
|
||||
|
||||
refreshToken(refreshToken: string): Observable<AuthResponse> {
|
||||
return this.http.post<AuthResponse>('/api/auth/refresh', { refreshToken })
|
||||
.pipe(tap(res => this.setSession(res)));
|
||||
}
|
||||
|
||||
logout(): Observable<void> {
|
||||
return this.http.post<void>('/api/auth/logout', {})
|
||||
.pipe(tap(() => this.clearSession()));
|
||||
}
|
||||
|
||||
getAccessToken(): string | null {
|
||||
return localStorage.getItem(this.ACCESS_TOKEN_KEY);
|
||||
}
|
||||
|
||||
getRefreshToken(): string | null {
|
||||
return localStorage.getItem(this.REFRESH_TOKEN_KEY);
|
||||
}
|
||||
|
||||
getUser(): UserInfo | null {
|
||||
return this.userSubject.value;
|
||||
}
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return !!this.getAccessToken();
|
||||
}
|
||||
|
||||
setSessionFromOAuth(accessToken: string, refreshToken: string): void {
|
||||
localStorage.setItem(this.ACCESS_TOKEN_KEY, accessToken);
|
||||
localStorage.setItem(this.REFRESH_TOKEN_KEY, refreshToken);
|
||||
const payload = this.decodeToken(accessToken);
|
||||
if (payload) {
|
||||
const user: UserInfo = {
|
||||
id: payload.sub,
|
||||
email: payload.email,
|
||||
name: payload.name,
|
||||
avatarUrl: payload.avatarUrl || '',
|
||||
provider: payload.provider || 'LOCAL',
|
||||
};
|
||||
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
||||
this.userSubject.next(user);
|
||||
}
|
||||
}
|
||||
|
||||
clearSessionForLogout(): void {
|
||||
this.clearSession();
|
||||
}
|
||||
|
||||
private setSession(res: AuthResponse): void {
|
||||
localStorage.setItem(this.ACCESS_TOKEN_KEY, res.accessToken);
|
||||
localStorage.setItem(this.REFRESH_TOKEN_KEY, res.refreshToken);
|
||||
localStorage.setItem(this.USER_KEY, JSON.stringify(res.user));
|
||||
this.userSubject.next(res.user);
|
||||
}
|
||||
|
||||
private clearSession(): void {
|
||||
localStorage.removeItem(this.ACCESS_TOKEN_KEY);
|
||||
localStorage.removeItem(this.REFRESH_TOKEN_KEY);
|
||||
localStorage.removeItem(this.USER_KEY);
|
||||
this.userSubject.next(null);
|
||||
}
|
||||
|
||||
private loadUser(): UserInfo | null {
|
||||
const data = localStorage.getItem(this.USER_KEY);
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
private decodeToken(token: string): any {
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
return JSON.parse(atob(parts[1]));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
frontend/src/app/core/services/page.service.ts
Normal file
25
frontend/src/app/core/services/page.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PageResponse, PageRequest } from '../models/page';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PageService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
list(siteId: string): Observable<PageResponse[]> {
|
||||
return this.http.get<PageResponse[]>(`/api/sites/${siteId}/pages`);
|
||||
}
|
||||
|
||||
create(siteId: string, request: PageRequest): Observable<PageResponse> {
|
||||
return this.http.post<PageResponse>(`/api/sites/${siteId}/pages`, request);
|
||||
}
|
||||
|
||||
update(siteId: string, pageId: string, request: PageRequest): Observable<PageResponse> {
|
||||
return this.http.put<PageResponse>(`/api/sites/${siteId}/pages/${pageId}`, request);
|
||||
}
|
||||
|
||||
delete(siteId: string, pageId: string): Observable<void> {
|
||||
return this.http.delete<void>(`/api/sites/${siteId}/pages/${pageId}`);
|
||||
}
|
||||
}
|
||||
29
frontend/src/app/core/services/site.service.ts
Normal file
29
frontend/src/app/core/services/site.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SiteResponse, SiteRequest } from '../models/site';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SiteService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
list(): Observable<SiteResponse[]> {
|
||||
return this.http.get<SiteResponse[]>('/api/sites');
|
||||
}
|
||||
|
||||
create(request: SiteRequest): Observable<SiteResponse> {
|
||||
return this.http.post<SiteResponse>('/api/sites', request);
|
||||
}
|
||||
|
||||
get(id: string): Observable<SiteResponse> {
|
||||
return this.http.get<SiteResponse>(`/api/sites/${id}`);
|
||||
}
|
||||
|
||||
update(id: string, request: SiteRequest): Observable<SiteResponse> {
|
||||
return this.http.put<SiteResponse>(`/api/sites/${id}`, request);
|
||||
}
|
||||
|
||||
delete(id: string): Observable<void> {
|
||||
return this.http.delete<void>(`/api/sites/${id}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user