import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { shareReplay, tap } from 'rxjs/operators'; import { Lore, LoreCreate, LoreNode, LoreNodeCreate } from './lore.model'; /** Compte des entités qui seront supprimées en cascade avec un dossier. */ export interface LoreNodeDeletionImpact { /** Sous-dossiers (récursif, sans compter le dossier racine lui-même). */ folders: number; /** Pages dans l'ensemble du sous-arbre. */ pages: number; } /** Compte des entités qui seront supprimées / détachées en cascade avec un Lore. */ export interface LoreDeletionImpact { folders: number; pages: number; templates: number; /** Campagnes qui perdront leur référence au Lore (mais resteront présentes). */ detachedCampaigns: number; } /** * Service HTTP pour la gestion des Lores. * Port de sortie vers le Backend Java (Architecture Hexagonale). * * Les lectures agrégées par la sidebar (getAllLores, getLoreById, getLoreNodes) * sont mises en cache via `shareReplay(1)` pour éviter 5 fetchs redondants à * chaque navigation interne. Toute mutation (create/update/delete) invalide * l'ensemble du cache du service. */ @Injectable({ providedIn: 'root' }) export class LoreService { private apiUrl = '/api/lores'; private nodesUrl = '/api/lore-nodes'; private allLoresCache: Observable | null = null; private loreByIdCache = new Map>(); private nodesByLoreIdCache = new Map>(); constructor(private http: HttpClient) {} /** Vide tous les caches de lecture — appelé après toute mutation. */ private invalidate(): void { this.allLoresCache = null; this.loreByIdCache.clear(); this.nodesByLoreIdCache.clear(); } getAllLores(): Observable { if (!this.allLoresCache) { this.allLoresCache = this.http.get(this.apiUrl).pipe( tap({ error: () => (this.allLoresCache = null) }), shareReplay(1) ); } return this.allLoresCache; } getLoreById(id: string): Observable { let obs = this.loreByIdCache.get(id); if (!obs) { obs = this.http.get(`${this.apiUrl}/${id}`).pipe( tap({ error: () => this.loreByIdCache.delete(id) }), shareReplay(1) ); this.loreByIdCache.set(id, obs); } return obs; } createLore(lore: LoreCreate): Observable { return this.http.post(this.apiUrl, lore).pipe(tap(() => this.invalidate())); } updateLore(id: string, lore: LoreCreate): Observable { return this.http.put(`${this.apiUrl}/${id}`, lore).pipe(tap(() => this.invalidate())); } deleteLore(id: string): Observable { return this.http.delete(`${this.apiUrl}/${id}`).pipe(tap(() => this.invalidate())); } getLoreDeletionImpact(id: string): Observable { return this.http.get(`${this.apiUrl}/${id}/deletion-impact`); } getLoreNodes(loreId: string): Observable { let obs = this.nodesByLoreIdCache.get(loreId); if (!obs) { obs = this.http.get(`${this.nodesUrl}?loreId=${loreId}`).pipe( tap({ error: () => this.nodesByLoreIdCache.delete(loreId) }), shareReplay(1) ); this.nodesByLoreIdCache.set(loreId, obs); } return obs; } getLoreNodeById(id: string): Observable { return this.http.get(`${this.nodesUrl}/${id}`); } createLoreNode(node: LoreNodeCreate): Observable { return this.http.post(this.nodesUrl, node).pipe(tap(() => this.invalidate())); } /** PUT complet — envoie l'objet entier au backend (qui attend un LoreNodeDTO). */ updateLoreNode(id: string, node: LoreNode): Observable { return this.http.put(`${this.nodesUrl}/${id}`, node).pipe(tap(() => this.invalidate())); } deleteLoreNode(id: string): Observable { return this.http.delete(`${this.nodesUrl}/${id}`).pipe(tap(() => this.invalidate())); } getLoreNodeDeletionImpact(id: string): Observable { return this.http.get(`${this.nodesUrl}/${id}/deletion-impact`); } searchLores(q: string): Observable { const params = new HttpParams().set('q', q); return this.http.get(`${this.apiUrl}/search`, { params }); } searchLoreNodes(q: string): Observable { const params = new HttpParams().set('q', q); return this.http.get(`${this.nodesUrl}/search`, { params }); } }