@yuuvis/client-core 2.20.0 → 2.21.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.
- package/fesm2022/yuuvis-client-core.mjs +171 -63
- package/fesm2022/yuuvis-client-core.mjs.map +1 -1
- package/lib/service/auth/auth.service.d.ts +1 -1
- package/lib/service/core-init/core-init.service.d.ts +1 -1
- package/lib/service/idm/idm.service.d.ts +106 -14
- package/lib/service/idm/models/idm-cached-user.model.d.ts +20 -4
- package/lib/service/idm/models/idm-user-cache.model.d.ts +22 -3
- package/lib/service/idm/models/idm-user-response.model.d.ts +27 -11
- package/lib/service/idm/models/organization-set-entry.model.d.ts +26 -5
- package/package.json +1 -1
|
@@ -3119,16 +3119,20 @@ class AuthService {
|
|
|
3119
3119
|
*/
|
|
3120
3120
|
setInitialRequestUri() {
|
|
3121
3121
|
const ignore = ['/', '/index.html'];
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3122
|
+
const baseHref = Utils.getBaseHref();
|
|
3123
|
+
// Check only the pathname against the ignore list so that auth callback
|
|
3124
|
+
// query params (e.g. ?session_state=…) don't bypass the check.
|
|
3125
|
+
let path = location.pathname.replace(baseHref, '');
|
|
3126
|
+
path = !path.startsWith('/') ? `/${path}` : path;
|
|
3127
|
+
if (!ignore.includes(path)) {
|
|
3128
|
+
let uri = `${location.pathname}${location.search}`.replace(baseHref, '');
|
|
3129
|
+
uri = !uri.startsWith('/') ? `/${uri}` : uri;
|
|
3130
|
+
return this.#appCache.setItem(this.#INITIAL_REQUEST_STORAGE_KEY, {
|
|
3127
3131
|
uri: uri,
|
|
3128
3132
|
timestamp: Date.now()
|
|
3129
|
-
})
|
|
3130
|
-
.subscribe();
|
|
3133
|
+
});
|
|
3131
3134
|
}
|
|
3135
|
+
return of(false);
|
|
3132
3136
|
}
|
|
3133
3137
|
/**
|
|
3134
3138
|
* Get the URL that entered the app. May be a deep link that could then be
|
|
@@ -3435,8 +3439,7 @@ const init_moduleFnc = () => {
|
|
|
3435
3439
|
const http = inject(HttpClient);
|
|
3436
3440
|
const configService = inject(ConfigService);
|
|
3437
3441
|
const authService = inject(AuthService);
|
|
3438
|
-
authService.setInitialRequestUri()
|
|
3439
|
-
return coreConfig.main
|
|
3442
|
+
return authService.setInitialRequestUri().pipe(switchMap(() => coreConfig.main
|
|
3440
3443
|
? !Array.isArray(coreConfig.main)
|
|
3441
3444
|
? of([coreConfig.main])
|
|
3442
3445
|
: forkJoin([...coreConfig.main].map((uri) => http.get(`${Utils.getBaseHref()}${uri}`).pipe(catchError((e) => {
|
|
@@ -3451,7 +3454,7 @@ const init_moduleFnc = () => {
|
|
|
3451
3454
|
};
|
|
3452
3455
|
return of(true);
|
|
3453
3456
|
}))))
|
|
3454
|
-
: of(false);
|
|
3457
|
+
: of(false)));
|
|
3455
3458
|
};
|
|
3456
3459
|
|
|
3457
3460
|
var DeviceScreenOrientation;
|
|
@@ -4511,47 +4514,128 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4511
4514
|
}] });
|
|
4512
4515
|
|
|
4513
4516
|
/**
|
|
4514
|
-
*
|
|
4515
|
-
*
|
|
4516
|
-
*
|
|
4517
|
+
* Centralized service for Identity Management (IDM) operations.
|
|
4518
|
+
*
|
|
4519
|
+
* Provides a typed API for querying users, roles, and organization entities
|
|
4520
|
+
* from the IDM backend, with a built-in client-side caching layer that
|
|
4521
|
+
* minimizes redundant HTTP calls.
|
|
4522
|
+
*
|
|
4523
|
+
* **Key Features:**
|
|
4524
|
+
* - User lookup by ID with automatic 1-hour TTL cache
|
|
4525
|
+
* - Unified search across users and roles via a single method
|
|
4526
|
+
* - Role listing with optional name filter
|
|
4527
|
+
* - Cache persisted to IndexedDB/localStorage (survives page refresh)
|
|
4528
|
+
* - In-memory cache hydrated on service creation for instant access
|
|
4529
|
+
*
|
|
4530
|
+
* **Caching Strategy:**
|
|
4531
|
+
* All cached users are stored as a single map ({@link IdmUserCache}) under one
|
|
4532
|
+
* storage key. This enables atomic load on startup — one read populates the
|
|
4533
|
+
* entire in-memory cache — and avoids N+1 reads when resolving multiple users
|
|
4534
|
+
* (e.g. audit trails, comment threads, assignment lists). Per-entry TTL is
|
|
4535
|
+
* enforced via {@link IdmCachedUser.expires}.
|
|
4536
|
+
*
|
|
4537
|
+
* ```
|
|
4538
|
+
* getUserById("abc")
|
|
4539
|
+
* ├─ Cache hit & valid → return immediately (no HTTP)
|
|
4540
|
+
* └─ Cache miss/expired → GET /idm/users/abc
|
|
4541
|
+
* ├─ Update in-memory cache
|
|
4542
|
+
* ├─ Persist to ClientCacheService
|
|
4543
|
+
* └─ Return YuvUser
|
|
4544
|
+
* ```
|
|
4545
|
+
*
|
|
4546
|
+
* **API Endpoints:**
|
|
4547
|
+
* | Method | Endpoint |
|
|
4548
|
+
* |----------------------------|----------------------|
|
|
4549
|
+
* | `queryOrganizationEntity` | `GET /idm/search` |
|
|
4550
|
+
* | `getRoles` | `GET /idm/roles` |
|
|
4551
|
+
* | `getUserById` | `GET /idm/users/:id` |
|
|
4552
|
+
*
|
|
4553
|
+
* **Usage:**
|
|
4554
|
+
* ```ts
|
|
4555
|
+
* const idm = inject(IdmService);
|
|
4556
|
+
*
|
|
4557
|
+
* // Search users and roles
|
|
4558
|
+
* idm.queryOrganizationEntity('admin', ['user', 'role'])
|
|
4559
|
+
* .subscribe(entries => console.log(entries));
|
|
4560
|
+
*
|
|
4561
|
+
* // Get a user by ID (cached)
|
|
4562
|
+
* idm.getUserById('user-uuid').subscribe(user => console.log(user?.title));
|
|
4563
|
+
*
|
|
4564
|
+
* // List roles
|
|
4565
|
+
* idm.getRoles('ADMIN').subscribe(roles => console.log(roles));
|
|
4566
|
+
* ```
|
|
4567
|
+
*
|
|
4568
|
+
* @see {@link ClientCacheService} for the underlying persistence layer
|
|
4569
|
+
* @see {@link YuvUser} for the application-level user model
|
|
4517
4570
|
*/
|
|
4518
4571
|
class IdmService {
|
|
4519
|
-
|
|
4520
|
-
#
|
|
4521
|
-
#
|
|
4522
|
-
|
|
4572
|
+
//#region Dependencies
|
|
4573
|
+
#backend = inject(BackendService);
|
|
4574
|
+
#clientCache = inject(ClientCacheService);
|
|
4575
|
+
//#endregion
|
|
4576
|
+
//#region Cache configuration
|
|
4577
|
+
#cacheKey = 'yuv-idm-user-cache';
|
|
4578
|
+
/**
|
|
4579
|
+
* Time-to-live for cached user entries (in milliseconds).
|
|
4580
|
+
*
|
|
4581
|
+
* After this period a cached entry is considered stale and the next
|
|
4582
|
+
* {@link getUserById} call for that user will re-fetch from the backend.
|
|
4583
|
+
*
|
|
4584
|
+
* **Timeline:**
|
|
4585
|
+
* ```
|
|
4586
|
+
* [User fetched] ─────────── 1 hour ──────────► [Entry expires]
|
|
4587
|
+
* cache hit returns instantly next access re-fetches
|
|
4588
|
+
* ```
|
|
4589
|
+
*
|
|
4590
|
+
* @default 1 hour (3600000 milliseconds)
|
|
4591
|
+
*/
|
|
4592
|
+
#cacheTtl = 60 * 60 * 1000;
|
|
4593
|
+
#userCache = {};
|
|
4594
|
+
//#endregion
|
|
4523
4595
|
constructor() {
|
|
4524
|
-
this.#
|
|
4525
|
-
this.#clientCache = inject(ClientCacheService);
|
|
4526
|
-
this.#IDM_USER_CACHE_KEY = 'yuv-idm-user-cache';
|
|
4527
|
-
this.#IDM_USER_CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
4528
|
-
this.userCache = {};
|
|
4529
|
-
this.#loadIdmCache();
|
|
4596
|
+
this.#loadCacheFromStorage();
|
|
4530
4597
|
}
|
|
4598
|
+
//#region Public methods
|
|
4531
4599
|
/**
|
|
4532
|
-
*
|
|
4600
|
+
* Searches organization entities (users and/or roles) by a free-text term.
|
|
4533
4601
|
*
|
|
4534
|
-
*
|
|
4535
|
-
*
|
|
4602
|
+
* Returns a unified {@link OrganizationSetEntry} array so consumers
|
|
4603
|
+
* (autocomplete fields, assignment dialogs, permission pickers) can
|
|
4604
|
+
* treat users and roles interchangeably.
|
|
4605
|
+
*
|
|
4606
|
+
* @param term - Free-text search term to filter entities
|
|
4607
|
+
* @param targetTypes - Entity types to include in results (`'user'`, `'role'`, or both)
|
|
4536
4608
|
* @param size - Optional maximum number of results to return
|
|
4537
|
-
* @returns Observable
|
|
4609
|
+
* @returns Observable of matching organization entities
|
|
4610
|
+
*
|
|
4611
|
+
* @example
|
|
4612
|
+
* ```ts
|
|
4613
|
+
* // Search for users and roles matching "admin"
|
|
4614
|
+
* idmService.queryOrganizationEntity('admin', ['user', 'role'], 10)
|
|
4615
|
+
* .subscribe(entries => {
|
|
4616
|
+
* const users = entries.filter(e => e.type === 'user');
|
|
4617
|
+
* const roles = entries.filter(e => e.type === 'role');
|
|
4618
|
+
* });
|
|
4619
|
+
* ```
|
|
4538
4620
|
*/
|
|
4539
4621
|
queryOrganizationEntity(term, targetTypes, size) {
|
|
4540
|
-
const
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4622
|
+
const params = new URLSearchParams();
|
|
4623
|
+
params.set('search', term);
|
|
4624
|
+
if (size) {
|
|
4625
|
+
params.set('size', size.toString());
|
|
4626
|
+
}
|
|
4627
|
+
return this.#backend.get(`/idm/search?${params.toString()}`).pipe(map$1((res) => [
|
|
4544
4628
|
...(targetTypes.includes('user')
|
|
4545
|
-
? res.users.map((
|
|
4546
|
-
id:
|
|
4547
|
-
title: `${
|
|
4629
|
+
? res.users.map((user) => ({
|
|
4630
|
+
id: user.id,
|
|
4631
|
+
title: `${user.lastname}, ${user.firstname} (${user.username})`,
|
|
4548
4632
|
type: 'user'
|
|
4549
4633
|
}))
|
|
4550
4634
|
: []),
|
|
4551
4635
|
...(targetTypes.includes('role')
|
|
4552
|
-
? res.roles.map((
|
|
4553
|
-
id:
|
|
4554
|
-
title:
|
|
4636
|
+
? res.roles.map((role) => ({
|
|
4637
|
+
id: role.name,
|
|
4638
|
+
title: role.name,
|
|
4555
4639
|
type: 'role'
|
|
4556
4640
|
}))
|
|
4557
4641
|
: [])
|
|
@@ -4560,42 +4644,66 @@ class IdmService {
|
|
|
4560
4644
|
/**
|
|
4561
4645
|
* Retrieves available roles, optionally filtered by a search term.
|
|
4562
4646
|
*
|
|
4647
|
+
* Returns an empty array on error to ensure consumers can always
|
|
4648
|
+
* safely iterate the result without null-checking.
|
|
4649
|
+
*
|
|
4563
4650
|
* @param role - Optional search term to filter roles by name
|
|
4564
|
-
* @returns Observable
|
|
4651
|
+
* @returns Observable of role objects containing name and description
|
|
4652
|
+
*
|
|
4653
|
+
* @example
|
|
4654
|
+
* ```ts
|
|
4655
|
+
* // List all roles
|
|
4656
|
+
* idmService.getRoles().subscribe(roles => console.log(roles));
|
|
4657
|
+
*
|
|
4658
|
+
* // Filter roles by name
|
|
4659
|
+
* idmService.getRoles('ADMIN').subscribe(roles => console.log(roles));
|
|
4660
|
+
* ```
|
|
4565
4661
|
*/
|
|
4566
4662
|
getRoles(role) {
|
|
4567
|
-
const
|
|
4568
|
-
|
|
4569
|
-
|
|
4663
|
+
const params = new URLSearchParams();
|
|
4664
|
+
if (role) {
|
|
4665
|
+
params.set('search', role);
|
|
4666
|
+
}
|
|
4667
|
+
return this.#backend.get(`/idm/roles?${params.toString()}`).pipe(catchError$1(() => of([])));
|
|
4570
4668
|
}
|
|
4571
4669
|
/**
|
|
4572
|
-
* Retrieves user
|
|
4573
|
-
*
|
|
4670
|
+
* Retrieves a user by ID with automatic caching.
|
|
4671
|
+
*
|
|
4672
|
+
* Checks the in-memory cache first. If a valid (non-expired) entry exists,
|
|
4673
|
+
* it is returned immediately without an HTTP call. Otherwise, the user is
|
|
4674
|
+
* fetched from the backend, stored in both the in-memory and persistent
|
|
4675
|
+
* cache, and returned as a {@link YuvUser}.
|
|
4574
4676
|
*
|
|
4575
|
-
*
|
|
4576
|
-
*
|
|
4677
|
+
* Returns `null` if the user is not found or the request fails,
|
|
4678
|
+
* so consumers can safely use the result without try/catch.
|
|
4679
|
+
*
|
|
4680
|
+
* @param id - Unique user identifier (UUID)
|
|
4681
|
+
* @returns Observable of the user model, or `null` if not found / on error
|
|
4682
|
+
*
|
|
4683
|
+
* @example
|
|
4684
|
+
* ```ts
|
|
4685
|
+
* idmService.getUserById('550e8400-e29b-41d4-a716-446655440000')
|
|
4686
|
+
* .subscribe(user => {
|
|
4687
|
+
* if (user) {
|
|
4688
|
+
* console.log(user.getFullName());
|
|
4689
|
+
* }
|
|
4690
|
+
* });
|
|
4691
|
+
* ```
|
|
4577
4692
|
*/
|
|
4578
4693
|
getUserById(id) {
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
if (isCacheValid) {
|
|
4582
|
-
return of(new YuvUser(cachedUser.user));
|
|
4583
|
-
}
|
|
4584
|
-
else {
|
|
4585
|
-
return this.#backend.get(`/idm/users/${id}`).pipe(switchMap$1((user) => {
|
|
4586
|
-
this.userCache[id] = {
|
|
4587
|
-
expires: Date.now() + this.#IDM_USER_CACHE_TTL,
|
|
4588
|
-
user
|
|
4589
|
-
};
|
|
4590
|
-
return this.#clientCache.updateCache(this.#IDM_USER_CACHE_KEY, this.userCache).pipe(map$1(() => user));
|
|
4591
|
-
}), map$1((user) => (user ? new YuvUser(user) : null)), catchError$1(() => of(null)));
|
|
4694
|
+
if (id in this.#userCache && Date.now() < this.#userCache[id].expires) {
|
|
4695
|
+
return of(new YuvUser(this.#userCache[id].user));
|
|
4592
4696
|
}
|
|
4697
|
+
return this.#backend.get(`/idm/users/${id}`).pipe(switchMap$1((user) => {
|
|
4698
|
+
this.#userCache[id] = { expires: Date.now() + this.#cacheTtl, user };
|
|
4699
|
+
return this.#clientCache.updateCache(this.#cacheKey, this.#userCache).pipe(map$1(() => user));
|
|
4700
|
+
}), map$1((user) => new YuvUser(user)), catchError$1(() => of(null)));
|
|
4593
4701
|
}
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
}
|
|
4702
|
+
//#endregion
|
|
4703
|
+
//#region Cache
|
|
4704
|
+
#loadCacheFromStorage() {
|
|
4705
|
+
this.#clientCache.getFromCache(this.#cacheKey).subscribe({
|
|
4706
|
+
next: (cache) => (this.#userCache = cache || {})
|
|
4599
4707
|
});
|
|
4600
4708
|
}
|
|
4601
4709
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: IdmService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|