import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpParams, HttpHeaders, HttpEvent, HttpEventType, HttpRequest } from '@angular/common/http'; import { Observable, map, filter } from 'rxjs'; import { environment } from '../../../environments/environment'; export interface UploadProgress { progress: number; loaded: number; total: number; complete: boolean; response?: T; } export interface ApiResponse { success: boolean; data: T; timestamp: string; message?: string; } export interface PaginatedResponse { data: T[]; total: number; page: number; limit: number; totalPages: number; hasNextPage: boolean; } @Injectable({ providedIn: 'root', }) export class ApiService { private readonly http = inject(HttpClient); private readonly baseUrl = environment.apiBaseUrl; get(path: string, params?: Record): Observable { let httpParams = new HttpParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { httpParams = httpParams.set(key, String(value)); } }); } return this.http .get>(`${this.baseUrl}${path}`, { params: httpParams }) .pipe(map((response) => response.data)); } getRaw(path: string, params?: Record): Observable { let httpParams = new HttpParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { httpParams = httpParams.set(key, String(value)); } }); } return this.http.get(`${this.baseUrl}${path}`, { params: httpParams }); } post(path: string, body: unknown): Observable { return this.http .post>(`${this.baseUrl}${path}`, body) .pipe(map((response) => response.data)); } postRaw(path: string, body: unknown): Observable { return this.http.post(`${this.baseUrl}${path}`, body); } put(path: string, body: unknown): Observable { return this.http .put>(`${this.baseUrl}${path}`, body) .pipe(map((response) => response.data)); } patch(path: string, body: unknown): Observable { return this.http .patch>(`${this.baseUrl}${path}`, body) .pipe(map((response) => response.data)); } delete(path: string): Observable { return this.http .delete>(`${this.baseUrl}${path}`) .pipe(map((response) => response.data)); } upload(path: string, formData: FormData): Observable { return this.http .post>(`${this.baseUrl}${path}`, formData) .pipe(map((response) => response.data)); } /** * Upload with progress tracking * Returns an observable that emits upload progress and final response */ uploadWithProgress(path: string, formData: FormData): Observable> { const req = new HttpRequest('POST', `${this.baseUrl}${path}`, formData, { reportProgress: true, }); return this.http.request>(req).pipe( map((event: HttpEvent>) => { switch (event.type) { case HttpEventType.UploadProgress: const total = event.total || 0; const loaded = event.loaded; const progress = total > 0 ? Math.round((loaded / total) * 100) : 0; return { progress, loaded, total, complete: false, } as UploadProgress; case HttpEventType.Response: return { progress: 100, loaded: event.body?.data ? 1 : 0, total: 1, complete: true, response: event.body?.data, } as UploadProgress; default: return { progress: 0, loaded: 0, total: 0, complete: false, } as UploadProgress; } }) ); } download(path: string): Observable { return this.http.get(`${this.baseUrl}${path}`, { responseType: 'blob', }); } getBlob(url: string): Observable { return this.http.get(url, { responseType: 'blob' }); } }