import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { BehaviorSubject, map, Observable } from 'rxjs'; import { IUser } from '../model/interface/user.interface'; import { IKey } from '../model/interface/key.interface'; import { ICylinder } from '../model/interface/cylinder.interface'; import { HotToastService } from '@ngxpert/hot-toast'; import { ICustomer } from '../model/interface/customer.interface'; import { ISystem } from '../model/interface/keysystem.interface'; @Injectable({ providedIn: 'root' }) export class ApiService { private http: HttpClient = inject(HttpClient); private toast: HotToastService = inject(HotToastService); public keys: BehaviorSubject = new BehaviorSubject([]); public cylinders: BehaviorSubject = new BehaviorSubject([]); public systems: BehaviorSubject = new BehaviorSubject([]); public customers: BehaviorSubject = new BehaviorSubject([]); public user: BehaviorSubject = new BehaviorSubject(null!); public settings: BehaviorSubject = new BehaviorSubject(null!); constructor() { setTimeout(() => { this.setupKeyFeed(); }, 2000) } getMe(): Promise { return new Promise(resolve => { this.http.get('/api/auth/me').subscribe({ next: user => { this.user.next(user); resolve(user) }, error: () => { resolve(null!) } }) }) } getAllUsers(): Observable { return this.http.get('/api/user'); } saveUser(user: IUser) { return this.http.post('/api/user', user); } getRoles(): Observable<{id: string, name: string}[]> { return this.http.get<{id: string, name: string}[]>('/api/role'); } private getKeys(): Observable { return this.http.get('api/key') } /** * triggert das Laden der Schlüssel vom Server und speichert sie in keys */ refreshKeys(): void{ this.getKeys().subscribe({ next: keys => { this.keys.next(keys); }, error: () => { this.toast.error('Fehler beim Laden der Schlüssel') } }) } getLostKeys(): Observable { return this.http.get('api/key/lost') } updateKey(key: IKey): Promise { return new Promise(resolve => { this.http.put('api/key', key).pipe( this.toast.observe({ loading: `Speichere Schlüssel ${key.name}....`, success: `Schlüssel ${key.name} gespeichert.`, error: `Es ist ein Fehler aufgetreten!` }) ).subscribe({ next: (key: IKey) => resolve(key), error: () => resolve(null), // complete: () => { this.refreshKeys(); } }) }) } updateCylinder(cylinder: ICylinder): Promise { return new Promise(resolve => { this.http.put('api/cylinder', cylinder).pipe( this.toast.observe({ loading: `Zylinder ${cylinder} wird gespeichert...`, success: `Zylinder ${cylinder.name} gespeichert.`, error: 'Es ist ein Fehler aufgetreten' }) ).subscribe({ next: () => resolve(true), error: () => resolve(false), complete: () => { this.refreshCylinders() } }) }) } updateSystem(cylinder: any): Promise { return new Promise(resolve => { this.http.put('api/system', cylinder).pipe( this.toast.observe({ loading: `Schließanlage ${cylinder} wird gespeichert...`, success: `Schließanlage ${cylinder.name} gespeichert.`, error: 'Es ist ein Fehler aufgetreten' }) ).subscribe({ next: () => resolve(true), error: () => resolve(false), complete: () => { this.refreshCylinders() } }) }) } createKey(key: any) { return this.http.post('api/key', key); } createSystem(keySystem: any) { return this.http.post('api/system', keySystem); } private getSystems(): Observable { return this.http.get('api/system'); } getSystemManagers(id: string): Observable { return this.http.get(`api/system/${id}/manager`); } handoverKey(data: any) { return this.http.post(`api/key/${data.key.id}/handover`, data); } getHandovers(keyID: string): Observable { return this.http.get(`api/key/${keyID}/handover`); } createCustomer(data: { name: string, system: any}) { return this.http.post('api/customer', data); } private getCustomers(): Observable { return this.http.get('api/customer') } /** * triggert das Laden der Schlüssel vom Server und speichert sie in keys */ refreshCustomers(): Promise { return new Promise(resolve => { this.getCustomers().subscribe({ next: customers => { this.customers.next(customers); resolve(customers) }, error: () => { this.toast.error('Fehler beim Laden der Schlüssel'); resolve([]) } }) }) } /** * Löscht einen Schlüssel und gibt Meldungen aus. * Aktualisiert die Schlüssel danach * @param key zu löschen * @returns true wenn gelöscht, false wenn nicht */ deleteKey(key: IKey): Promise { return new Promise(resolve => { this.http.delete(`api/key/${key.id}`).pipe( this.toast.observe({ loading: `Lösche Schlüssel ${key.name}...`, success: `Schlüssel ${key.name} wurde gelöscht.`, error: 'Es ist ein Fehler aufgetreten' })).subscribe({ next: () => { return resolve(true); }, error: () => resolve(false), complete: () => { this.refreshKeys(); } }) }) } /** * Löscht das System vom Server und zeigt Toast an. * Aktualisiert die Systeme danach * @param system zu löschen * @returns true oder false */ deleteSystem(system: any): Promise { return new Promise(resolve => { this.http.delete(`api/system/${system.id}`).pipe( this.toast.observe({ loading: `Lösche Schließanlage ${system.name}...`, success: `Schließanlage ${system.name} wurde gelöscht.`, error: 'Es ist ein Fehler aufgetreten' }) ).subscribe({ next: () => resolve(true), error: () => resolve(false), complete: () => { this.refreshSystems(); } }) }) } getKeyArchive(): Observable { return this.http.get('api/key/archive'); } restoreKey(id: string) { return this.http.put(`api/key/${id}/restore`, null); } restoreSystem(system: ISystem): Promise { return new Promise(resolve => { this.http.put(`api/system/${system.id}/restore`, null).subscribe({ next: () => { this.refreshSystems(); resolve(true); }, error: () => { this.toast.error('Schließanlage konnte nicht wiederhergestellt werden'); resolve(false) } }) }) } private getCylinders(): Observable { return this.http.get('api/cylinder'); } getCylinderArchive(): Promise { return new Promise(resolve => { this.http.get('api/cylinder/archive').subscribe({ next: val => { return resolve(val); }, error: () => { this.toast.error('Gelöschte Zylinder konnten nicht geladen werden'); return resolve([]) } }) }) } getSystemArchive(): Promise { return new Promise(resolve => { this.http.get('api/system/archive').subscribe({ next: val => { return resolve(val); }, error: () => { this.toast.error('Gelöschte Schließanlagen konnten nicht geladen werden'); return resolve([]) } }) }) } /** * Aktualisiert die Zylinder im Behaviour Subject * cylinders * @returns Promise wenn geladen */ refreshCylinders(): Promise { return new Promise(resolve => { this.getCylinders().subscribe({ next: data => { this.cylinders.next(data); }, error: () => { this.toast.error('Fehler beim Laden der Zylinder') }, complete: () => resolve(this.cylinders.value) }) }) } /** * Aktualisiert die Schließanlagen im Behaviour Subject * systems * @returns Promise wenn geladen */ refreshSystems(): Promise { return new Promise(resolve => { this.getSystems().subscribe({ next: data => { this.systems.next(data); resolve() }, error: () => { this.toast.error('Fehler beim Laden der Schließanlagee') }, complete: () => resolve() }) }) } deleteCylinder(cylinder: ICylinder): Promise { return new Promise(resolve => { this.http.delete(`api/cylinder/${cylinder.id}`).pipe( this.toast.observe({ loading: `Lösche Zylinder ${cylinder.name}...`, success: `Zylinder ${cylinder.name} wurde gelöscht.`, error: 'Es ist ein Fehler aufgetreten' }) ).subscribe({ next: () => resolve(true), error: () => resolve(false), complete: () => this.refreshCylinders() }) }) } restoreCylinder(cylinder: ICylinder): Promise { return new Promise(resolve => { this.http.put(`api/cylinder/${cylinder.id}/restore`, null).pipe( this.toast.observe({ loading: `Stelle Zylinder ${cylinder.name} wieder her...`, success: `Zylinder ${cylinder.name} erfolgreich wiederhergestellt`, error: 'Es ist ein Fehler aufgetreten' })).subscribe({ next: () => resolve(true), error: () => resolve(false), complete: () => this.refreshCylinders() }) }) } deleteUser(id: string) { return this.http.delete(`api/user/${id}`) } addManager(systemID: string, email: string): Observable { return this.http.post(`api/system/${systemID}/manager`, { email, action: 'add' }); } removeManager(systemID: string, email: string): Observable { return this.http.post(`api/system/${systemID}/manager`, { email, action: 'remove' }); } getStats(): Observable<{ keys: number, cylinders: number, systems: number, handedOut: number }> { return this.http.get<{ keys: number, cylinders: number, systems: number, handedOut: number }>('api/user/stats'); } getActivities(): Observable { return this.http.get('api/user/activities'); } createCylinder(data: any) { return this.http.post('api/cylinder', data); } private loadSettings(): Promise { return new Promise(resolve => { this.http.get('api/user/settings').subscribe({ next: val => { this.settings.next(val); return resolve(val); }, error: () => { this.toast.error("Einstellungen konnten nicht geladen werden"); } }) }) } get userSettings(): Promise { if (!this.settings.value) { return this.loadSettings(); } return new Promise(resolve => { return resolve(this.settings.value); }) } updateSettings(settings: any): Promise { return new Promise(resolve => { this.http.post('api/user/settings', settings).subscribe({ next: async () => { await this.loadSettings(); this.toast.success('Einstellungen gespeichert') return resolve(true); }, error: () => { this.toast.error("Fehler beim Speichern der Einstellungen"); return resolve(false) } }) }) } private async setupKeyFeed() { const ticket = await this.getSSETicket(); const sseSource = new EventSource('api/sse/key?ticket=' + ticket); sseSource.addEventListener('message', (e: MessageEvent) => { try { const keys = JSON.parse(e.data) as IKey[]; this.keys.next(keys) } catch { this.toast.error('Fehler beim Empfangen der Schlüssel. Bitte später versuchen') } }) sseSource.addEventListener('error', (error) => { console.error(error) }) } private getSSETicket() { return new Promise(resolve => { this.http.get<{ ticket: string }>('api/sse/ticket').subscribe({ next: (ticket) => { return resolve(ticket.ticket) } }) }) } }