@flusys/ng-core 1.1.1-beta → 1.2.0-rc.1
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.
- package/README.md +90 -500
- package/fesm2022/flusys-ng-core.mjs +66 -138
- package/fesm2022/flusys-ng-core.mjs.map +1 -1
- package/package.json +6 -6
- package/types/flusys-ng-core.d.ts +69 -116
package/README.md
CHANGED
|
@@ -1,563 +1,153 @@
|
|
|
1
|
-
# @flusys/ng-core
|
|
1
|
+
# @flusys/ng-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Foundation library for the FLUSYS Angular platform — app configuration token, base HTTP service, loader service, and HTTP interceptors.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Key Principle:** ng-core has NO internal dependencies on other @flusys packages. All other packages depend on ng-core.
|
|
8
|
-
|
|
9
|
-
## Package Information
|
|
10
|
-
|
|
11
|
-
- **Package:** `@flusys/ng-core`
|
|
12
|
-
- **Dependencies:** None (base package)
|
|
13
|
-
- **Dependents:** ng-shared, ng-layout, ng-auth, ng-iam, ng-storage, flusysng
|
|
14
|
-
- **Build Command:** `npm run build:ng-core`
|
|
15
|
-
|
|
16
|
-
## Package Structure
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
ng-core/
|
|
20
|
-
├── components/
|
|
21
|
-
│ └── lib-app-config/
|
|
22
|
-
│ ├── lib-app-config.component.ts
|
|
23
|
-
│ ├── lib-app-config.component.html
|
|
24
|
-
│ └── lib-app-config.component.scss
|
|
25
|
-
├── interceptors/
|
|
26
|
-
│ ├── api-loader.interceptor.ts
|
|
27
|
-
│ └── error-catching.interceptor.ts
|
|
28
|
-
├── interfaces/
|
|
29
|
-
│ └── app-config.interface.ts
|
|
30
|
-
├── services/
|
|
31
|
-
│ ├── api-loader.service.ts
|
|
32
|
-
│ └── base-api.service.ts
|
|
33
|
-
└── public-api.ts
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Public API Exports
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// Components
|
|
40
|
-
export * from './components/lib-app-config/lib-app-config.component';
|
|
41
|
-
|
|
42
|
-
// Interceptors
|
|
43
|
-
export * from './interceptors/api-loader.interceptor';
|
|
44
|
-
export * from './interceptors/error-catching.interceptor';
|
|
45
|
-
|
|
46
|
-
// Interfaces
|
|
47
|
-
export * from './interfaces/app-config.interface';
|
|
48
|
-
|
|
49
|
-
// Services
|
|
50
|
-
export * from './services/api-loader.service';
|
|
51
|
-
export * from './services/base-api.service';
|
|
52
|
-
```
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/ng-core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
53
7
|
|
|
54
8
|
---
|
|
55
9
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
### 1. APP_CONFIG (Configuration Management)
|
|
59
|
-
|
|
60
|
-
Centralized configuration system with dynamic service URLs and feature flags.
|
|
61
|
-
|
|
62
|
-
#### IAppConfig Interface
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
export type PermissionMode = 'rbac' | 'direct' | 'full';
|
|
66
|
-
|
|
67
|
-
export interface IServiceConfig {
|
|
68
|
-
baseUrl: string;
|
|
69
|
-
enabled: boolean;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface IAuthorConfig {
|
|
73
|
-
name: string;
|
|
74
|
-
url: string;
|
|
75
|
-
}
|
|
10
|
+
## Installation
|
|
76
11
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
author?: IAuthorConfig; // Author/Brand info for footer attribution
|
|
80
|
-
apiBaseUrl: string; // Default fallback URL
|
|
81
|
-
enableCompanyFeature: boolean; // Company/branch selection
|
|
82
|
-
|
|
83
|
-
services: {
|
|
84
|
-
auth: IServiceConfig;
|
|
85
|
-
iam: IServiceConfig;
|
|
86
|
-
storage: IServiceConfig;
|
|
87
|
-
[key: string]: IServiceConfig; // Extensible for custom services
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
multiTenant: {
|
|
91
|
-
enabled: boolean; // true = multi-tenant DB, false = single DB
|
|
92
|
-
tenantHeader: string; // Header name (e.g., 'x-tenant-id')
|
|
93
|
-
tenantId?: string; // Optional tenant ID value
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
permissionMode?: PermissionMode; // Defaults to 'full'
|
|
97
|
-
}
|
|
12
|
+
```bash
|
|
13
|
+
npm install @flusys/ng-core
|
|
98
14
|
```
|
|
99
15
|
|
|
100
|
-
|
|
16
|
+
---
|
|
101
17
|
|
|
102
|
-
|
|
103
|
-
export const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');
|
|
104
|
-
```
|
|
18
|
+
## 1. Register APP_CONFIG and Interceptors
|
|
105
19
|
|
|
106
|
-
|
|
20
|
+
Provide the config token and HTTP interceptors once in `app.config.ts`. Every FLUSYS package reads from `APP_CONFIG`.
|
|
107
21
|
|
|
108
22
|
```typescript
|
|
109
|
-
import {
|
|
110
|
-
import {
|
|
23
|
+
import { ApplicationConfig } from "@angular/core";
|
|
24
|
+
import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http";
|
|
25
|
+
import { APP_CONFIG, errorCatchingInterceptor, apiLoaderInterceptor } from "@flusys/ng-core";
|
|
111
26
|
|
|
112
27
|
export const appConfig: ApplicationConfig = {
|
|
113
28
|
providers: [
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
29
|
+
{
|
|
30
|
+
provide: APP_CONFIG,
|
|
31
|
+
useValue: {
|
|
32
|
+
appName: "My App",
|
|
33
|
+
appLogo: "/assets/logo.png",
|
|
34
|
+
apiBaseUrl: "http://localhost:2002",
|
|
35
|
+
production: false,
|
|
36
|
+
permissionMode: "FULL", // 'RBAC' | 'DIRECT' | 'FULL'
|
|
37
|
+
multiTenant: false,
|
|
38
|
+
services: {
|
|
39
|
+
auth: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
features: {
|
|
42
|
+
signUp: true,
|
|
43
|
+
emailVerification: true,
|
|
44
|
+
companySelection: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
iam: { enabled: true },
|
|
48
|
+
storage: { enabled: true },
|
|
49
|
+
formBuilder: { enabled: false },
|
|
50
|
+
email: { enabled: false },
|
|
51
|
+
eventManager: { enabled: false },
|
|
52
|
+
notification: { enabled: false, socketUrl: "ws://localhost:2002" }, // optional
|
|
53
|
+
localization: { enabled: false },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
provideHttpClient(
|
|
58
|
+
withFetch(),
|
|
59
|
+
withInterceptors([
|
|
60
|
+
// authInterceptor, // from @flusys/ng-auth — add first
|
|
61
|
+
// tokenRefreshInterceptor, // from @flusys/ng-auth — add second
|
|
62
|
+
errorCatchingInterceptor, // shows toast on non-401/403 errors
|
|
63
|
+
apiLoaderInterceptor, // drives global progress bar
|
|
64
|
+
]),
|
|
65
|
+
),
|
|
66
|
+
],
|
|
143
67
|
};
|
|
144
68
|
```
|
|
145
69
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
import {
|
|
150
|
-
getDatabaseMode,
|
|
151
|
-
isFeatureEnabled,
|
|
152
|
-
getServiceUrl,
|
|
153
|
-
isServiceEnabled,
|
|
154
|
-
isCompanyFeatureEnabled,
|
|
155
|
-
isStorageFeatureEnabled,
|
|
156
|
-
getPermissionMode,
|
|
157
|
-
} from '@flusys/ng-core';
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
| Function | Signature | Description |
|
|
161
|
-
|----------|-----------|-------------|
|
|
162
|
-
| `getDatabaseMode` | `(config) → 'single' \| 'multi-tenant'` | Get database mode from `multiTenant.enabled` |
|
|
163
|
-
| `isFeatureEnabled` | `(config, feature: 'auth' \| 'iam' \| 'storage') → boolean` | Check if a service feature is enabled |
|
|
164
|
-
| `getServiceUrl` | `(config, serviceName) → string` | Get service URL or fallback to `apiBaseUrl` |
|
|
165
|
-
| `isServiceEnabled` | `(config, serviceName) → boolean` | Check if a service is enabled |
|
|
166
|
-
| `isCompanyFeatureEnabled` | `(config) → boolean` | Check `enableCompanyFeature` flag |
|
|
167
|
-
| `isStorageFeatureEnabled` | `(config) → boolean` | Check `services.storage.enabled` |
|
|
168
|
-
| `getPermissionMode` | `(config) → PermissionMode` | Get permission mode (defaults to `'full'`) |
|
|
70
|
+
Add `<lib-app-config />` once in your app root template. It renders the global toast, confirm dialog, and progress bar.
|
|
169
71
|
|
|
170
|
-
|
|
72
|
+
---
|
|
171
73
|
|
|
172
|
-
|
|
173
|
-
export const DEFAULT_APP_NAME = 'FLUSYS';
|
|
174
|
-
export const DEFAULT_AUTHOR: IAuthorConfig = {
|
|
175
|
-
name: 'Flusys',
|
|
176
|
-
url: 'https://flusys.vercel.app',
|
|
177
|
-
};
|
|
178
|
-
```
|
|
74
|
+
## 2. BaseApiService
|
|
179
75
|
|
|
180
|
-
|
|
76
|
+
Extend to create a typed service for any POST-only RPC endpoint. The base URL is derived from `APP_CONFIG.apiBaseUrl`.
|
|
181
77
|
|
|
182
|
-
**Mode 1: Simple (No Company, No IAM)**
|
|
183
78
|
```typescript
|
|
184
|
-
{
|
|
185
|
-
|
|
186
|
-
multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
|
|
187
|
-
services: {
|
|
188
|
-
auth: { baseUrl: '...', enabled: false },
|
|
189
|
-
iam: { baseUrl: '...', enabled: false },
|
|
190
|
-
storage: { baseUrl: '...', enabled: false },
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
79
|
+
import { Injectable } from "@angular/core";
|
|
80
|
+
import { BaseApiService } from "@flusys/ng-core";
|
|
194
81
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{
|
|
198
|
-
|
|
199
|
-
multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
|
|
200
|
-
services: {
|
|
201
|
-
auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
|
|
202
|
-
iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
|
|
203
|
-
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
|
|
82
|
+
@Injectable({ providedIn: "root" })
|
|
83
|
+
export class ProductService extends BaseApiService {
|
|
84
|
+
getAll() {
|
|
85
|
+
return this.http.post(`${this.baseUrl}/products/get-all`, {});
|
|
204
86
|
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
87
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
enableCompanyFeature: true,
|
|
212
|
-
multiTenant: { enabled: true, tenantHeader: 'x-tenant-id', tenantId: 'tenant-123' },
|
|
213
|
-
services: {
|
|
214
|
-
auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
|
|
215
|
-
iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
|
|
216
|
-
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
|
|
88
|
+
findById(id: string) {
|
|
89
|
+
return this.http.post(this.getUrl(`products/get/${id}`), {});
|
|
217
90
|
}
|
|
218
91
|
}
|
|
219
92
|
```
|
|
220
93
|
|
|
221
94
|
---
|
|
222
95
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
ng-core provides two functional interceptors. Both use the modern Angular functional interceptor pattern.
|
|
226
|
-
|
|
227
|
-
#### apiLoaderInterceptor
|
|
228
|
-
|
|
229
|
-
Tracks HTTP request loading state with tagged loaders. Uses a custom `x-loader-tag` header to group related requests.
|
|
230
|
-
|
|
231
|
-
**Behavior:**
|
|
232
|
-
1. Reads `x-loader-tag` header from request (defaults to `'default'`)
|
|
233
|
-
2. Increments loader count for that tag
|
|
234
|
-
3. Removes `x-loader-tag` header before sending to backend
|
|
235
|
-
4. Decrements loader count on request completion (success or error)
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
import { apiLoaderInterceptor } from '@flusys/ng-core';
|
|
239
|
-
|
|
240
|
-
// Tag a specific request
|
|
241
|
-
this.http.post('/api/users/get-all', filter, {
|
|
242
|
-
headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Check loading state for that tag
|
|
246
|
-
apiLoaderService.getLoader('users-list')(); // true while loading
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
#### errorCatchingInterceptor
|
|
250
|
-
|
|
251
|
-
Displays user-friendly error toast messages for failed HTTP requests via PrimeNG `MessageService`.
|
|
252
|
-
|
|
253
|
-
**Behavior:**
|
|
254
|
-
- Skips 401 errors (handled by `tokenRefreshInterceptor` in ng-auth)
|
|
255
|
-
- Skips `/check-login` endpoint errors (expected to fail when not logged in)
|
|
256
|
-
- Extracts message from `error.error.message` (string or array), `error.message`, or fallback
|
|
257
|
-
- SSR-safe: only displays messages in browser (`isPlatformBrowser` check)
|
|
258
|
-
|
|
259
|
-
#### Interceptor Chain
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
import { errorCatchingInterceptor, apiLoaderInterceptor } from '@flusys/ng-core';
|
|
263
|
-
import { authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
|
|
264
|
-
|
|
265
|
-
provideHttpClient(
|
|
266
|
-
withFetch(),
|
|
267
|
-
withInterceptors([
|
|
268
|
-
authInterceptor, // from @flusys/ng-auth (adds auth + tenant headers)
|
|
269
|
-
tokenRefreshInterceptor, // from @flusys/ng-auth (handles 401 + token refresh)
|
|
270
|
-
errorCatchingInterceptor, // from @flusys/ng-core (displays error toasts)
|
|
271
|
-
apiLoaderInterceptor, // from @flusys/ng-core (tracks loading state)
|
|
272
|
-
])
|
|
273
|
-
)
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
**Order matters:** Auth interceptors first, then error handling, then loader tracking.
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
### 3. Services
|
|
281
|
-
|
|
282
|
-
#### ApiLoaderService
|
|
283
|
-
|
|
284
|
-
Signal-based loading state management with tagged loaders. Provided at root level.
|
|
285
|
-
|
|
286
|
-
**Signals:**
|
|
287
|
-
|
|
288
|
-
| Signal | Type | Description |
|
|
289
|
-
|--------|------|-------------|
|
|
290
|
-
| `show` | `Signal<boolean>` | `true` if ANY loader tag has active requests |
|
|
291
|
-
|
|
292
|
-
**Methods:**
|
|
96
|
+
## 3. ApiLoaderService — Global Progress Bar
|
|
293
97
|
|
|
294
|
-
|
|
295
|
-
|--------|-----------|-------------|
|
|
296
|
-
| `increase(name?)` | `name: string = 'default'` | Increment loader count for a tag |
|
|
297
|
-
| `decrease(name?)` | `name: string = 'default'` | Decrement loader count for a tag (min 0) |
|
|
298
|
-
| `getLoader(name)` | `name: string` | Get computed `Signal<boolean>` for a specific tag |
|
|
299
|
-
|
|
300
|
-
**Properties:**
|
|
301
|
-
|
|
302
|
-
| Property | Type | Description |
|
|
303
|
-
|----------|------|-------------|
|
|
304
|
-
| `DEFAULT_TAG` | `string` | Default tag name (`'default'`) |
|
|
305
|
-
|
|
306
|
-
**Usage:**
|
|
98
|
+
`apiLoaderInterceptor` automatically increments/decrements a reference count per request. `show` is `true` while any request is in flight.
|
|
307
99
|
|
|
308
100
|
```typescript
|
|
309
|
-
|
|
310
|
-
@if (apiLoaderService.show()) {
|
|
311
|
-
<p-progressBar mode="indeterminate" />
|
|
312
|
-
}
|
|
101
|
+
import { ApiLoaderService } from '@flusys/ng-core';
|
|
313
102
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
<p>Loading users...</p>
|
|
317
|
-
}
|
|
103
|
+
readonly loader = inject(ApiLoaderService);
|
|
104
|
+
// loader.show — Signal<boolean>
|
|
318
105
|
```
|
|
319
106
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
Abstract base class for API services. Provides automatic URL resolution from APP_CONFIG.
|
|
323
|
-
|
|
324
|
-
**Protected members:**
|
|
325
|
-
|
|
326
|
-
| Member | Type | Description |
|
|
327
|
-
|--------|------|-------------|
|
|
328
|
-
| `http` | `HttpClient` | Injected HTTP client |
|
|
329
|
-
| `appConfig` | `IAppConfig` | Injected app configuration |
|
|
330
|
-
| `baseUrl` | `string` | Resolved base URL for the service |
|
|
331
|
-
|
|
332
|
-
**Protected methods:**
|
|
333
|
-
|
|
334
|
-
| Method | Parameters | Returns | Description |
|
|
335
|
-
|--------|-----------|---------|-------------|
|
|
336
|
-
| `getUrl(endpoint)` | `endpoint: string` | `string` | Build full URL, removes leading slash to avoid double slashes |
|
|
337
|
-
|
|
338
|
-
**Usage:**
|
|
107
|
+
Tag requests to get per-section loading signals:
|
|
339
108
|
|
|
340
109
|
```typescript
|
|
341
|
-
|
|
342
|
-
export class UserApiService extends BaseApiService {
|
|
343
|
-
constructor() {
|
|
344
|
-
super('auth'); // Resolves to services.auth.baseUrl
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
getAll(filter: any) {
|
|
348
|
-
return this.http.post(this.getUrl('user/get-all'), filter);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
getById(id: string) {
|
|
352
|
-
return this.http.post(`${this.getUrl('user/get')}/${id}`, {});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
---
|
|
358
|
-
|
|
359
|
-
### 4. Components
|
|
360
|
-
|
|
361
|
-
#### LibAppConfigComponent
|
|
362
|
-
|
|
363
|
-
Root configuration component providing global UI elements. Include once at app root.
|
|
364
|
-
|
|
365
|
-
**Selector:** `lib-app-config`
|
|
110
|
+
import { HttpHeaders } from "@angular/common/http";
|
|
366
111
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
- Confirm dialogs (PrimeNG ConfirmDialog)
|
|
371
|
-
|
|
372
|
-
**Template:**
|
|
373
|
-
```html
|
|
374
|
-
@if (apiLoaderService.show()) {
|
|
375
|
-
<p-progressBar mode="indeterminate" />
|
|
376
|
-
}
|
|
377
|
-
<p-toast />
|
|
378
|
-
<p-confirmdialog />
|
|
379
|
-
```
|
|
112
|
+
this.http.post(url, body, {
|
|
113
|
+
headers: new HttpHeaders({ "x-loader-tag": "user-list" }),
|
|
114
|
+
});
|
|
380
115
|
|
|
381
|
-
|
|
382
|
-
```typescript
|
|
383
|
-
import { LibAppConfigComponent } from '@flusys/ng-core';
|
|
384
|
-
|
|
385
|
-
@Component({
|
|
386
|
-
selector: 'app-root',
|
|
387
|
-
standalone: true,
|
|
388
|
-
imports: [LibAppConfigComponent, RouterOutlet],
|
|
389
|
-
template: `
|
|
390
|
-
<lib-app-config />
|
|
391
|
-
<router-outlet />
|
|
392
|
-
`
|
|
393
|
-
})
|
|
394
|
-
export class AppComponent {}
|
|
116
|
+
const isUserListLoading = this.loader.getLoader("user-list"); // Signal<boolean>
|
|
395
117
|
```
|
|
396
118
|
|
|
397
119
|
---
|
|
398
120
|
|
|
399
|
-
##
|
|
400
|
-
|
|
401
|
-
### Basic App Setup
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
// app.config.ts
|
|
405
|
-
import { ApplicationConfig } from '@angular/core';
|
|
406
|
-
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
|
|
407
|
-
import { APP_CONFIG, errorCatchingInterceptor, apiLoaderInterceptor } from '@flusys/ng-core';
|
|
408
|
-
import { authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
|
|
409
|
-
import { DEFAULT_APP_CONFIG } from './config/app.config';
|
|
410
|
-
|
|
411
|
-
export const appConfig: ApplicationConfig = {
|
|
412
|
-
providers: [
|
|
413
|
-
provideHttpClient(
|
|
414
|
-
withFetch(),
|
|
415
|
-
withInterceptors([
|
|
416
|
-
authInterceptor,
|
|
417
|
-
tokenRefreshInterceptor,
|
|
418
|
-
errorCatchingInterceptor,
|
|
419
|
-
apiLoaderInterceptor,
|
|
420
|
-
])
|
|
421
|
-
),
|
|
422
|
-
{ provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
|
|
423
|
-
]
|
|
424
|
-
};
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Creating an API Service
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
import { Injectable } from '@angular/core';
|
|
431
|
-
import { BaseApiService } from '@flusys/ng-core';
|
|
432
|
-
|
|
433
|
-
@Injectable({ providedIn: 'root' })
|
|
434
|
-
export class AuthApiService extends BaseApiService {
|
|
435
|
-
constructor() {
|
|
436
|
-
super('auth'); // Resolves to services.auth.baseUrl
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
login(credentials: any) {
|
|
440
|
-
return this.http.post(this.getUrl('auth/login'), credentials);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
```
|
|
121
|
+
## 4. Skip Error Toast Per Request
|
|
444
122
|
|
|
445
|
-
|
|
123
|
+
Opt out of the global error toast for requests you handle locally:
|
|
446
124
|
|
|
447
125
|
```typescript
|
|
448
|
-
import {
|
|
449
|
-
import {
|
|
126
|
+
import { SKIP_ERROR_TOAST } from "@flusys/ng-core";
|
|
127
|
+
import { HttpContext } from "@angular/common/http";
|
|
450
128
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
|
|
129
|
+
this.http.post(url, body, {
|
|
130
|
+
context: new HttpContext().set(SKIP_ERROR_TOAST, true),
|
|
454
131
|
});
|
|
455
|
-
|
|
456
|
-
// In a component - check specific loader
|
|
457
|
-
readonly usersLoading = this.apiLoaderService.getLoader('users-list');
|
|
458
|
-
|
|
459
|
-
// In template
|
|
460
|
-
@if (usersLoading()) {
|
|
461
|
-
<p-skeleton />
|
|
462
|
-
}
|
|
463
132
|
```
|
|
464
133
|
|
|
465
134
|
---
|
|
466
135
|
|
|
467
|
-
##
|
|
136
|
+
## 6. Feature Helper Functions
|
|
468
137
|
|
|
469
|
-
|
|
470
|
-
2. **Match backend configuration mode** (`multiTenant.enabled` should align with FLUSYS_NEST setup)
|
|
471
|
-
3. **Enable features based on backend availability** (`services.iam.enabled` only if IAM module is deployed)
|
|
472
|
-
4. **Never import from other @flusys packages** in ng-core
|
|
473
|
-
5. **Keep ng-core minimal** - only foundation functionality
|
|
474
|
-
6. **Use `BaseApiService`** for all API services to avoid hardcoded URLs
|
|
475
|
-
7. **Don't create custom error handling** - use `errorCatchingInterceptor`
|
|
476
|
-
8. **Don't manually track loading** - use `apiLoaderInterceptor` with `x-loader-tag` headers
|
|
138
|
+
Read flags without injecting `APP_CONFIG` directly:
|
|
477
139
|
|
|
478
|
-
## Common Issues
|
|
479
|
-
|
|
480
|
-
### APP_CONFIG Not Found
|
|
481
|
-
|
|
482
|
-
**Problem:** `NullInjectorError: No provider for InjectionToken APP_CONFIG`
|
|
483
|
-
|
|
484
|
-
**Solution:** Provide APP_CONFIG in app.config.ts:
|
|
485
140
|
```typescript
|
|
486
|
-
{
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### Circular Dependency
|
|
141
|
+
import { isSignUpEnabled, getPermissionMode, getNotificationSocketUrl } from "@flusys/ng-core";
|
|
490
142
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
143
|
+
const config = inject(APP_CONFIG);
|
|
144
|
+
isSignUpEnabled(config); // boolean — reads services.auth.features.signUp
|
|
145
|
+
getPermissionMode(config); // 'RBAC' | 'DIRECT' | 'FULL'
|
|
146
|
+
getNotificationSocketUrl(config); // string | undefined
|
|
147
|
+
```
|
|
494
148
|
|
|
495
149
|
---
|
|
496
150
|
|
|
497
|
-
##
|
|
498
|
-
|
|
499
|
-
### Tokens
|
|
500
|
-
|
|
501
|
-
| Token | Type | Description |
|
|
502
|
-
|-------|------|-------------|
|
|
503
|
-
| `APP_CONFIG` | `InjectionToken<IAppConfig>` | Application configuration |
|
|
504
|
-
|
|
505
|
-
### Interceptors
|
|
506
|
-
|
|
507
|
-
| Function | Description |
|
|
508
|
-
|----------|-------------|
|
|
509
|
-
| `apiLoaderInterceptor` | Loading state management with tagged loaders |
|
|
510
|
-
| `errorCatchingInterceptor` | Error toast display (skips 401 and check-login) |
|
|
511
|
-
|
|
512
|
-
### Services
|
|
513
|
-
|
|
514
|
-
| Service | Description |
|
|
515
|
-
|---------|-------------|
|
|
516
|
-
| `ApiLoaderService` | Signal-based loading state with tagged loaders |
|
|
517
|
-
| `BaseApiService` | Abstract base class for API services with URL resolution |
|
|
518
|
-
|
|
519
|
-
### Interfaces & Types
|
|
520
|
-
|
|
521
|
-
| Export | Description |
|
|
522
|
-
|--------|-------------|
|
|
523
|
-
| `IAppConfig` | App configuration interface |
|
|
524
|
-
| `IServiceConfig` | Service configuration (`baseUrl`, `enabled`) |
|
|
525
|
-
| `IAuthorConfig` | Author/brand configuration (`name`, `url`) |
|
|
526
|
-
| `PermissionMode` | `'rbac' \| 'direct' \| 'full'` |
|
|
527
|
-
|
|
528
|
-
### Helper Functions
|
|
529
|
-
|
|
530
|
-
| Function | Description |
|
|
531
|
-
|----------|-------------|
|
|
532
|
-
| `getDatabaseMode(config)` | Returns `'single'` or `'multi-tenant'` |
|
|
533
|
-
| `isFeatureEnabled(config, feature)` | Check service feature enabled (`'auth' \| 'iam' \| 'storage'`) |
|
|
534
|
-
| `getServiceUrl(config, serviceName)` | Get service URL with fallback |
|
|
535
|
-
| `isServiceEnabled(config, serviceName)` | Check if service is enabled |
|
|
536
|
-
| `isCompanyFeatureEnabled(config)` | Check company feature flag |
|
|
537
|
-
| `isStorageFeatureEnabled(config)` | Check storage service enabled |
|
|
538
|
-
| `getPermissionMode(config)` | Get permission mode (default `'full'`) |
|
|
539
|
-
|
|
540
|
-
### Constants
|
|
541
|
-
|
|
542
|
-
| Constant | Value | Description |
|
|
543
|
-
|----------|-------|-------------|
|
|
544
|
-
| `DEFAULT_APP_NAME` | `'FLUSYS'` | Default application name |
|
|
545
|
-
| `DEFAULT_AUTHOR` | `{ name: 'Flusys', url: '...' }` | Default author/brand info |
|
|
546
|
-
|
|
547
|
-
### Components
|
|
548
|
-
|
|
549
|
-
| Component | Selector | Description |
|
|
550
|
-
|-----------|----------|-------------|
|
|
551
|
-
| `LibAppConfigComponent` | `lib-app-config` | Global toast, confirm dialog, and loading bar |
|
|
552
|
-
|
|
553
|
-
## See Also
|
|
554
|
-
|
|
555
|
-
- **[SHARED-GUIDE.md](./SHARED-GUIDE.md)** - API services, components, directives
|
|
556
|
-
- **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Layout system
|
|
557
|
-
- **[Angular Patterns](../../.claude/docs/angular/)** - Framework patterns
|
|
558
|
-
- **[FLUSYS_NEST/docs/CORE-GUIDE.md](../../FLUSYS_NEST/docs/CORE-GUIDE.md)** - Backend core module
|
|
559
|
-
|
|
560
|
-
---
|
|
151
|
+
## License
|
|
561
152
|
|
|
562
|
-
|
|
563
|
-
**Angular Version:** 21
|
|
153
|
+
MIT © FLUSYS
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, inject, Component,
|
|
3
|
-
import * as
|
|
4
|
-
import {
|
|
5
|
-
import { ConfirmDialog } from 'primeng/confirmdialog';
|
|
6
|
-
import * as i1 from 'primeng/progressbar';
|
|
7
|
-
import { ProgressBarModule } from 'primeng/progressbar';
|
|
2
|
+
import { signal, computed, Injectable, inject, Component, InjectionToken } from '@angular/core';
|
|
3
|
+
import * as i1 from '@flusys/ng-ui';
|
|
4
|
+
import { ConfirmDialog, UiModule } from '@flusys/ng-ui';
|
|
8
5
|
import { finalize } from 'rxjs/operators';
|
|
9
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
10
|
-
import { MessageService } from 'primeng/api';
|
|
11
|
-
import { catchError, throwError } from 'rxjs';
|
|
12
6
|
import { HttpClient } from '@angular/common/http';
|
|
13
7
|
|
|
14
8
|
class ApiLoaderService {
|
|
15
|
-
loaders = signal({},
|
|
9
|
+
loaders = signal({}, /* @ts-ignore */
|
|
10
|
+
...(ngDevMode ? [{ debugName: "loaders" }] : /* istanbul ignore next */ []));
|
|
16
11
|
DEFAULT_TAG = 'default';
|
|
17
12
|
// Global loading state
|
|
18
13
|
show = computed(() => {
|
|
19
14
|
const loaders = this.loaders();
|
|
20
15
|
return Object.values(loaders).some((count) => count > 0);
|
|
21
|
-
},
|
|
16
|
+
}, /* @ts-ignore */
|
|
17
|
+
...(ngDevMode ? [{ debugName: "show" }] : /* istanbul ignore next */ []));
|
|
22
18
|
// Get specific loader as a signal
|
|
23
19
|
getLoader(name) {
|
|
24
20
|
return computed(() => (this.loaders()[name] ?? 0) > 0);
|
|
@@ -35,26 +31,38 @@ class ApiLoaderService {
|
|
|
35
31
|
return { ...loaders, [name]: Math.max(0, current - 1) };
|
|
36
32
|
});
|
|
37
33
|
}
|
|
38
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
39
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
34
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ApiLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
35
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ApiLoaderService, providedIn: 'root' });
|
|
40
36
|
}
|
|
41
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
37
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ApiLoaderService, decorators: [{
|
|
42
38
|
type: Injectable,
|
|
43
39
|
args: [{ providedIn: 'root' }]
|
|
44
40
|
}] });
|
|
45
41
|
|
|
46
|
-
/**
|
|
47
|
-
* Root configuration component providing global UI elements.
|
|
48
|
-
* Include once at app root for global toast, confirm dialog, and loader.
|
|
49
|
-
*/
|
|
50
42
|
class LibAppConfigComponent {
|
|
51
43
|
apiLoaderService = inject(ApiLoaderService);
|
|
52
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
53
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
44
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LibAppConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
45
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LibAppConfigComponent, isStandalone: true, selector: "lib-app-config", ngImport: i0, template: `
|
|
46
|
+
@if (apiLoaderService.show()) {
|
|
47
|
+
<f-progressbar mode="indeterminate" />
|
|
48
|
+
}
|
|
49
|
+
<f-toast />
|
|
50
|
+
<f-confirmdialog />
|
|
51
|
+
`, isInline: true, dependencies: [{ kind: "component", type: ConfirmDialog, selector: "f-confirmdialog", inputs: ["key", "icon", "acceptLabel", "rejectLabel", "acceptIcon", "rejectIcon", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "blockScroll", "defaultFocus", "styleClass", "style"] }, { kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.ToastComponent, selector: "f-toast", inputs: ["key", "position"], outputs: ["onClose"] }, { kind: "component", type: i1.ProgressbarComponent, selector: "f-progressbar", inputs: ["value", "mode", "showValue", "unit", "severity", "color", "styleClass"] }] });
|
|
54
52
|
}
|
|
55
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
53
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LibAppConfigComponent, decorators: [{
|
|
56
54
|
type: Component,
|
|
57
|
-
args: [{
|
|
55
|
+
args: [{
|
|
56
|
+
selector: 'lib-app-config',
|
|
57
|
+
imports: [ConfirmDialog, UiModule],
|
|
58
|
+
template: `
|
|
59
|
+
@if (apiLoaderService.show()) {
|
|
60
|
+
<f-progressbar mode="indeterminate" />
|
|
61
|
+
}
|
|
62
|
+
<f-toast />
|
|
63
|
+
<f-confirmdialog />
|
|
64
|
+
`,
|
|
65
|
+
}]
|
|
58
66
|
}] });
|
|
59
67
|
|
|
60
68
|
/**
|
|
@@ -79,146 +87,64 @@ function apiLoaderInterceptor(req, next) {
|
|
|
79
87
|
return next(cleanReq).pipe(finalize(() => loaderService.decrease(loaderTag)));
|
|
80
88
|
}
|
|
81
89
|
|
|
82
|
-
/**
|
|
83
|
-
* Error Catching Interceptor
|
|
84
|
-
* Displays error messages for failed HTTP requests.
|
|
85
|
-
*
|
|
86
|
-
* NOTE: This interceptor only handles error display.
|
|
87
|
-
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* ```typescript
|
|
91
|
-
* // In app.config.ts
|
|
92
|
-
* provideHttpClient(
|
|
93
|
-
* withFetch(),
|
|
94
|
-
* withInterceptors([
|
|
95
|
-
* authInterceptor, // from @flusys/ng-auth
|
|
96
|
-
* tokenRefreshInterceptor, // from @flusys/ng-auth
|
|
97
|
-
* errorCatchingInterceptor, // from @flusys/ng-core
|
|
98
|
-
* apiLoaderInterceptor, // from @flusys/ng-core
|
|
99
|
-
* ])
|
|
100
|
-
* )
|
|
101
|
-
* ```
|
|
102
|
-
*/
|
|
103
|
-
function errorCatchingInterceptor(req, next) {
|
|
104
|
-
const messageService = inject(MessageService);
|
|
105
|
-
const platformId = inject(PLATFORM_ID);
|
|
106
|
-
const isBrowser = isPlatformBrowser(platformId);
|
|
107
|
-
// Skip error display for check-login (expected to fail when not logged in)
|
|
108
|
-
const isCheckLogin = req.url.includes('/check-login');
|
|
109
|
-
return next(req).pipe(catchError((error) => {
|
|
110
|
-
// Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth
|
|
111
|
-
// Skip 403 errors - handled by permission denied UI in components
|
|
112
|
-
// Skip check-login errors - expected behavior
|
|
113
|
-
if (error.status === 401 || error.status === 403 || isCheckLogin) {
|
|
114
|
-
return throwError(() => error);
|
|
115
|
-
}
|
|
116
|
-
// Display error message for other errors
|
|
117
|
-
if (isBrowser) {
|
|
118
|
-
const message = extractErrorMessage(error);
|
|
119
|
-
messageService.add({
|
|
120
|
-
severity: 'error',
|
|
121
|
-
summary: 'Error',
|
|
122
|
-
detail: message,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return throwError(() => error);
|
|
126
|
-
}));
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Extract error message from HTTP error response
|
|
130
|
-
*/
|
|
131
|
-
function extractErrorMessage(error) {
|
|
132
|
-
if (typeof error.error?.message === 'string') {
|
|
133
|
-
return error.error.message;
|
|
134
|
-
}
|
|
135
|
-
if (Array.isArray(error.error?.message)) {
|
|
136
|
-
return error.error.message[0];
|
|
137
|
-
}
|
|
138
|
-
if (error.message) {
|
|
139
|
-
return error.message;
|
|
140
|
-
}
|
|
141
|
-
return 'An unexpected error occurred.';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* App Configuration Token
|
|
146
|
-
* Use this to inject app config in services/components
|
|
147
|
-
*/
|
|
148
90
|
const APP_CONFIG = new InjectionToken('APP_CONFIG');
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
* Derived from multiTenant.enabled
|
|
152
|
-
*/
|
|
153
|
-
function getDatabaseMode(config) {
|
|
154
|
-
return config.multiTenant.enabled ? 'multi-tenant' : 'single';
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Check if a feature is enabled
|
|
158
|
-
* Features are enabled if their corresponding service exists and is enabled
|
|
159
|
-
*/
|
|
160
|
-
function isFeatureEnabled(config, feature) {
|
|
161
|
-
const service = config.services[feature];
|
|
91
|
+
function isServiceEnabled(config, serviceName) {
|
|
92
|
+
const service = config.services[serviceName];
|
|
162
93
|
return service?.enabled ?? false;
|
|
163
94
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Helper function to get service base URL
|
|
166
|
-
* Returns service-specific URL or falls back to default apiBaseUrl
|
|
167
|
-
*/
|
|
168
95
|
function getServiceUrl(config, serviceName) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return service.
|
|
96
|
+
if (isServiceEnabled(config, serviceName)) {
|
|
97
|
+
const service = config.services[serviceName];
|
|
98
|
+
return service?.baseUrl ?? config.apiBaseUrl;
|
|
172
99
|
}
|
|
173
|
-
// Fallback to default API base URL
|
|
174
100
|
return config.apiBaseUrl;
|
|
175
101
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Helper function to check if a service is enabled
|
|
178
|
-
*/
|
|
179
|
-
function isServiceEnabled(config, serviceName) {
|
|
180
|
-
const service = config.services[serviceName];
|
|
181
|
-
return service?.enabled ?? false;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Check if company feature is enabled (backward compatible)
|
|
185
|
-
* Company feature is enabled if auth service is enabled
|
|
186
|
-
*/
|
|
187
102
|
function isCompanyFeatureEnabled(config) {
|
|
188
103
|
return config.enableCompanyFeature ?? false;
|
|
189
104
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return config.services.storage?.enabled ?? false;
|
|
105
|
+
function getNotificationSocketUrl(config) {
|
|
106
|
+
const service = config.services.notification;
|
|
107
|
+
if (!service?.enabled)
|
|
108
|
+
return null;
|
|
109
|
+
return service.socketUrl ?? service.baseUrl;
|
|
196
110
|
}
|
|
197
|
-
/**
|
|
198
|
-
* Get permission mode from config
|
|
199
|
-
* Defaults to 'full' if not specified
|
|
200
|
-
*/
|
|
201
111
|
function getPermissionMode(config) {
|
|
202
112
|
return config.permissionMode ?? 'full';
|
|
203
113
|
}
|
|
204
|
-
/**
|
|
205
|
-
* Get default storage config ID from config
|
|
206
|
-
* Used for file upload operations when no storageConfigId is provided
|
|
207
|
-
*/
|
|
208
114
|
function getDefaultStorageConfigId(config) {
|
|
209
115
|
return config.services.storage?.defaultStorageConfigId;
|
|
210
116
|
}
|
|
211
|
-
|
|
117
|
+
function isSignUpEnabled(config) {
|
|
118
|
+
return config.enableSignUp ?? true;
|
|
119
|
+
}
|
|
120
|
+
function getDefaultCompanySlug(config) {
|
|
121
|
+
return config.registrationDefaults?.companySlug;
|
|
122
|
+
}
|
|
123
|
+
function getDefaultBranchSlug(config) {
|
|
124
|
+
return config.registrationDefaults?.branchSlug;
|
|
125
|
+
}
|
|
212
126
|
const DEFAULT_APP_NAME = 'FLUSYS';
|
|
213
127
|
const DEFAULT_AUTHOR = {
|
|
214
128
|
name: 'Flusys',
|
|
215
129
|
url: 'https://flusys.vercel.app',
|
|
216
130
|
};
|
|
217
131
|
|
|
132
|
+
/** Registry for fallback messages when localization is not active */
|
|
133
|
+
const FALLBACK_MESSAGES_REGISTRY = new InjectionToken('FALLBACK_MESSAGES_REGISTRY');
|
|
134
|
+
// ✅ Provide it once at root as a single shared object
|
|
135
|
+
const provideFallbackMessagesRegistry = () => ({
|
|
136
|
+
provide: FALLBACK_MESSAGES_REGISTRY,
|
|
137
|
+
useValue: {}, // Single object instance shared across all injections
|
|
138
|
+
});
|
|
139
|
+
const LOCALIZATION_STATE_SERVICE = new InjectionToken('LOCALIZATION_STATE_SERVICE');
|
|
140
|
+
const LOCALIZATION_API_SERVICE = new InjectionToken('LOCALIZATION_API_SERVICE');
|
|
141
|
+
|
|
218
142
|
/**
|
|
219
143
|
* Base API Service
|
|
220
144
|
* Provides service-specific base URL resolution
|
|
221
145
|
*
|
|
146
|
+
* Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.
|
|
147
|
+
*
|
|
222
148
|
* Usage:
|
|
223
149
|
* ```typescript
|
|
224
150
|
* @Injectable({ providedIn: 'root' })
|
|
@@ -228,7 +154,7 @@ const DEFAULT_AUTHOR = {
|
|
|
228
154
|
* }
|
|
229
155
|
*
|
|
230
156
|
* getData() {
|
|
231
|
-
* return this.http.
|
|
157
|
+
* return this.http.post(`${this.baseUrl}/data/get`, {});
|
|
232
158
|
* }
|
|
233
159
|
* }
|
|
234
160
|
* ```
|
|
@@ -250,7 +176,9 @@ class BaseApiService {
|
|
|
250
176
|
*/
|
|
251
177
|
getUrl(endpoint) {
|
|
252
178
|
// Remove leading slash if present to avoid double slashes
|
|
253
|
-
const cleanEndpoint = endpoint.startsWith('/')
|
|
179
|
+
const cleanEndpoint = endpoint.startsWith('/')
|
|
180
|
+
? endpoint.substring(1)
|
|
181
|
+
: endpoint;
|
|
254
182
|
return `${this.baseUrl}/${cleanEndpoint}`;
|
|
255
183
|
}
|
|
256
184
|
}
|
|
@@ -261,5 +189,5 @@ class BaseApiService {
|
|
|
261
189
|
* Generated bundle index. Do not edit.
|
|
262
190
|
*/
|
|
263
191
|
|
|
264
|
-
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, apiLoaderInterceptor,
|
|
192
|
+
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE, LibAppConfigComponent, apiLoaderInterceptor, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, provideFallbackMessagesRegistry };
|
|
265
193
|
//# sourceMappingURL=flusys-ng-core.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.html","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interceptors/error-catching.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { Component, inject } from '@angular/core';\nimport { ToastModule } from 'primeng/toast';\nimport { ConfirmDialog } from 'primeng/confirmdialog';\nimport { ApiLoaderService } from '../../services/api-loader.service';\nimport { ProgressBarModule } from 'primeng/progressbar';\n\n/**\n * Root configuration component providing global UI elements.\n * Include once at app root for global toast, confirm dialog, and loader.\n */\n@Component({\n selector: 'lib-app-config',\n standalone: true,\n imports: [ConfirmDialog, ProgressBarModule, ToastModule],\n templateUrl: './lib-app-config.component.html',\n styleUrl: './lib-app-config.component.scss',\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { ApiLoaderService } from '../services/api-loader.service';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\n\n/**\n * Interceptor that tracks loading state for HTTP requests.\n * Uses 'x-loader-tag' header to group related requests.\n *\n * @example\n * ```typescript\n * http.get('/api/users', {\n * headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })\n * });\n * // In template: apiLoaderService.getLoader('users-list')\n * ```\n */\nexport function apiLoaderInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const loaderService = inject(ApiLoaderService);\n const loaderTag = req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;\n\n loaderService.increase(loaderTag);\n\n const cleanReq = req.clone({\n headers: req.headers.delete('x-loader-tag'),\n });\n\n return next(cleanReq).pipe(\n finalize(() => loaderService.decrease(loaderTag))\n );\n}\n","import {\n HttpErrorResponse,\n HttpEvent,\n HttpHandlerFn,\n HttpRequest,\n} from '@angular/common/http';\nimport { inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MessageService } from 'primeng/api';\nimport { catchError, Observable, throwError } from 'rxjs';\n\n/**\n * Error Catching Interceptor\n * Displays error messages for failed HTTP requests.\n *\n * NOTE: This interceptor only handles error display.\n * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.\n *\n * @example\n * ```typescript\n * // In app.config.ts\n * provideHttpClient(\n * withFetch(),\n * withInterceptors([\n * authInterceptor, // from @flusys/ng-auth\n * tokenRefreshInterceptor, // from @flusys/ng-auth\n * errorCatchingInterceptor, // from @flusys/ng-core\n * apiLoaderInterceptor, // from @flusys/ng-core\n * ])\n * )\n * ```\n */\nexport function errorCatchingInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const messageService = inject(MessageService);\n const platformId = inject(PLATFORM_ID);\n const isBrowser = isPlatformBrowser(platformId);\n\n // Skip error display for check-login (expected to fail when not logged in)\n const isCheckLogin = req.url.includes('/check-login');\n\n return next(req).pipe(\n catchError((error: HttpErrorResponse) => {\n // Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth\n // Skip 403 errors - handled by permission denied UI in components\n // Skip check-login errors - expected behavior\n if (error.status === 401 || error.status === 403 || isCheckLogin) {\n return throwError(() => error);\n }\n\n // Display error message for other errors\n if (isBrowser) {\n const message = extractErrorMessage(error);\n messageService.add({\n severity: 'error',\n summary: 'Error',\n detail: message,\n });\n }\n\n return throwError(() => error);\n })\n );\n}\n\n/**\n * Extract error message from HTTP error response\n */\nfunction extractErrorMessage(error: HttpErrorResponse): string {\n if (typeof error.error?.message === 'string') {\n return error.error.message;\n }\n\n if (Array.isArray(error.error?.message)) {\n return error.error.message[0];\n }\n\n if (error.message) {\n return error.message;\n }\n\n return 'An unexpected error occurred.';\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * IAM Permission Mode - controls permission system behavior\n */\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\n/**\n * Service Configuration Interface\n */\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\n/**\n * Storage Service Configuration Interface\n * Extends base service config with storage-specific options\n */\nexport interface IStorageServiceConfig extends IServiceConfig {\n /**\n * Default storage config ID from backend storage_config table\n * Used when no storageConfigId is provided to upload operations\n * If not set, backend will use company/branch default config\n */\n defaultStorageConfigId?: string;\n}\n\n/**\n * Author/Brand Configuration\n */\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\n/**\n * App Configuration Interface\n * Centralized configuration for feature toggles and behavior control\n */\nexport interface IAppConfig {\n /** Application name */\n appName: string;\n\n /** Author/Brand info for footer attribution */\n author?: IAuthorConfig;\n\n /** Default API base URL (fallback) */\n apiBaseUrl: string; \n \n /**\n * enableCompanyFeature in across the app\n */\n enableCompanyFeature: boolean,\n\n /**\n * Service-specific base URLs\n * If a service exists in this object, its feature is enabled\n * Database mode is determined by multiTenant.enabled\n */\n services: {\n auth: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n [key: string]: IServiceConfig | IStorageServiceConfig;\n };\n\n /**\n * Multi-tenant configuration\n * enabled: true → Database mode = multi-tenant\n * enabled: false → Database mode = single\n */\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n\n /**\n * IAM Permission Mode - controls permission system behavior\n * - 'rbac': Role-Based Access Control only (users get permissions via roles)\n * - 'direct': Direct action assignments only (actions assigned directly to users)\n * - 'full': Both RBAC and direct permissions (most flexible, default)\n */\n permissionMode?: PermissionMode;\n}\n\n/**\n * App Configuration Token\n * Use this to inject app config in services/components\n */\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\n/**\n * Get database mode from config\n * Derived from multiTenant.enabled\n */\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\n/**\n * Check if a feature is enabled\n * Features are enabled if their corresponding service exists and is enabled\n */\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'iam' | 'storage',\n): boolean {\n const service = config.services[feature];\n return service?.enabled ?? false;\n}\n\n/**\n * Helper function to get service base URL\n * Returns service-specific URL or falls back to default apiBaseUrl\n */\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: keyof IAppConfig['services'],\n): string {\n const service = config.services[serviceName];\n if (service?.enabled) {\n return service.baseUrl;\n }\n // Fallback to default API base URL\n return config.apiBaseUrl;\n}\n\n/**\n * Helper function to check if a service is enabled\n */\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: keyof IAppConfig['services'],\n): boolean {\n const service = config.services[serviceName];\n return service?.enabled ?? false;\n}\n\n/**\n * Check if company feature is enabled (backward compatible)\n * Company feature is enabled if auth service is enabled\n */\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\n/**\n * Check if storage feature is enabled (backward compatible)\n * Storage feature is enabled if storage service is enabled\n */\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return config.services.storage?.enabled ?? false;\n}\n\n/**\n * Get permission mode from config\n * Defaults to 'full' if not specified\n */\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\n/**\n * Get default storage config ID from config\n * Used for file upload operations when no storageConfigId is provided\n */\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\n// Default branding constants\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.get(`${this.baseUrl}/data`);\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/lib-app-config/lib-app-config.component';\n\n// Interceptors\nexport * from './interceptors/api-loader.interceptor';\nexport * from './interceptors/error-catching.interceptor';\n\n// Interfaces\nexport * from './interfaces/app-config.interface';\n\n// Services\nexport * from './services/api-loader.service';\nexport * from './services/base-api.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/interfaces/translate-adapter.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { Component, inject } from '@angular/core';\nimport { ConfirmDialog, UiModule } from '@flusys/ng-ui';\nimport { ApiLoaderService } from '../../services/api-loader.service';\n\n@Component({\n selector: 'lib-app-config',\n imports: [ConfirmDialog, UiModule],\n template: `\n @if (apiLoaderService.show()) {\n <f-progressbar mode=\"indeterminate\" />\n }\n <f-toast />\n <f-confirmdialog />\n `,\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\nimport { ApiLoaderService } from '../services/api-loader.service';\n\n/**\n * Interceptor that tracks loading state for HTTP requests.\n * Uses 'x-loader-tag' header to group related requests.\n *\n * @example\n * ```typescript\n * http.get('/api/users', {\n * headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })\n * });\n * // In template: apiLoaderService.getLoader('users-list')\n * ```\n */\nexport function apiLoaderInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn,\n): Observable<HttpEvent<unknown>> {\n const loaderService = inject(ApiLoaderService);\n const loaderTag =\n req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;\n\n loaderService.increase(loaderTag);\n\n const cleanReq = req.clone({\n headers: req.headers.delete('x-loader-tag'),\n });\n\n return next(cleanReq).pipe(finalize(() => loaderService.decrease(loaderTag)));\n}\n","import { InjectionToken } from '@angular/core';\n\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\nexport type ServiceName =\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification'\n | 'localization'\n | 'taskManager';\n\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\nexport interface IStorageServiceConfig extends IServiceConfig {\n /** Default storage config ID (falls back to company/branch default if not set) */\n defaultStorageConfigId?: string;\n}\n\nexport interface INotificationServiceConfig extends IServiceConfig {\n /** WebSocket URL (defaults to baseUrl if not set) */\n socketUrl?: string;\n}\n\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\nexport interface IRegistrationDefaults {\n companySlug?: string;\n branchSlug?: string;\n}\n\nexport interface LayoutConfig {\n preset?: string;\n primary?: string;\n surface?: string | null;\n darkTheme?: boolean;\n menuMode?: 'static' | 'overlay' | 'topbar';\n isMemorable?: boolean;\n}\n\nexport interface IAppConfig {\n appName: string;\n appLogo: string;\n author?: IAuthorConfig;\n apiBaseUrl: string;\n enableCompanyFeature: boolean;\n /** @default true */\n enableSignUp?: boolean;\n registrationDefaults?: IRegistrationDefaults;\n layoutConfig?: LayoutConfig;\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n eventManager: IServiceConfig;\n notification?: INotificationServiceConfig;\n localization?: IServiceConfig;\n taskManager?: IServiceConfig;\n };\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n /** 'rbac' | 'direct' | 'full' (default: 'full') */\n permissionMode?: PermissionMode;\n}\n\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: ServiceName,\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: ServiceName,\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service =\n config.services[serviceName as keyof typeof config.services];\n return service?.baseUrl ?? config.apiBaseUrl;\n }\n return config.apiBaseUrl;\n}\n\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\nexport function getNotificationSocketUrl(config: IAppConfig): string | null {\n const service = config.services.notification;\n if (!service?.enabled) return null;\n return service.socketUrl ?? service.baseUrl;\n}\n\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\nexport function getDefaultStorageConfigId(\n config: IAppConfig,\n): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\nexport function isSignUpEnabled(config: IAppConfig): boolean {\n return config.enableSignUp ?? true;\n}\n\nexport function getDefaultCompanySlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.companySlug;\n}\n\nexport function getDefaultBranchSlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.branchSlug;\n}\n\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { InjectionToken, Signal } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nexport interface ITranslateAdapter {\n /** Current language code signal - read this to create reactive dependency */\n translate(key: string, params?: Record<string, unknown>): string;\n}\n\n/** Registry for fallback messages when localization is not active */\nexport const FALLBACK_MESSAGES_REGISTRY = new InjectionToken<\n Record<string, string>\n>('FALLBACK_MESSAGES_REGISTRY');\n// ✅ Provide it once at root as a single shared object\nexport const provideFallbackMessagesRegistry = () => ({\n provide: FALLBACK_MESSAGES_REGISTRY,\n useValue: {}, // Single object instance shared across all injections\n});\n\n// Injection Token for localization services used in the resolver\nexport interface ILocalizationStateServicesAdapter {\n updateFallbackMessages(messages: Record<string, string>): void;\n currentLanguageCode: Signal<string>;\n isModuleLoaded(module: string): boolean;\n mergeTranslations(translations: Record<string, string>): void;\n markModuleLoaded(module: string): void;\n currentLanguage: Signal<string>;\n translations: Signal<Record<string, string>>;\n fallbackMessages: Signal<Record<string, string>>;\n}\nexport interface ILocalizationApiServicesAdapter {\n getTranslationsByLanguage(languageCode: string, modules: string[]): Observable<unknown>;\n}\nexport const LOCALIZATION_STATE_SERVICE =\n new InjectionToken<ILocalizationStateServicesAdapter>(\n 'LOCALIZATION_STATE_SERVICE',\n );\nexport const LOCALIZATION_API_SERVICE =\n new InjectionToken<ILocalizationApiServicesAdapter>(\n 'LOCALIZATION_API_SERVICE',\n );\n","import { HttpClient } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport {\n APP_CONFIG,\n getServiceUrl,\n ServiceName,\n} from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.post(`${this.baseUrl}/data/get`, {});\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: ServiceName) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/')\n ? endpoint.substring(1)\n : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/public-api';\n\n// Interceptors\nexport * from './interceptors/public-api';\n\n// Interfaces\nexport * from './interfaces/public-api';\n\n// Services\nexport * from './services/public-api';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/ng-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-rc.1",
|
|
4
4
|
"description": "Core utilities and services for FLUSYS Angular packages",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@angular/common": "
|
|
8
|
-
"@angular/core": "
|
|
9
|
-
"@
|
|
10
|
-
"
|
|
11
|
-
"primeng": "^21.0.0"
|
|
7
|
+
"@angular/common": ">=22.0.0",
|
|
8
|
+
"@angular/core": ">=22.0.0",
|
|
9
|
+
"@flusys/ng-ui": ">=1.2.0-rc.1",
|
|
10
|
+
"rxjs": "^7.8.0"
|
|
12
11
|
},
|
|
13
12
|
"sideEffects": false,
|
|
14
13
|
"module": "fesm2022/flusys-ng-core.mjs",
|
|
@@ -22,6 +21,7 @@
|
|
|
22
21
|
"default": "./fesm2022/flusys-ng-core.mjs"
|
|
23
22
|
}
|
|
24
23
|
},
|
|
24
|
+
"type": "module",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"tslib": "^2.3.0"
|
|
27
27
|
}
|
|
@@ -15,10 +15,6 @@ declare class ApiLoaderService {
|
|
|
15
15
|
static ɵprov: i0.ɵɵInjectableDeclaration<ApiLoaderService>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Root configuration component providing global UI elements.
|
|
20
|
-
* Include once at app root for global toast, confirm dialog, and loader.
|
|
21
|
-
*/
|
|
22
18
|
declare class LibAppConfigComponent {
|
|
23
19
|
readonly apiLoaderService: ApiLoaderService;
|
|
24
20
|
static ɵfac: i0.ɵɵFactoryDeclaration<LibAppConfigComponent, never>;
|
|
@@ -39,154 +35,111 @@ declare class LibAppConfigComponent {
|
|
|
39
35
|
*/
|
|
40
36
|
declare function apiLoaderInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
|
|
41
37
|
|
|
42
|
-
/**
|
|
43
|
-
* Error Catching Interceptor
|
|
44
|
-
* Displays error messages for failed HTTP requests.
|
|
45
|
-
*
|
|
46
|
-
* NOTE: This interceptor only handles error display.
|
|
47
|
-
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```typescript
|
|
51
|
-
* // In app.config.ts
|
|
52
|
-
* provideHttpClient(
|
|
53
|
-
* withFetch(),
|
|
54
|
-
* withInterceptors([
|
|
55
|
-
* authInterceptor, // from @flusys/ng-auth
|
|
56
|
-
* tokenRefreshInterceptor, // from @flusys/ng-auth
|
|
57
|
-
* errorCatchingInterceptor, // from @flusys/ng-core
|
|
58
|
-
* apiLoaderInterceptor, // from @flusys/ng-core
|
|
59
|
-
* ])
|
|
60
|
-
* )
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
declare function errorCatchingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* IAM Permission Mode - controls permission system behavior
|
|
67
|
-
*/
|
|
68
38
|
type PermissionMode = 'rbac' | 'direct' | 'full';
|
|
69
|
-
|
|
70
|
-
* Service Configuration Interface
|
|
71
|
-
*/
|
|
39
|
+
type ServiceName = 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | 'localization' | 'taskManager';
|
|
72
40
|
interface IServiceConfig {
|
|
73
41
|
baseUrl: string;
|
|
74
42
|
enabled: boolean;
|
|
75
43
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Storage Service Configuration Interface
|
|
78
|
-
* Extends base service config with storage-specific options
|
|
79
|
-
*/
|
|
80
44
|
interface IStorageServiceConfig extends IServiceConfig {
|
|
81
|
-
/**
|
|
82
|
-
* Default storage config ID from backend storage_config table
|
|
83
|
-
* Used when no storageConfigId is provided to upload operations
|
|
84
|
-
* If not set, backend will use company/branch default config
|
|
85
|
-
*/
|
|
45
|
+
/** Default storage config ID (falls back to company/branch default if not set) */
|
|
86
46
|
defaultStorageConfigId?: string;
|
|
87
47
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
48
|
+
interface INotificationServiceConfig extends IServiceConfig {
|
|
49
|
+
/** WebSocket URL (defaults to baseUrl if not set) */
|
|
50
|
+
socketUrl?: string;
|
|
51
|
+
}
|
|
91
52
|
interface IAuthorConfig {
|
|
92
53
|
name: string;
|
|
93
54
|
url: string;
|
|
94
55
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
56
|
+
interface IRegistrationDefaults {
|
|
57
|
+
companySlug?: string;
|
|
58
|
+
branchSlug?: string;
|
|
59
|
+
}
|
|
60
|
+
interface LayoutConfig {
|
|
61
|
+
preset?: string;
|
|
62
|
+
primary?: string;
|
|
63
|
+
surface?: string | null;
|
|
64
|
+
darkTheme?: boolean;
|
|
65
|
+
menuMode?: 'static' | 'overlay' | 'topbar';
|
|
66
|
+
isMemorable?: boolean;
|
|
67
|
+
}
|
|
99
68
|
interface IAppConfig {
|
|
100
|
-
/** Application name */
|
|
101
69
|
appName: string;
|
|
102
|
-
|
|
70
|
+
appLogo: string;
|
|
103
71
|
author?: IAuthorConfig;
|
|
104
|
-
/** Default API base URL (fallback) */
|
|
105
72
|
apiBaseUrl: string;
|
|
106
|
-
/**
|
|
107
|
-
* enableCompanyFeature in across the app
|
|
108
|
-
*/
|
|
109
73
|
enableCompanyFeature: boolean;
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*/
|
|
74
|
+
/** @default true */
|
|
75
|
+
enableSignUp?: boolean;
|
|
76
|
+
registrationDefaults?: IRegistrationDefaults;
|
|
77
|
+
layoutConfig?: LayoutConfig;
|
|
115
78
|
services: {
|
|
116
79
|
auth: IServiceConfig;
|
|
80
|
+
administration: IServiceConfig;
|
|
117
81
|
iam: IServiceConfig;
|
|
118
82
|
storage: IStorageServiceConfig;
|
|
119
|
-
|
|
83
|
+
formBuilder: IServiceConfig;
|
|
84
|
+
email: IServiceConfig;
|
|
85
|
+
eventManager: IServiceConfig;
|
|
86
|
+
notification?: INotificationServiceConfig;
|
|
87
|
+
localization?: IServiceConfig;
|
|
88
|
+
taskManager?: IServiceConfig;
|
|
120
89
|
};
|
|
121
|
-
/**
|
|
122
|
-
* Multi-tenant configuration
|
|
123
|
-
* enabled: true → Database mode = multi-tenant
|
|
124
|
-
* enabled: false → Database mode = single
|
|
125
|
-
*/
|
|
126
90
|
multiTenant: {
|
|
127
91
|
enabled: boolean;
|
|
128
92
|
tenantHeader: string;
|
|
129
93
|
tenantId?: string;
|
|
130
94
|
};
|
|
131
|
-
/**
|
|
132
|
-
* IAM Permission Mode - controls permission system behavior
|
|
133
|
-
* - 'rbac': Role-Based Access Control only (users get permissions via roles)
|
|
134
|
-
* - 'direct': Direct action assignments only (actions assigned directly to users)
|
|
135
|
-
* - 'full': Both RBAC and direct permissions (most flexible, default)
|
|
136
|
-
*/
|
|
95
|
+
/** 'rbac' | 'direct' | 'full' (default: 'full') */
|
|
137
96
|
permissionMode?: PermissionMode;
|
|
138
97
|
}
|
|
139
|
-
/**
|
|
140
|
-
* App Configuration Token
|
|
141
|
-
* Use this to inject app config in services/components
|
|
142
|
-
*/
|
|
143
98
|
declare const APP_CONFIG: InjectionToken<IAppConfig>;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* Derived from multiTenant.enabled
|
|
147
|
-
*/
|
|
148
|
-
declare function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant';
|
|
149
|
-
/**
|
|
150
|
-
* Check if a feature is enabled
|
|
151
|
-
* Features are enabled if their corresponding service exists and is enabled
|
|
152
|
-
*/
|
|
153
|
-
declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'iam' | 'storage'): boolean;
|
|
154
|
-
/**
|
|
155
|
-
* Helper function to get service base URL
|
|
156
|
-
* Returns service-specific URL or falls back to default apiBaseUrl
|
|
157
|
-
*/
|
|
158
|
-
declare function getServiceUrl(config: IAppConfig, serviceName: keyof IAppConfig['services']): string;
|
|
159
|
-
/**
|
|
160
|
-
* Helper function to check if a service is enabled
|
|
161
|
-
*/
|
|
162
|
-
declare function isServiceEnabled(config: IAppConfig, serviceName: keyof IAppConfig['services']): boolean;
|
|
163
|
-
/**
|
|
164
|
-
* Check if company feature is enabled (backward compatible)
|
|
165
|
-
* Company feature is enabled if auth service is enabled
|
|
166
|
-
*/
|
|
99
|
+
declare function isServiceEnabled(config: IAppConfig, serviceName: ServiceName): boolean;
|
|
100
|
+
declare function getServiceUrl(config: IAppConfig, serviceName: ServiceName): string;
|
|
167
101
|
declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
|
|
168
|
-
|
|
169
|
-
* Check if storage feature is enabled (backward compatible)
|
|
170
|
-
* Storage feature is enabled if storage service is enabled
|
|
171
|
-
*/
|
|
172
|
-
declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
|
|
173
|
-
/**
|
|
174
|
-
* Get permission mode from config
|
|
175
|
-
* Defaults to 'full' if not specified
|
|
176
|
-
*/
|
|
102
|
+
declare function getNotificationSocketUrl(config: IAppConfig): string | null;
|
|
177
103
|
declare function getPermissionMode(config: IAppConfig): PermissionMode;
|
|
178
|
-
/**
|
|
179
|
-
* Get default storage config ID from config
|
|
180
|
-
* Used for file upload operations when no storageConfigId is provided
|
|
181
|
-
*/
|
|
182
104
|
declare function getDefaultStorageConfigId(config: IAppConfig): string | undefined;
|
|
105
|
+
declare function isSignUpEnabled(config: IAppConfig): boolean;
|
|
106
|
+
declare function getDefaultCompanySlug(config: IAppConfig): string | undefined;
|
|
107
|
+
declare function getDefaultBranchSlug(config: IAppConfig): string | undefined;
|
|
183
108
|
declare const DEFAULT_APP_NAME = "FLUSYS";
|
|
184
109
|
declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
185
110
|
|
|
111
|
+
interface ITranslateAdapter {
|
|
112
|
+
/** Current language code signal - read this to create reactive dependency */
|
|
113
|
+
translate(key: string, params?: Record<string, unknown>): string;
|
|
114
|
+
}
|
|
115
|
+
/** Registry for fallback messages when localization is not active */
|
|
116
|
+
declare const FALLBACK_MESSAGES_REGISTRY: InjectionToken<Record<string, string>>;
|
|
117
|
+
declare const provideFallbackMessagesRegistry: () => {
|
|
118
|
+
provide: InjectionToken<Record<string, string>>;
|
|
119
|
+
useValue: {};
|
|
120
|
+
};
|
|
121
|
+
interface ILocalizationStateServicesAdapter {
|
|
122
|
+
updateFallbackMessages(messages: Record<string, string>): void;
|
|
123
|
+
currentLanguageCode: Signal<string>;
|
|
124
|
+
isModuleLoaded(module: string): boolean;
|
|
125
|
+
mergeTranslations(translations: Record<string, string>): void;
|
|
126
|
+
markModuleLoaded(module: string): void;
|
|
127
|
+
currentLanguage: Signal<string>;
|
|
128
|
+
translations: Signal<Record<string, string>>;
|
|
129
|
+
fallbackMessages: Signal<Record<string, string>>;
|
|
130
|
+
}
|
|
131
|
+
interface ILocalizationApiServicesAdapter {
|
|
132
|
+
getTranslationsByLanguage(languageCode: string, modules: string[]): Observable<unknown>;
|
|
133
|
+
}
|
|
134
|
+
declare const LOCALIZATION_STATE_SERVICE: InjectionToken<ILocalizationStateServicesAdapter>;
|
|
135
|
+
declare const LOCALIZATION_API_SERVICE: InjectionToken<ILocalizationApiServicesAdapter>;
|
|
136
|
+
|
|
186
137
|
/**
|
|
187
138
|
* Base API Service
|
|
188
139
|
* Provides service-specific base URL resolution
|
|
189
140
|
*
|
|
141
|
+
* Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.
|
|
142
|
+
*
|
|
190
143
|
* Usage:
|
|
191
144
|
* ```typescript
|
|
192
145
|
* @Injectable({ providedIn: 'root' })
|
|
@@ -196,7 +149,7 @@ declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
|
196
149
|
* }
|
|
197
150
|
*
|
|
198
151
|
* getData() {
|
|
199
|
-
* return this.http.
|
|
152
|
+
* return this.http.post(`${this.baseUrl}/data/get`, {});
|
|
200
153
|
* }
|
|
201
154
|
* }
|
|
202
155
|
* ```
|
|
@@ -208,7 +161,7 @@ declare abstract class BaseApiService {
|
|
|
208
161
|
/**
|
|
209
162
|
* @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')
|
|
210
163
|
*/
|
|
211
|
-
constructor(serviceName:
|
|
164
|
+
constructor(serviceName: ServiceName);
|
|
212
165
|
/**
|
|
213
166
|
* Get full URL for an endpoint
|
|
214
167
|
* @param endpoint - API endpoint (e.g., '/users/get-all')
|
|
@@ -217,5 +170,5 @@ declare abstract class BaseApiService {
|
|
|
217
170
|
protected getUrl(endpoint: string): string;
|
|
218
171
|
}
|
|
219
172
|
|
|
220
|
-
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, apiLoaderInterceptor,
|
|
221
|
-
export type { IAppConfig, IAuthorConfig, IServiceConfig, IStorageServiceConfig, PermissionMode };
|
|
173
|
+
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE, LibAppConfigComponent, apiLoaderInterceptor, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, provideFallbackMessagesRegistry };
|
|
174
|
+
export type { IAppConfig, IAuthorConfig, ILocalizationApiServicesAdapter, ILocalizationStateServicesAdapter, INotificationServiceConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, ITranslateAdapter, LayoutConfig, PermissionMode, ServiceName };
|