Admin mode. Data driven content sections. Bugfixes.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, firstValueFrom, map, Observable, tap } from 'rxjs';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
import { AppUser, AuthTokenResponse } from '../interfaces/auth';
|
||||
|
||||
const TOKEN_KEY = 'kmtn_access_token';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
private http = inject(HttpClient);
|
||||
private baseUrl = `${environment.apiBaseUrl}/api/auth`;
|
||||
|
||||
private authStateSubject = new BehaviorSubject<AppUser | null>(null);
|
||||
|
||||
/** Observable that emits the current auth state. */
|
||||
readonly authState$: Observable<AppUser | null> = this.authStateSubject.asObservable();
|
||||
|
||||
constructor() {
|
||||
// Check for existing token on init
|
||||
this._initAuth();
|
||||
}
|
||||
|
||||
/** Login with username/password (bootstrap admin). */
|
||||
login(username: string, password: string): Observable<AuthTokenResponse> {
|
||||
return this.http.post<AuthTokenResponse>(`${this.baseUrl}/login`, {
|
||||
username,
|
||||
password,
|
||||
}).pipe(
|
||||
tap((resp) => localStorage.setItem(TOKEN_KEY, resp.access_token)),
|
||||
);
|
||||
}
|
||||
|
||||
/** Login with Google ID token. */
|
||||
loginWithGoogle(idToken: string): Observable<AuthTokenResponse> {
|
||||
return this.http.post<AuthTokenResponse>(`${this.baseUrl}/google`, {
|
||||
id_token: idToken,
|
||||
}).pipe(
|
||||
tap((resp) => localStorage.setItem(TOKEN_KEY, resp.access_token)),
|
||||
);
|
||||
}
|
||||
|
||||
/** Clear stored token and reset auth state. */
|
||||
logout(): void {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
this.authStateSubject.next(null);
|
||||
}
|
||||
|
||||
/** Fetch current user info from the API. */
|
||||
getCurrentUser(): Promise<AppUser | null> {
|
||||
const token = this._getToken();
|
||||
if (!token) return Promise.resolve(null);
|
||||
|
||||
return firstValueFrom(
|
||||
this.http.get<AppUser>(`${this.baseUrl}/me`).pipe(
|
||||
tap((user) => this.authStateSubject.next(user)),
|
||||
map((user) => user),
|
||||
)
|
||||
).catch(() => {
|
||||
this.logout();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/** Check if there is a stored token. */
|
||||
isLoggedIn(): boolean {
|
||||
return !!this._getToken();
|
||||
}
|
||||
|
||||
/** Check if the current user is an admin (synchronous, based on cached state). */
|
||||
isAdmin(): boolean {
|
||||
return this.authStateSubject.value?.is_admin ?? false;
|
||||
}
|
||||
|
||||
/** Get the current user synchronously (from cached state). */
|
||||
getUser(): AppUser | null {
|
||||
return this.authStateSubject.value;
|
||||
}
|
||||
|
||||
// ── Private ──────────────────────────────────────────────
|
||||
|
||||
private _getToken(): string | null {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
private async _initAuth(): Promise<void> {
|
||||
const token = this._getToken();
|
||||
if (token) {
|
||||
await this.getCurrentUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user