@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