feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with: Backend: - NestJS API with JWT authentication - PostgreSQL database with Knex ORM - Redis caching and session management - MinIO document storage - Hyperledger Besu blockchain integration - Multi-department workflow system - Comprehensive API tests (266/282 passing) Frontend: - Angular 21 with standalone components - Angular Material + TailwindCSS UI - Visual workflow builder - Document upload with progress tracking - Blockchain explorer integration - Role-based dashboards (Admin, Department, Citizen) - E2E tests with Playwright (37 tests) Infrastructure: - Docker Compose orchestration - Blockscout blockchain explorer - Development and production configurations
This commit is contained in:
150
frontend/src/app/core/services/api.service.ts
Normal file
150
frontend/src/app/core/services/api.service.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
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<T> {
|
||||
progress: number;
|
||||
loaded: number;
|
||||
total: number;
|
||||
complete: boolean;
|
||||
response?: T;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
timestamp: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
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<T>(path: string, params?: Record<string, string | number | boolean>): Observable<T> {
|
||||
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<ApiResponse<T>>(`${this.baseUrl}${path}`, { params: httpParams })
|
||||
.pipe(map((response) => response.data));
|
||||
}
|
||||
|
||||
getRaw<T>(path: string, params?: Record<string, string | number | boolean>): Observable<T> {
|
||||
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<T>(`${this.baseUrl}${path}`, { params: httpParams });
|
||||
}
|
||||
|
||||
post<T>(path: string, body: unknown): Observable<T> {
|
||||
return this.http
|
||||
.post<ApiResponse<T>>(`${this.baseUrl}${path}`, body)
|
||||
.pipe(map((response) => response.data));
|
||||
}
|
||||
|
||||
postRaw<T>(path: string, body: unknown): Observable<T> {
|
||||
return this.http.post<T>(`${this.baseUrl}${path}`, body);
|
||||
}
|
||||
|
||||
put<T>(path: string, body: unknown): Observable<T> {
|
||||
return this.http
|
||||
.put<ApiResponse<T>>(`${this.baseUrl}${path}`, body)
|
||||
.pipe(map((response) => response.data));
|
||||
}
|
||||
|
||||
patch<T>(path: string, body: unknown): Observable<T> {
|
||||
return this.http
|
||||
.patch<ApiResponse<T>>(`${this.baseUrl}${path}`, body)
|
||||
.pipe(map((response) => response.data));
|
||||
}
|
||||
|
||||
delete<T>(path: string): Observable<T> {
|
||||
return this.http
|
||||
.delete<ApiResponse<T>>(`${this.baseUrl}${path}`)
|
||||
.pipe(map((response) => response.data));
|
||||
}
|
||||
|
||||
upload<T>(path: string, formData: FormData): Observable<T> {
|
||||
return this.http
|
||||
.post<ApiResponse<T>>(`${this.baseUrl}${path}`, formData)
|
||||
.pipe(map((response) => response.data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload with progress tracking
|
||||
* Returns an observable that emits upload progress and final response
|
||||
*/
|
||||
uploadWithProgress<T>(path: string, formData: FormData): Observable<UploadProgress<T>> {
|
||||
const req = new HttpRequest('POST', `${this.baseUrl}${path}`, formData, {
|
||||
reportProgress: true,
|
||||
});
|
||||
|
||||
return this.http.request<ApiResponse<T>>(req).pipe(
|
||||
map((event: HttpEvent<ApiResponse<T>>) => {
|
||||
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<T>;
|
||||
|
||||
case HttpEventType.Response:
|
||||
return {
|
||||
progress: 100,
|
||||
loaded: event.body?.data ? 1 : 0,
|
||||
total: 1,
|
||||
complete: true,
|
||||
response: event.body?.data,
|
||||
} as UploadProgress<T>;
|
||||
|
||||
default:
|
||||
return {
|
||||
progress: 0,
|
||||
loaded: 0,
|
||||
total: 0,
|
||||
complete: false,
|
||||
} as UploadProgress<T>;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
download(path: string): Observable<Blob> {
|
||||
return this.http.get(`${this.baseUrl}${path}`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
}
|
||||
|
||||
getBlob(url: string): Observable<Blob> {
|
||||
return this.http.get(url, { responseType: 'blob' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user