@acontplus/ng-infrastructure 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, signal, computed } from '@angular/core';
|
|
3
|
+
import { HttpContextToken, HttpResponse, HttpContext, HttpClient } from '@angular/common/http';
|
|
4
|
+
import { retry, map, catchError, finalize } from 'rxjs/operators';
|
|
5
|
+
import { throwError, catchError as catchError$1, lastValueFrom } from 'rxjs';
|
|
6
|
+
import { NotificationService } from '@acontplus/ng-notifications';
|
|
7
|
+
import { Router } from '@angular/router';
|
|
8
|
+
import { v4 } from 'uuid';
|
|
9
|
+
import { ENVIRONMENT } from '@acontplus/ng-config';
|
|
10
|
+
import { OverlayService } from '@acontplus/ng-components';
|
|
11
|
+
import { jwtDecode } from 'jwt-decode';
|
|
12
|
+
|
|
13
|
+
const TOKEN_PROVIDER = new InjectionToken('TOKEN_PROVIDER');
|
|
14
|
+
|
|
15
|
+
// A token to use with HttpContext for skipping notifications on specific requests.
|
|
16
|
+
const SKIP_NOTIFICATION = new HttpContextToken(() => false);
|
|
17
|
+
// A token to use with HttpContext for forcing notifications on specific requests (overrides exclusion patterns).
|
|
18
|
+
const SHOW_NOTIFICATIONS = new HttpContextToken(() => undefined);
|
|
19
|
+
const apiInterceptor = (req, next) => {
|
|
20
|
+
const toastr = inject(NotificationService);
|
|
21
|
+
const tokenProvider = inject(TOKEN_PROVIDER, { optional: true });
|
|
22
|
+
// Add authorization header if token is available
|
|
23
|
+
let modifiedReq = req;
|
|
24
|
+
const token = tokenProvider?.getToken();
|
|
25
|
+
if (token) {
|
|
26
|
+
modifiedReq = req.clone({
|
|
27
|
+
setHeaders: { Authorization: `Bearer ${token}` }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return next(modifiedReq).pipe(
|
|
31
|
+
// Retries the request up to 2 times on failure with a 1-second delay.
|
|
32
|
+
retry({ count: 2, delay: 1000 }),
|
|
33
|
+
// Use the `map` operator to handle successful responses.
|
|
34
|
+
map((event) => {
|
|
35
|
+
if (event instanceof HttpResponse) {
|
|
36
|
+
return handleSuccessResponse(event, toastr, req);
|
|
37
|
+
}
|
|
38
|
+
return event;
|
|
39
|
+
}),
|
|
40
|
+
// Use the `catchError` operator to handle any errors that occur.
|
|
41
|
+
catchError((error) => handleErrorResponse(error, toastr)));
|
|
42
|
+
};
|
|
43
|
+
// --- Helper Functions ---
|
|
44
|
+
/**
|
|
45
|
+
* Handles successful HTTP responses, standardizes them, shows notifications, and transforms for consumers.
|
|
46
|
+
*/
|
|
47
|
+
function handleSuccessResponse(event, notificationService, req) {
|
|
48
|
+
const body = event.body;
|
|
49
|
+
// Standardize the response to always have ApiResponse structure
|
|
50
|
+
const standardizedResponse = standardizeApiResponse(body);
|
|
51
|
+
// Handle toast notifications based on standardized response
|
|
52
|
+
handleToastNotifications(standardizedResponse, notificationService, req);
|
|
53
|
+
// Return the appropriate data based on response type
|
|
54
|
+
return transformResponseForConsumers(standardizedResponse, event);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Handles HTTP errors (from the interceptor chain, not backend ApiResponse).
|
|
58
|
+
*/
|
|
59
|
+
function handleErrorResponse(error, notificationService) {
|
|
60
|
+
const status = error.status;
|
|
61
|
+
// Only show notifications for critical HTTP-level errors.
|
|
62
|
+
// We avoid showing toasts for 4xx errors, which are handled by the component.
|
|
63
|
+
if (status !== null && shouldShowCriticalErrorNotification(status)) {
|
|
64
|
+
const message = getCriticalErrorMessage(error);
|
|
65
|
+
const title = getErrorTitle(status);
|
|
66
|
+
notificationService.error({
|
|
67
|
+
message: message,
|
|
68
|
+
title: title,
|
|
69
|
+
config: { duration: 5000 },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Always re-throw the error so components/services can handle it.
|
|
73
|
+
return throwError(() => error);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Standardizes any response to follow the ApiResponse structure
|
|
77
|
+
*/
|
|
78
|
+
function standardizeApiResponse(body) {
|
|
79
|
+
// If it's already a proper ApiResponse, return as is
|
|
80
|
+
if (isValidApiResponse(body)) {
|
|
81
|
+
return body;
|
|
82
|
+
}
|
|
83
|
+
// If it's a raw data response (no wrapper), wrap it
|
|
84
|
+
if (body && typeof body === 'object' && !('status' in body)) {
|
|
85
|
+
return {
|
|
86
|
+
status: 'success',
|
|
87
|
+
code: '200',
|
|
88
|
+
message: 'Operation completed successfully',
|
|
89
|
+
data: body,
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// If it's a primitive value, wrap it
|
|
94
|
+
if (body !== null && body !== undefined && typeof body !== 'object') {
|
|
95
|
+
return {
|
|
96
|
+
status: 'success',
|
|
97
|
+
code: '200',
|
|
98
|
+
message: 'Operation completed successfully',
|
|
99
|
+
data: body,
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// If it's null/undefined, create a success response without data
|
|
104
|
+
return {
|
|
105
|
+
status: 'success',
|
|
106
|
+
code: '200',
|
|
107
|
+
message: 'Operation completed successfully',
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Handles toast notifications based on standardized response
|
|
113
|
+
*/
|
|
114
|
+
function handleToastNotifications(response, notificationService, req) {
|
|
115
|
+
const shouldShowToast = shouldShowSuccessToast(req);
|
|
116
|
+
const forceShow = req.context.get(SHOW_NOTIFICATIONS);
|
|
117
|
+
const skipNotification = req.context.get(SKIP_NOTIFICATION);
|
|
118
|
+
// Determine if we should show notifications, considering overrides
|
|
119
|
+
const showNotifications = forceShow !== undefined ? forceShow : shouldShowToast;
|
|
120
|
+
if (skipNotification)
|
|
121
|
+
return;
|
|
122
|
+
// Dynamic handling: Use show() for runtime type selection
|
|
123
|
+
if (response.message && showNotifications && ['success', 'warning', 'error'].includes(response.status)) {
|
|
124
|
+
notificationService.show({
|
|
125
|
+
type: response.status,
|
|
126
|
+
message: response.message,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// Handle warnings separately if needed
|
|
130
|
+
if (response.status === 'warning' && response.warnings && response.warnings.length > 0) {
|
|
131
|
+
response.warnings.forEach(warning => {
|
|
132
|
+
notificationService.show({
|
|
133
|
+
type: 'warning',
|
|
134
|
+
message: warning.message,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Handle errors separately if needed
|
|
139
|
+
if (response.status === 'error' && response.errors && response.errors.length > 0) {
|
|
140
|
+
response.errors.forEach(error => {
|
|
141
|
+
notificationService.show({
|
|
142
|
+
type: 'error',
|
|
143
|
+
message: error.message,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Transforms the standardized response for consumers
|
|
150
|
+
*/
|
|
151
|
+
function transformResponseForConsumers(response, originalEvent) {
|
|
152
|
+
switch (response.status) {
|
|
153
|
+
case 'success':
|
|
154
|
+
// For success responses, return the data if it exists, otherwise the full response
|
|
155
|
+
if (response.data !== undefined && response.data !== null) {
|
|
156
|
+
return originalEvent.clone({ body: response.data });
|
|
157
|
+
}
|
|
158
|
+
// For message-only success responses, return the full response
|
|
159
|
+
return originalEvent.clone({ body: response });
|
|
160
|
+
case 'warning':
|
|
161
|
+
// For warnings, return data if it exists, otherwise the full response
|
|
162
|
+
if (response.data !== undefined && response.data !== null) {
|
|
163
|
+
return originalEvent.clone({ body: response.data });
|
|
164
|
+
}
|
|
165
|
+
return originalEvent.clone({ body: response });
|
|
166
|
+
case 'error':
|
|
167
|
+
// Errors should be thrown, not returned
|
|
168
|
+
throw response;
|
|
169
|
+
default:
|
|
170
|
+
return originalEvent.clone({ body: response });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Checks if a response is a valid ApiResponse
|
|
175
|
+
*/
|
|
176
|
+
function isValidApiResponse(response) {
|
|
177
|
+
if (response === null || typeof response !== 'object')
|
|
178
|
+
return false;
|
|
179
|
+
const r = response;
|
|
180
|
+
return ('status' in r &&
|
|
181
|
+
'code' in r &&
|
|
182
|
+
typeof r['status'] === 'string' &&
|
|
183
|
+
['success', 'error', 'warning'].includes(r['status']));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Determines whether to show success toast notifications based on the request type.
|
|
187
|
+
*/
|
|
188
|
+
function shouldShowSuccessToast(req) {
|
|
189
|
+
const url = req.url?.toLowerCase() || '';
|
|
190
|
+
const method = req.method?.toLowerCase() || '';
|
|
191
|
+
// Never show for these cases
|
|
192
|
+
const excludedPatterns = [
|
|
193
|
+
'get',
|
|
194
|
+
'/list',
|
|
195
|
+
'/search',
|
|
196
|
+
'/query',
|
|
197
|
+
'/page',
|
|
198
|
+
'/paginated',
|
|
199
|
+
'/health',
|
|
200
|
+
'/status',
|
|
201
|
+
'/ping',
|
|
202
|
+
];
|
|
203
|
+
if (excludedPatterns.some(pattern => method === pattern || url.includes(pattern))) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
// Always show for these methods
|
|
207
|
+
if (['post', 'put', 'patch', 'delete'].includes(method)) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
// Default behavior
|
|
211
|
+
return method !== 'get';
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Determines if we should show a notification for this HTTP error.
|
|
215
|
+
*/
|
|
216
|
+
function shouldShowCriticalErrorNotification(status) {
|
|
217
|
+
// Show notifications for:
|
|
218
|
+
// - Network errors (status 0)
|
|
219
|
+
// - Server errors (5xx)
|
|
220
|
+
return status === 0 || (status >= 500 && status < 600);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Gets the appropriate title for error notifications.
|
|
224
|
+
*/
|
|
225
|
+
function getErrorTitle(status) {
|
|
226
|
+
if (status === 0)
|
|
227
|
+
return 'Connection Error';
|
|
228
|
+
if (status >= 500)
|
|
229
|
+
return 'Server Error';
|
|
230
|
+
return 'Error';
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Gets the appropriate message for critical errors.
|
|
234
|
+
*/
|
|
235
|
+
function getCriticalErrorMessage(error) {
|
|
236
|
+
if (error.status === 0) {
|
|
237
|
+
return 'Unable to connect to the server. Please check your network connection.';
|
|
238
|
+
}
|
|
239
|
+
if (error.status >= 500) {
|
|
240
|
+
return (error.error?.message || error.message || 'A server error occurred. Please try again later.');
|
|
241
|
+
}
|
|
242
|
+
return error.error?.message || error.message || 'An unexpected error occurred';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
class TenantService {
|
|
246
|
+
tenantId = null;
|
|
247
|
+
getTenantId() {
|
|
248
|
+
if (!this.tenantId) {
|
|
249
|
+
// Get from localStorage, sessionStorage, or JWT token
|
|
250
|
+
this.tenantId =
|
|
251
|
+
localStorage.getItem('tenantId') ||
|
|
252
|
+
sessionStorage.getItem('tenantId') ||
|
|
253
|
+
this.extractTenantFromToken() ||
|
|
254
|
+
'default-tenant';
|
|
255
|
+
}
|
|
256
|
+
return this.tenantId;
|
|
257
|
+
}
|
|
258
|
+
getCurrentTenant() {
|
|
259
|
+
return this.tenantId;
|
|
260
|
+
}
|
|
261
|
+
setTenantId(tenantId) {
|
|
262
|
+
this.tenantId = tenantId;
|
|
263
|
+
localStorage.setItem('tenantId', tenantId);
|
|
264
|
+
}
|
|
265
|
+
setTenant(tenantId) {
|
|
266
|
+
this.setTenantId(tenantId);
|
|
267
|
+
}
|
|
268
|
+
handleForbidden() {
|
|
269
|
+
console.error('Access forbidden for tenant:', this.tenantId);
|
|
270
|
+
// Redirect to tenant selection or show error message
|
|
271
|
+
// this.router.navigate(['/tenant-access-denied']);
|
|
272
|
+
}
|
|
273
|
+
extractTenantFromToken() {
|
|
274
|
+
// Implementation depends on your JWT token structure
|
|
275
|
+
// This is a placeholder
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
clearTenant() {
|
|
279
|
+
this.tenantId = null;
|
|
280
|
+
localStorage.removeItem('tenantId');
|
|
281
|
+
sessionStorage.removeItem('tenantId');
|
|
282
|
+
}
|
|
283
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TenantService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
284
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TenantService, providedIn: 'root' });
|
|
285
|
+
}
|
|
286
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TenantService, decorators: [{
|
|
287
|
+
type: Injectable,
|
|
288
|
+
args: [{
|
|
289
|
+
providedIn: 'root',
|
|
290
|
+
}]
|
|
291
|
+
}] });
|
|
292
|
+
|
|
293
|
+
class CorrelationService {
|
|
294
|
+
correlationId = signal(null, ...(ngDevMode ? [{ debugName: "correlationId" }] : []));
|
|
295
|
+
CORRELATION_KEY = 'correlation-id';
|
|
296
|
+
// Signal-based getter for reactive updates
|
|
297
|
+
currentCorrelationId = computed(() => this.correlationId(), ...(ngDevMode ? [{ debugName: "currentCorrelationId" }] : []));
|
|
298
|
+
getOrCreateCorrelationId() {
|
|
299
|
+
if (!this.correlationId()) {
|
|
300
|
+
// Try to get from sessionStorage first (for page refreshes)
|
|
301
|
+
// @ts-ignore
|
|
302
|
+
const id = sessionStorage.getItem(this.CORRELATION_KEY) || v4();
|
|
303
|
+
this.correlationId.set(id);
|
|
304
|
+
sessionStorage.setItem(this.CORRELATION_KEY, id);
|
|
305
|
+
}
|
|
306
|
+
return this.correlationId();
|
|
307
|
+
}
|
|
308
|
+
setCorrelationId(correlationId) {
|
|
309
|
+
this.correlationId.set(correlationId);
|
|
310
|
+
sessionStorage.setItem(this.CORRELATION_KEY, correlationId);
|
|
311
|
+
}
|
|
312
|
+
resetCorrelationId() {
|
|
313
|
+
this.correlationId.set(null);
|
|
314
|
+
sessionStorage.removeItem(this.CORRELATION_KEY);
|
|
315
|
+
}
|
|
316
|
+
getId() {
|
|
317
|
+
return this.getOrCreateCorrelationId();
|
|
318
|
+
}
|
|
319
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CorrelationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
320
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CorrelationService, providedIn: 'root' });
|
|
321
|
+
}
|
|
322
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CorrelationService, decorators: [{
|
|
323
|
+
type: Injectable,
|
|
324
|
+
args: [{
|
|
325
|
+
providedIn: 'root',
|
|
326
|
+
}]
|
|
327
|
+
}] });
|
|
328
|
+
|
|
329
|
+
class LoggingService {
|
|
330
|
+
environment = inject(ENVIRONMENT);
|
|
331
|
+
log(level, message, context) {
|
|
332
|
+
if (this.environment.isProduction) {
|
|
333
|
+
// Production logging (e.g., to external service)
|
|
334
|
+
this.logToExternalService(level, message, context);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// Development logging
|
|
338
|
+
console[level](`[${level.toUpperCase()}] ${message}`, context);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
info(message, context) {
|
|
342
|
+
this.log('info', message, context);
|
|
343
|
+
}
|
|
344
|
+
warn(message, context) {
|
|
345
|
+
this.log('warn', message, context);
|
|
346
|
+
}
|
|
347
|
+
error(message, context) {
|
|
348
|
+
this.log('error', message, context);
|
|
349
|
+
}
|
|
350
|
+
// HTTP Request Logging
|
|
351
|
+
logHttpRequest(log) {
|
|
352
|
+
this.info(`HTTP Request - ${log.method} ${log.url}`, {
|
|
353
|
+
requestId: log.requestId,
|
|
354
|
+
correlationId: log.correlationId,
|
|
355
|
+
tenantId: log.tenantId,
|
|
356
|
+
headers: log.headers,
|
|
357
|
+
isCustomUrl: log.isCustomUrl,
|
|
358
|
+
timestamp: log.timestamp,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// HTTP Error Logging
|
|
362
|
+
logHttpError(error) {
|
|
363
|
+
this.error(`HTTP Error - ${error.method} ${error.url}`, {
|
|
364
|
+
status: error.status,
|
|
365
|
+
statusText: error.statusText,
|
|
366
|
+
requestId: error.requestId,
|
|
367
|
+
correlationId: error.correlationId,
|
|
368
|
+
tenantId: error.tenantId,
|
|
369
|
+
errorDetails: error.errorDetails,
|
|
370
|
+
environment: error.environment,
|
|
371
|
+
timestamp: error.timestamp,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Network Error Logging
|
|
375
|
+
logNetworkError(correlationId) {
|
|
376
|
+
this.error('Network connection failed', {
|
|
377
|
+
type: 'network-error',
|
|
378
|
+
correlationId,
|
|
379
|
+
userAgent: navigator.userAgent,
|
|
380
|
+
online: navigator.onLine,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
// Rate Limit Error Logging
|
|
384
|
+
logRateLimitError(correlationId, url) {
|
|
385
|
+
this.warn('Rate limit exceeded', {
|
|
386
|
+
type: 'rate-limit-error',
|
|
387
|
+
correlationId,
|
|
388
|
+
url,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
logToExternalService(level, message, context) {
|
|
392
|
+
// Implement external logging service integration
|
|
393
|
+
// e.g., Sentry, LogRocket, etc.
|
|
394
|
+
// This is a placeholder for production logging implementation
|
|
395
|
+
}
|
|
396
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoggingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
397
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoggingService, providedIn: 'root' });
|
|
398
|
+
}
|
|
399
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoggingService, decorators: [{
|
|
400
|
+
type: Injectable,
|
|
401
|
+
args: [{
|
|
402
|
+
providedIn: 'root',
|
|
403
|
+
}]
|
|
404
|
+
}] });
|
|
405
|
+
|
|
406
|
+
// HTTP Context tokens
|
|
407
|
+
const CUSTOM_URL = new HttpContextToken(() => false);
|
|
408
|
+
const SKIP_CONTEXT_HEADERS = new HttpContextToken(() => false);
|
|
409
|
+
const CUSTOM_HEADERS = new HttpContextToken(() => ({}));
|
|
410
|
+
// Helper functions for HTTP context
|
|
411
|
+
function customUrl() {
|
|
412
|
+
return new HttpContext().set(CUSTOM_URL, true);
|
|
413
|
+
}
|
|
414
|
+
function skipContextHeaders() {
|
|
415
|
+
return new HttpContext().set(SKIP_CONTEXT_HEADERS, true);
|
|
416
|
+
}
|
|
417
|
+
function withCustomHeaders(headers) {
|
|
418
|
+
return new HttpContext().set(CUSTOM_HEADERS, headers);
|
|
419
|
+
}
|
|
420
|
+
// Default configuration
|
|
421
|
+
const DEFAULT_CONFIG$1 = {
|
|
422
|
+
enableCorrelationTracking: true,
|
|
423
|
+
enableRequestLogging: false, // This will be overridden by environment.isProduction
|
|
424
|
+
enableErrorLogging: true,
|
|
425
|
+
customHeaders: {},
|
|
426
|
+
clientVersion: '1.0.0',
|
|
427
|
+
tenantIdHeader: 'Tenant-Id',
|
|
428
|
+
correlationIdHeader: 'Correlation-Id',
|
|
429
|
+
requestIdHeader: 'Request-Id',
|
|
430
|
+
timestampHeader: 'Timestamp',
|
|
431
|
+
excludeUrls: [],
|
|
432
|
+
includeAuthToken: true,
|
|
433
|
+
baseUrlInjection: true,
|
|
434
|
+
};
|
|
435
|
+
// Injection token for configuration - FIXED
|
|
436
|
+
const HTTP_CONTEXT_CONFIG = new InjectionToken('HTTP_CONTEXT_CONFIG', {
|
|
437
|
+
factory: () => DEFAULT_CONFIG$1, // Provide a default factory in case it's not provided
|
|
438
|
+
});
|
|
439
|
+
const httpContextInterceptor = (req, next) => {
|
|
440
|
+
const tokenProvider = inject(TOKEN_PROVIDER, { optional: true });
|
|
441
|
+
const router = inject(Router);
|
|
442
|
+
const tenantService = inject(TenantService);
|
|
443
|
+
const correlationService = inject(CorrelationService);
|
|
444
|
+
const loggingService = inject(LoggingService);
|
|
445
|
+
const environment = inject(ENVIRONMENT);
|
|
446
|
+
// Get configuration (with fallback to default) - FIXED
|
|
447
|
+
const config = {
|
|
448
|
+
...DEFAULT_CONFIG$1,
|
|
449
|
+
// The default enableRequestLogging needs to be set based on environment here,
|
|
450
|
+
// as it's dynamic, and DEFAULT_CONFIG is static.
|
|
451
|
+
enableRequestLogging: !environment.isProduction,
|
|
452
|
+
...inject(HTTP_CONTEXT_CONFIG, { optional: true }),
|
|
453
|
+
};
|
|
454
|
+
// Check HTTP context tokens
|
|
455
|
+
const isCustomUrl = req.context.get(CUSTOM_URL);
|
|
456
|
+
const skipHeaders = req.context.get(SKIP_CONTEXT_HEADERS);
|
|
457
|
+
const contextHeaders = req.context.get(CUSTOM_HEADERS);
|
|
458
|
+
// Skip processing if headers are disabled for this request
|
|
459
|
+
if (skipHeaders) {
|
|
460
|
+
return next(req);
|
|
461
|
+
}
|
|
462
|
+
// Check if URL should be excluded
|
|
463
|
+
const shouldExclude = config.excludeUrls.some(url => req.url.includes(url) || new RegExp(url).test(req.url));
|
|
464
|
+
if (shouldExclude) {
|
|
465
|
+
return next(req);
|
|
466
|
+
}
|
|
467
|
+
// Handle URL transformation
|
|
468
|
+
const baseUrl = environment.apiBaseUrl;
|
|
469
|
+
const finalUrl = isCustomUrl || !config.baseUrlInjection ? req.url : `${baseUrl}${req.url}`;
|
|
470
|
+
// Generate or get correlation context
|
|
471
|
+
const correlationId = config.enableCorrelationTracking
|
|
472
|
+
? correlationService.getOrCreateCorrelationId()
|
|
473
|
+
: v4();
|
|
474
|
+
const tenantId = tenantService.getTenantId();
|
|
475
|
+
const requestId = v4();
|
|
476
|
+
// Build dynamic headers
|
|
477
|
+
const headers = {};
|
|
478
|
+
// Core context headers
|
|
479
|
+
headers[config.requestIdHeader] = requestId;
|
|
480
|
+
if (config.enableCorrelationTracking) {
|
|
481
|
+
headers[config.correlationIdHeader] = correlationId;
|
|
482
|
+
}
|
|
483
|
+
if (tenantId) {
|
|
484
|
+
headers[config.tenantIdHeader] = tenantId;
|
|
485
|
+
}
|
|
486
|
+
headers[config.timestampHeader] = new Date().toISOString();
|
|
487
|
+
// Client information
|
|
488
|
+
if (config.clientVersion) {
|
|
489
|
+
headers['Client-Version'] = config.clientVersion;
|
|
490
|
+
}
|
|
491
|
+
// Environment client ID
|
|
492
|
+
if (environment.clientId) {
|
|
493
|
+
headers['Client-Id'] = environment.clientId;
|
|
494
|
+
}
|
|
495
|
+
// Add authorization header if configured and available
|
|
496
|
+
if (config.includeAuthToken) {
|
|
497
|
+
const authToken = tokenProvider?.getToken();
|
|
498
|
+
if (authToken) {
|
|
499
|
+
headers['Authorization'] = `Bearer ${authToken}`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Process custom headers from configuration (can be static values or dynamic functions)
|
|
503
|
+
Object.entries(config.customHeaders).forEach(([key, value]) => {
|
|
504
|
+
headers[key] = typeof value === 'function' ? value() : value;
|
|
505
|
+
});
|
|
506
|
+
// Add headers from HTTP context
|
|
507
|
+
Object.entries(contextHeaders).forEach(([key, value]) => {
|
|
508
|
+
headers[key] = value;
|
|
509
|
+
});
|
|
510
|
+
// Add content-type for data requests if not already set
|
|
511
|
+
if (['POST', 'PUT', 'PATCH'].includes(req.method.toUpperCase()) &&
|
|
512
|
+
!req.headers.has('Content-Type')) {
|
|
513
|
+
headers['Content-Type'] = 'application/json';
|
|
514
|
+
}
|
|
515
|
+
// Clone request with enhanced headers and URL
|
|
516
|
+
const enhancedReq = req.clone({
|
|
517
|
+
url: finalUrl,
|
|
518
|
+
setHeaders: headers,
|
|
519
|
+
});
|
|
520
|
+
// Log outgoing request if enabled
|
|
521
|
+
if (config.enableRequestLogging) {
|
|
522
|
+
loggingService.logHttpRequest({
|
|
523
|
+
method: req.method,
|
|
524
|
+
url: finalUrl,
|
|
525
|
+
originalUrl: req.url,
|
|
526
|
+
requestId,
|
|
527
|
+
correlationId,
|
|
528
|
+
tenantId,
|
|
529
|
+
timestamp: new Date().toISOString(),
|
|
530
|
+
headers: Object.keys(headers),
|
|
531
|
+
isCustomUrl,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return next(enhancedReq).pipe(catchError$1((error) => {
|
|
535
|
+
// Enhanced error logging with context
|
|
536
|
+
if (config.enableErrorLogging) {
|
|
537
|
+
loggingService.logHttpError({
|
|
538
|
+
method: req.method,
|
|
539
|
+
url: finalUrl,
|
|
540
|
+
originalUrl: req.url,
|
|
541
|
+
requestId,
|
|
542
|
+
correlationId,
|
|
543
|
+
tenantId,
|
|
544
|
+
status: error.status,
|
|
545
|
+
statusText: error.statusText,
|
|
546
|
+
message: error.message,
|
|
547
|
+
timestamp: new Date().toISOString(),
|
|
548
|
+
errorDetails: error.error,
|
|
549
|
+
environment: environment.clientId,
|
|
550
|
+
isCustomUrl,
|
|
551
|
+
headers: [], // Headers are not included in HttpErrorResponse by default, adjust if needed
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
// Handle specific error scenarios
|
|
555
|
+
switch (error.status) {
|
|
556
|
+
case 401:
|
|
557
|
+
console.error('Unauthorized access - token expired or invalid');
|
|
558
|
+
// Note: Token clearing should be handled by the auth service, not infrastructure
|
|
559
|
+
router.navigate(['/login']);
|
|
560
|
+
break;
|
|
561
|
+
case 403:
|
|
562
|
+
tenantService.handleForbidden();
|
|
563
|
+
break;
|
|
564
|
+
case 0:
|
|
565
|
+
loggingService.logNetworkError(correlationId);
|
|
566
|
+
break;
|
|
567
|
+
case 429:
|
|
568
|
+
loggingService.logRateLimitError(correlationId, finalUrl);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
return throwError(() => error);
|
|
572
|
+
}));
|
|
573
|
+
};
|
|
574
|
+
// Helper function to create configuration
|
|
575
|
+
function createHttpContextConfig(overrides = {}) {
|
|
576
|
+
return {
|
|
577
|
+
...DEFAULT_CONFIG$1,
|
|
578
|
+
...overrides,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
// Provider function for easy setup
|
|
582
|
+
function provideHttpContext(config = {}) {
|
|
583
|
+
return [
|
|
584
|
+
{
|
|
585
|
+
provide: HTTP_CONTEXT_CONFIG,
|
|
586
|
+
useValue: createHttpContextConfig(config),
|
|
587
|
+
},
|
|
588
|
+
];
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Token to determine if a request should show spinner
|
|
593
|
+
* Default is true (show spinner for all requests)
|
|
594
|
+
*/
|
|
595
|
+
const SHOW_SPINNER = new HttpContextToken(() => true);
|
|
596
|
+
const requests = [];
|
|
597
|
+
/**
|
|
598
|
+
* Helper function to disable spinner for specific requests
|
|
599
|
+
* @returns HttpContext with spinner disabled
|
|
600
|
+
*/
|
|
601
|
+
function withoutSpinner() {
|
|
602
|
+
return new HttpContext().set(SHOW_SPINNER, false);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Service to track active HTTP requests
|
|
606
|
+
*/
|
|
607
|
+
class ActiveRequestsTracker {
|
|
608
|
+
get count() {
|
|
609
|
+
return requests.length;
|
|
610
|
+
}
|
|
611
|
+
add(request) {
|
|
612
|
+
requests.push(request);
|
|
613
|
+
}
|
|
614
|
+
remove(request) {
|
|
615
|
+
const index = requests.indexOf(request);
|
|
616
|
+
if (index >= 0) {
|
|
617
|
+
requests.splice(index, 1);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: ActiveRequestsTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
621
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: ActiveRequestsTracker, providedIn: 'root' });
|
|
622
|
+
}
|
|
623
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: ActiveRequestsTracker, decorators: [{
|
|
624
|
+
type: Injectable,
|
|
625
|
+
args: [{
|
|
626
|
+
providedIn: 'root',
|
|
627
|
+
}]
|
|
628
|
+
}] });
|
|
629
|
+
/**
|
|
630
|
+
* Interceptor that shows/hides a loading spinner based on active HTTP requests
|
|
631
|
+
*/
|
|
632
|
+
const spinnerInterceptor = (req, next) => {
|
|
633
|
+
// Track active requests requiring spinner
|
|
634
|
+
console.log(requests);
|
|
635
|
+
const activeRequests = inject(ActiveRequestsTracker);
|
|
636
|
+
const overlayService = inject(OverlayService);
|
|
637
|
+
// Skip spinner if disabled for this request
|
|
638
|
+
if (!req.context.get(SHOW_SPINNER)) {
|
|
639
|
+
return next(req);
|
|
640
|
+
}
|
|
641
|
+
// Add request to tracking
|
|
642
|
+
activeRequests.add(req);
|
|
643
|
+
// Show spinner if this is the first active request
|
|
644
|
+
if (activeRequests.count === 1) {
|
|
645
|
+
overlayService.showSpinner();
|
|
646
|
+
}
|
|
647
|
+
return next(req).pipe(finalize(() => {
|
|
648
|
+
// Remove request and hide spinner if no more active requests
|
|
649
|
+
activeRequests.remove(req);
|
|
650
|
+
if (activeRequests.count === 0) {
|
|
651
|
+
overlayService.hideSpinner();
|
|
652
|
+
}
|
|
653
|
+
}));
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
class BaseHttpRepository {
|
|
657
|
+
http = inject(HttpClient);
|
|
658
|
+
buildUrl(path = '') {
|
|
659
|
+
const baseUrl = this.config.baseUrl || '/api';
|
|
660
|
+
const version = this.config.version ? `/v${this.config.version}` : '';
|
|
661
|
+
const endpoint = path ? `${this.config.endpoint}/${path}` : this.config.endpoint;
|
|
662
|
+
return `${baseUrl}${version}/${endpoint}`.replace(/\/+/g, '/');
|
|
663
|
+
}
|
|
664
|
+
get(path = '', params) {
|
|
665
|
+
return this.http.get(this.buildUrl(path), { params });
|
|
666
|
+
}
|
|
667
|
+
post(path = '', body) {
|
|
668
|
+
return this.http.post(this.buildUrl(path), body);
|
|
669
|
+
}
|
|
670
|
+
put(path = '', body) {
|
|
671
|
+
return this.http.put(this.buildUrl(path), body);
|
|
672
|
+
}
|
|
673
|
+
patch(path = '', body) {
|
|
674
|
+
return this.http.patch(this.buildUrl(path), body);
|
|
675
|
+
}
|
|
676
|
+
delete(path = '') {
|
|
677
|
+
return this.http.delete(this.buildUrl(path));
|
|
678
|
+
}
|
|
679
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: BaseHttpRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
680
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: BaseHttpRepository });
|
|
681
|
+
}
|
|
682
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: BaseHttpRepository, decorators: [{
|
|
683
|
+
type: Injectable
|
|
684
|
+
}] });
|
|
685
|
+
|
|
686
|
+
// Create an injection token for RepositoryConfig
|
|
687
|
+
const REPOSITORY_CONFIG = new InjectionToken('REPOSITORY_CONFIG');
|
|
688
|
+
class GenericRepository extends BaseHttpRepository {
|
|
689
|
+
config;
|
|
690
|
+
constructor() {
|
|
691
|
+
const config = inject(REPOSITORY_CONFIG);
|
|
692
|
+
super();
|
|
693
|
+
this.config = config;
|
|
694
|
+
}
|
|
695
|
+
getById(id) {
|
|
696
|
+
return this.get(id.toString());
|
|
697
|
+
}
|
|
698
|
+
getAll(pagination) {
|
|
699
|
+
const params = this.buildParams(pagination);
|
|
700
|
+
return this.get('', params);
|
|
701
|
+
}
|
|
702
|
+
create(entity) {
|
|
703
|
+
return this.post('', entity);
|
|
704
|
+
}
|
|
705
|
+
update(id, entity) {
|
|
706
|
+
return this.put(id.toString(), entity);
|
|
707
|
+
}
|
|
708
|
+
remove(id) {
|
|
709
|
+
return super.delete(id.toString());
|
|
710
|
+
}
|
|
711
|
+
buildParams(pagination, filters) {
|
|
712
|
+
const params = {};
|
|
713
|
+
if (pagination) {
|
|
714
|
+
params.pageIndex = pagination.pageIndex?.toString() || '1';
|
|
715
|
+
params.pageSize = pagination.pageSize?.toString() || '20';
|
|
716
|
+
if (pagination.sortBy)
|
|
717
|
+
params.sortBy = pagination.sortBy;
|
|
718
|
+
if (pagination.sortDirection)
|
|
719
|
+
params.sortDirection = pagination.sortDirection;
|
|
720
|
+
}
|
|
721
|
+
if (filters) {
|
|
722
|
+
Object.assign(params, filters);
|
|
723
|
+
}
|
|
724
|
+
return params;
|
|
725
|
+
}
|
|
726
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: GenericRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
727
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: GenericRepository });
|
|
728
|
+
}
|
|
729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: GenericRepository, decorators: [{
|
|
730
|
+
type: Injectable
|
|
731
|
+
}], ctorParameters: () => [] });
|
|
732
|
+
class SearchableGenericRepository extends GenericRepository {
|
|
733
|
+
search(query, pagination) {
|
|
734
|
+
const searchFilters = { q: query };
|
|
735
|
+
const params = this.buildParams(pagination, searchFilters);
|
|
736
|
+
return this.get('search', params);
|
|
737
|
+
}
|
|
738
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: SearchableGenericRepository, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
739
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: SearchableGenericRepository });
|
|
740
|
+
}
|
|
741
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: SearchableGenericRepository, decorators: [{
|
|
742
|
+
type: Injectable
|
|
743
|
+
}] });
|
|
744
|
+
|
|
745
|
+
class RepositoryFactory {
|
|
746
|
+
http = inject(HttpClient);
|
|
747
|
+
create(config) {
|
|
748
|
+
const buildUrl = (path = '') => {
|
|
749
|
+
const baseUrl = config.baseUrl || '/api';
|
|
750
|
+
const version = config.version ? `/v${config.version}` : '';
|
|
751
|
+
const endpoint = path ? `${config.endpoint}/${path}` : config.endpoint;
|
|
752
|
+
return `${baseUrl}${version}/${endpoint}`.replace(/\/+/g, '/');
|
|
753
|
+
};
|
|
754
|
+
const buildParams = (pagination) => {
|
|
755
|
+
const params = {};
|
|
756
|
+
if (pagination) {
|
|
757
|
+
params.page = pagination.pageIndex?.toString() || '1';
|
|
758
|
+
params.pageSize = pagination.pageSize?.toString() || '20';
|
|
759
|
+
if (pagination.sortBy)
|
|
760
|
+
params.sortBy = pagination.sortBy;
|
|
761
|
+
if (pagination.sortDirection)
|
|
762
|
+
params.sortDirection = pagination.sortDirection;
|
|
763
|
+
}
|
|
764
|
+
return params;
|
|
765
|
+
};
|
|
766
|
+
// Safe ID conversion function
|
|
767
|
+
const idToString = (id) => {
|
|
768
|
+
if (typeof id === 'string' || typeof id === 'number' || typeof id === 'bigint') {
|
|
769
|
+
return id.toString();
|
|
770
|
+
}
|
|
771
|
+
// Fallback for other types - you might want to handle this differently
|
|
772
|
+
return String(id);
|
|
773
|
+
};
|
|
774
|
+
return {
|
|
775
|
+
getById: (id) => this.http.get(buildUrl(idToString(id))),
|
|
776
|
+
getAll: (pagination) => this.http.get(buildUrl(), {
|
|
777
|
+
params: buildParams(pagination),
|
|
778
|
+
}),
|
|
779
|
+
create: (entity) => this.http.post(buildUrl(), entity),
|
|
780
|
+
update: (id, entity) => this.http.put(buildUrl(idToString(id)), entity),
|
|
781
|
+
remove: (id) => this.http.delete(buildUrl(idToString(id))),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RepositoryFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
785
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RepositoryFactory, providedIn: 'root' });
|
|
786
|
+
}
|
|
787
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RepositoryFactory, decorators: [{
|
|
788
|
+
type: Injectable,
|
|
789
|
+
args: [{ providedIn: 'root' }]
|
|
790
|
+
}] });
|
|
791
|
+
|
|
792
|
+
class UserRepository {
|
|
793
|
+
tokenProvider = inject(TOKEN_PROVIDER);
|
|
794
|
+
getCurrentUser() {
|
|
795
|
+
const token = this.tokenProvider.getToken();
|
|
796
|
+
if (!token) {
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
const decodedToken = jwtDecode(token);
|
|
801
|
+
const email = decodedToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] ??
|
|
802
|
+
decodedToken['email'] ??
|
|
803
|
+
decodedToken['sub'] ??
|
|
804
|
+
decodedToken['user_id'];
|
|
805
|
+
const name = decodedToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'] ??
|
|
806
|
+
decodedToken['name'] ??
|
|
807
|
+
decodedToken['given_name'] ??
|
|
808
|
+
decodedToken['display_name'];
|
|
809
|
+
if (!email) {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
const userData = {
|
|
813
|
+
email: email.toString(),
|
|
814
|
+
name: name?.toString() ?? 'Unknown User',
|
|
815
|
+
};
|
|
816
|
+
return userData;
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UserRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
823
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UserRepository, providedIn: 'root' });
|
|
824
|
+
}
|
|
825
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UserRepository, decorators: [{
|
|
826
|
+
type: Injectable,
|
|
827
|
+
args: [{
|
|
828
|
+
providedIn: 'root',
|
|
829
|
+
}]
|
|
830
|
+
}] });
|
|
831
|
+
|
|
832
|
+
function mergeUrl(baseURL, endpoint) {
|
|
833
|
+
if (!baseURL)
|
|
834
|
+
return endpoint;
|
|
835
|
+
return `${baseURL.replace(/\/+$/, '')}/${endpoint.replace(/^\/+/, '')}`;
|
|
836
|
+
}
|
|
837
|
+
class AngularHttpAdapter {
|
|
838
|
+
http;
|
|
839
|
+
baseURL;
|
|
840
|
+
constructor(http, baseURL) {
|
|
841
|
+
this.http = http;
|
|
842
|
+
this.baseURL = baseURL;
|
|
843
|
+
}
|
|
844
|
+
buildOptions(options) {
|
|
845
|
+
return {
|
|
846
|
+
headers: options?.headers ?? {},
|
|
847
|
+
params: options?.params ?? {},
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
async request(params) {
|
|
851
|
+
const fullUrl = mergeUrl(this.baseURL, params.url);
|
|
852
|
+
const httpOptions = this.buildOptions(params.options);
|
|
853
|
+
const observable = this.http.request(params.method, fullUrl, {
|
|
854
|
+
body: params.data,
|
|
855
|
+
...httpOptions,
|
|
856
|
+
});
|
|
857
|
+
return await lastValueFrom(observable);
|
|
858
|
+
}
|
|
859
|
+
/** GET */
|
|
860
|
+
get(url, options) {
|
|
861
|
+
return this.request({ method: 'get', url, options });
|
|
862
|
+
}
|
|
863
|
+
/** POST */
|
|
864
|
+
post(url, data, options) {
|
|
865
|
+
return this.request({ method: 'post', url, data, options });
|
|
866
|
+
}
|
|
867
|
+
/** PUT */
|
|
868
|
+
put(url, data, options) {
|
|
869
|
+
return this.request({ method: 'put', url, data, options });
|
|
870
|
+
}
|
|
871
|
+
/** DELETE */
|
|
872
|
+
delete(url, options) {
|
|
873
|
+
return this.request({ method: 'delete', url, options });
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Default configuration
|
|
878
|
+
const DEFAULT_CONFIG = {
|
|
879
|
+
apiBaseUrl: '',
|
|
880
|
+
apiTimeout: 30000,
|
|
881
|
+
retryAttempts: 2,
|
|
882
|
+
retryDelay: 1000,
|
|
883
|
+
enableCorrelationTracking: true,
|
|
884
|
+
enableRequestLogging: false,
|
|
885
|
+
enableErrorLogging: true,
|
|
886
|
+
enableToastNotifications: true,
|
|
887
|
+
includeAuthToken: true,
|
|
888
|
+
authTokenHeader: 'Authorization',
|
|
889
|
+
enableMultiTenancy: true,
|
|
890
|
+
tenantIdHeader: 'Tenant-Id',
|
|
891
|
+
logLevel: 'info',
|
|
892
|
+
enableConsoleLogging: true,
|
|
893
|
+
customHeaders: {},
|
|
894
|
+
excludeUrls: [],
|
|
895
|
+
excludeMethods: [],
|
|
896
|
+
};
|
|
897
|
+
// Injection token for configuration
|
|
898
|
+
const CORE_CONFIG = new InjectionToken('CORE_CONFIG', {
|
|
899
|
+
factory: () => DEFAULT_CONFIG,
|
|
900
|
+
});
|
|
901
|
+
class CoreConfigService {
|
|
902
|
+
config;
|
|
903
|
+
environment = inject(ENVIRONMENT);
|
|
904
|
+
constructor() {
|
|
905
|
+
// Initialize with default config and environment overrides
|
|
906
|
+
this.config = this.initializeConfig();
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Initialize configuration with defaults and environment overrides
|
|
910
|
+
*/
|
|
911
|
+
initializeConfig() {
|
|
912
|
+
const injectedConfig = inject(CORE_CONFIG, { optional: true });
|
|
913
|
+
return {
|
|
914
|
+
...DEFAULT_CONFIG,
|
|
915
|
+
// Environment overrides
|
|
916
|
+
apiBaseUrl: this.environment.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl,
|
|
917
|
+
enableRequestLogging: !this.environment.isProduction,
|
|
918
|
+
// Injected config overrides
|
|
919
|
+
...injectedConfig,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Get the current configuration
|
|
924
|
+
*/
|
|
925
|
+
getConfig() {
|
|
926
|
+
return { ...this.config };
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Update configuration at runtime
|
|
930
|
+
*/
|
|
931
|
+
updateConfig(updates) {
|
|
932
|
+
this.config = { ...this.config, ...updates };
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Get a specific configuration value
|
|
936
|
+
*/
|
|
937
|
+
get(key) {
|
|
938
|
+
return this.config[key];
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Check if a feature is enabled
|
|
942
|
+
*/
|
|
943
|
+
isFeatureEnabled(feature) {
|
|
944
|
+
const value = this.config[feature];
|
|
945
|
+
return typeof value === 'boolean' ? value : false;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Get API URL for a specific entity
|
|
949
|
+
*/
|
|
950
|
+
getApiUrl(entityName) {
|
|
951
|
+
const baseUrl = this.config.apiBaseUrl;
|
|
952
|
+
if (!entityName)
|
|
953
|
+
return baseUrl;
|
|
954
|
+
return `${baseUrl}/${entityName}`.replace(/\/+/g, '/');
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Check if a URL should be excluded from processing
|
|
958
|
+
*/
|
|
959
|
+
shouldExcludeUrl(url) {
|
|
960
|
+
return this.config.excludeUrls.some(excludePattern => url.includes(excludePattern) || new RegExp(excludePattern).test(url));
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Check if a method should be excluded from processing
|
|
964
|
+
*/
|
|
965
|
+
shouldExcludeMethod(method) {
|
|
966
|
+
return this.config.excludeMethods.some(excludeMethod => excludeMethod.toLowerCase() === method.toLowerCase());
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Get custom headers with dynamic values resolved
|
|
970
|
+
*/
|
|
971
|
+
getCustomHeaders() {
|
|
972
|
+
const headers = {};
|
|
973
|
+
Object.entries(this.config.customHeaders).forEach(([key, value]) => {
|
|
974
|
+
headers[key] = typeof value === 'function' ? value() : value;
|
|
975
|
+
});
|
|
976
|
+
return headers;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Reset configuration to defaults
|
|
980
|
+
*/
|
|
981
|
+
resetConfig() {
|
|
982
|
+
this.config = this.initializeConfig();
|
|
983
|
+
}
|
|
984
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CoreConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
985
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CoreConfigService, providedIn: 'root' });
|
|
986
|
+
}
|
|
987
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CoreConfigService, decorators: [{
|
|
988
|
+
type: Injectable,
|
|
989
|
+
args: [{
|
|
990
|
+
providedIn: 'root',
|
|
991
|
+
}]
|
|
992
|
+
}], ctorParameters: () => [] });
|
|
993
|
+
// Provider function for easy setup
|
|
994
|
+
function provideCoreConfig(config = {}) {
|
|
995
|
+
return [
|
|
996
|
+
{
|
|
997
|
+
provide: CORE_CONFIG,
|
|
998
|
+
useValue: config,
|
|
999
|
+
},
|
|
1000
|
+
];
|
|
1001
|
+
}
|
|
1002
|
+
// Helper function to create configuration
|
|
1003
|
+
function createCoreConfig(overrides = {}) {
|
|
1004
|
+
return {
|
|
1005
|
+
...DEFAULT_CONFIG,
|
|
1006
|
+
...overrides,
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Generated bundle index. Do not edit.
|
|
1012
|
+
*/
|
|
1013
|
+
|
|
1014
|
+
export { ActiveRequestsTracker, AngularHttpAdapter, BaseHttpRepository, CORE_CONFIG, CoreConfigService, CorrelationService, GenericRepository, HTTP_CONTEXT_CONFIG, LoggingService, REPOSITORY_CONFIG, RepositoryFactory, SHOW_NOTIFICATIONS, SKIP_NOTIFICATION, SearchableGenericRepository, TOKEN_PROVIDER, UserRepository, apiInterceptor, createCoreConfig, createHttpContextConfig, customUrl, httpContextInterceptor, provideCoreConfig, provideHttpContext, skipContextHeaders, spinnerInterceptor, withCustomHeaders, withoutSpinner };
|
|
1015
|
+
//# sourceMappingURL=acontplus-ng-infrastructure.mjs.map
|