From fd32ae5dcce556a576703359481785340ecd0a94 Mon Sep 17 00:00:00 2001 From: ChrisChen Date: Mon, 3 Nov 2025 17:10:18 +0000 Subject: [PATCH] Update src/app/services/crudServices/crud.service.ts --- src/app/services/crudServices/crud.service.ts | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/app/services/crudServices/crud.service.ts b/src/app/services/crudServices/crud.service.ts index 6021d75..ee372a5 100644 --- a/src/app/services/crudServices/crud.service.ts +++ b/src/app/services/crudServices/crud.service.ts @@ -178,3 +178,165 @@ export class CombinedKeyCrudService implements ICrudService { } } +// Type definitions +type TextResponse = { message: string }; +/** +* Base CRUD service that targets the provided controller path. +* +* It mirrors the endpoints of CrudBaseApiController: +* GET /api/{controller} +* GET /api/{controller}/{id} +* POST /api/{controller} -> string +* POST /api/{controller}/batch -> string[] +* PUT /api/{controller} +* PUT /api/{controller}/batch -> number +* DELETE /api/{controller}/{id} +* DELETE /api/{controller}/batch -> text summary +* GET /api/{controller}/{id}/exists -> boolean +* GET /api/{controller}/count -> number +*/ +@Injectable({ providedIn: 'root' }) +export class CrudBaseApiService { + /** + * Example: baseUrl = 'https://your-api', controller = 'Customer' → + * endpoint = 'https://your-api/api/Customer' + */ + protected readonly endpoint: string; + + + /** + * @param http Angular HttpClient + * @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl) + * @param controllerName Controller name (e.g., 'Customer', 'Orders') + */ + constructor( + protected http: HttpClient, + protected apiConfig: ApiConfigService, + @Inject(String) private controllerName: string + ) { + this.endpoint = apiConfig.getApiUrl(this.controllerName); + } + + + /** Optional default headers (JSON). Override in subclasses if needed. */ + protected get jsonHeaders(): HttpHeaders { + return new HttpHeaders({ 'Content-Type': 'application/json' }); + } + + + /** Shared error handler that surfaces useful messages. */ + protected handleError(error: HttpErrorResponse): Observable { + let msg = 'Unknown error'; + if (error.error instanceof Blob) { + // In case backend returns text/plain; charset=utf-8 as Blob + return throwError(() => new Error('Server returned an error blob')); + } + if (typeof error.error === 'string') msg = error.error; + else if (error.error?.message) msg = error.error.message; + else if (error.message) msg = error.message; + return throwError(() => new Error(msg)); + } + /** Prepare the response for the given entity. Override in subclasses if needed. */ + protected prepareResponse(response: T): T { + // Do nothing by default + return response; + } + + /** GET /api/{controller} */ + getAll(): Observable { + return this.http + .get(this.endpoint) + .pipe( + map(response => { + + for (let i = 0; i < response.length; i++) { + const element = response[i]; + response[i] = this.prepareResponse(element); + } + return response; + }), + catchError(err => this.handleError(err))); + } + + + /** GET /api/{controller}/{id} */ + getById(id: string): Observable { + return this.http + .get(`${this.endpoint}/${id}`) + .pipe( + map(response => this.prepareResponse(response)), + catchError(err => this.handleError(err))); + } + + + + /** POST /api/{controller} -> string */ + create(entity: T): Observable { + return this.http + .post(this.endpoint, entity, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** POST /api/{controller}/batch -> string[] */ + createRange(entities: T[]): Observable { + return this.http + .post(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** PUT /api/{controller} */ + update(entity: T): Observable { + return this.http + .put(this.endpoint, entity, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** PUT /api/{controller}/batch -> number (updated count) */ + updateRange(entities: T[]): Observable { + return this.http + .put(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** DELETE /api/{controller}/{id} */ + delete(id: string): Observable { + return this.http + .delete(`${this.endpoint}/${id}`) + .pipe(catchError(err => this.handleError(err))); + } + + + /** DELETE /api/{controller}/batch -> text summary */ + deleteRange(ids: string[]): Observable { + // API returns a plain text message; map it into a TextResponse for convenience + return this.http + .delete(`${this.endpoint}/batch`, { + body: ids, + headers: this.jsonHeaders + }) + .pipe( + map((response: any) => ({ message: response || 'Batch delete completed' })), + catchError(err => this.handleError(err)) + ); + } + + + /** GET /api/{controller}/{id}/exists -> boolean */ + exists(id: string): Observable { + return this.http + .get(`${this.endpoint}/${id}/exists`) + .pipe(catchError(err => this.handleError(err))); + } + + + /** GET /api/{controller}/count -> number */ + count(): Observable { + return this.http + .get(`${this.endpoint}/count`) + .pipe(catchError(err => this.handleError(err))); + } +}