@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.
@@ -3119,16 +3119,20 @@ class AuthService {
3119
3119
  */
3120
3120
  setInitialRequestUri() {
3121
3121
  const ignore = ['/', '/index.html'];
3122
- let uri = `${location.pathname}${location.search}`.replace(Utils.getBaseHref(), '');
3123
- uri = !uri.startsWith('/') ? `/${uri}` : uri;
3124
- if (!ignore.includes(uri)) {
3125
- this.#appCache
3126
- .setItem(this.#INITIAL_REQUEST_STORAGE_KEY, {
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
- * Service for managing Identity Management (IDM) operations.
4515
- * Provides functionality for querying users, roles, and organization entities,
4516
- * with built-in caching mechanisms to optimize performance.
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
- #backend;
4520
- #clientCache;
4521
- #IDM_USER_CACHE_KEY;
4522
- #IDM_USER_CACHE_TTL; // 1 hour in milliseconds
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.#backend = inject(BackendService);
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
- * Queries organization entities (users and/or roles) based on search term.
4600
+ * Searches organization entities (users and/or roles) by a free-text term.
4533
4601
  *
4534
- * @param term - The search term to filter entities
4535
- * @param targetTypes - Array of entity types to search for ('user', 'role')
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 array of organization set entries matching the search criteria
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 searchParams = new URLSearchParams();
4541
- searchParams.set('search', term);
4542
- size && searchParams.set('size', size.toString());
4543
- return this.#backend.get(`/idm/search?${searchParams.toString()}`).pipe(map$1((res) => [
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((u) => ({
4546
- id: u.id,
4547
- title: `${u.lastname}, ${u.firstname} (${u.username})`, // TODO: u.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((u) => ({
4553
- id: u.name,
4554
- title: u.name,
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 array of role objects containing name and description
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 searchParams = new URLSearchParams();
4568
- role && searchParams.set('search', role);
4569
- return this.#backend.get(`/idm/roles?${searchParams.toString()}`).pipe(catchError$1(() => of([])));
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 information by user ID with automatic caching.
4573
- * Uses a 1-hour TTL cache to minimize backend requests.
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
- * @param id - The unique identifier of the user
4576
- * @returns Observable of YuvUser object or null if user is not found
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
- const cachedUser = this.userCache[id];
4580
- const isCacheValid = cachedUser && Date.now() < cachedUser.expires;
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
- #loadIdmCache() {
4595
- return this.#clientCache.getFromCache(this.#IDM_USER_CACHE_KEY).subscribe({
4596
- next: (cache) => {
4597
- this.userCache = cache || {};
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 }); }