@yuuvis/client-core 2.20.1 → 2.21.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/fesm2022/yuuvis-client-core.mjs +158 -53
- package/fesm2022/yuuvis-client-core.mjs.map +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
|
@@ -1,41 +1,133 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
2
|
import { YuvUser } from '../../model/yuv-user.model';
|
|
3
|
-
import {
|
|
3
|
+
import { OrganizationSetEntry } from './models';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Centralized service for Identity Management (IDM) operations.
|
|
7
|
+
*
|
|
8
|
+
* Provides a typed API for querying users, roles, and organization entities
|
|
9
|
+
* from the IDM backend, with a built-in client-side caching layer that
|
|
10
|
+
* minimizes redundant HTTP calls.
|
|
11
|
+
*
|
|
12
|
+
* **Key Features:**
|
|
13
|
+
* - User lookup by ID with automatic 1-hour TTL cache
|
|
14
|
+
* - Unified search across users and roles via a single method
|
|
15
|
+
* - Role listing with optional name filter
|
|
16
|
+
* - Cache persisted to IndexedDB/localStorage (survives page refresh)
|
|
17
|
+
* - In-memory cache hydrated on service creation for instant access
|
|
18
|
+
*
|
|
19
|
+
* **Caching Strategy:**
|
|
20
|
+
* All cached users are stored as a single map ({@link IdmUserCache}) under one
|
|
21
|
+
* storage key. This enables atomic load on startup — one read populates the
|
|
22
|
+
* entire in-memory cache — and avoids N+1 reads when resolving multiple users
|
|
23
|
+
* (e.g. audit trails, comment threads, assignment lists). Per-entry TTL is
|
|
24
|
+
* enforced via {@link IdmCachedUser.expires}.
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* getUserById("abc")
|
|
28
|
+
* ├─ Cache hit & valid → return immediately (no HTTP)
|
|
29
|
+
* └─ Cache miss/expired → GET /idm/users/abc
|
|
30
|
+
* ├─ Update in-memory cache
|
|
31
|
+
* ├─ Persist to ClientCacheService
|
|
32
|
+
* └─ Return YuvUser
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* **API Endpoints:**
|
|
36
|
+
* | Method | Endpoint |
|
|
37
|
+
* |----------------------------|----------------------|
|
|
38
|
+
* | `queryOrganizationEntity` | `GET /idm/search` |
|
|
39
|
+
* | `getRoles` | `GET /idm/roles` |
|
|
40
|
+
* | `getUserById` | `GET /idm/users/:id` |
|
|
41
|
+
*
|
|
42
|
+
* **Usage:**
|
|
43
|
+
* ```ts
|
|
44
|
+
* const idm = inject(IdmService);
|
|
45
|
+
*
|
|
46
|
+
* // Search users and roles
|
|
47
|
+
* idm.queryOrganizationEntity('admin', ['user', 'role'])
|
|
48
|
+
* .subscribe(entries => console.log(entries));
|
|
49
|
+
*
|
|
50
|
+
* // Get a user by ID (cached)
|
|
51
|
+
* idm.getUserById('user-uuid').subscribe(user => console.log(user?.title));
|
|
52
|
+
*
|
|
53
|
+
* // List roles
|
|
54
|
+
* idm.getRoles('ADMIN').subscribe(roles => console.log(roles));
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @see {@link ClientCacheService} for the underlying persistence layer
|
|
58
|
+
* @see {@link YuvUser} for the application-level user model
|
|
9
59
|
*/
|
|
10
60
|
export declare class IdmService {
|
|
11
61
|
#private;
|
|
12
|
-
userCache: IdmUserCache;
|
|
13
62
|
constructor();
|
|
14
63
|
/**
|
|
15
|
-
*
|
|
64
|
+
* Searches organization entities (users and/or roles) by a free-text term.
|
|
16
65
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
66
|
+
* Returns a unified {@link OrganizationSetEntry} array so consumers
|
|
67
|
+
* (autocomplete fields, assignment dialogs, permission pickers) can
|
|
68
|
+
* treat users and roles interchangeably.
|
|
69
|
+
*
|
|
70
|
+
* @param term - Free-text search term to filter entities
|
|
71
|
+
* @param targetTypes - Entity types to include in results (`'user'`, `'role'`, or both)
|
|
19
72
|
* @param size - Optional maximum number of results to return
|
|
20
|
-
* @returns Observable
|
|
73
|
+
* @returns Observable of matching organization entities
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* // Search for users and roles matching "admin"
|
|
78
|
+
* idmService.queryOrganizationEntity('admin', ['user', 'role'], 10)
|
|
79
|
+
* .subscribe(entries => {
|
|
80
|
+
* const users = entries.filter(e => e.type === 'user');
|
|
81
|
+
* const roles = entries.filter(e => e.type === 'role');
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
21
84
|
*/
|
|
22
85
|
queryOrganizationEntity(term: string, targetTypes: string[], size?: number): Observable<OrganizationSetEntry[]>;
|
|
23
86
|
/**
|
|
24
87
|
* Retrieves available roles, optionally filtered by a search term.
|
|
25
88
|
*
|
|
89
|
+
* Returns an empty array on error to ensure consumers can always
|
|
90
|
+
* safely iterate the result without null-checking.
|
|
91
|
+
*
|
|
26
92
|
* @param role - Optional search term to filter roles by name
|
|
27
|
-
* @returns Observable
|
|
93
|
+
* @returns Observable of role objects containing name and description
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* // List all roles
|
|
98
|
+
* idmService.getRoles().subscribe(roles => console.log(roles));
|
|
99
|
+
*
|
|
100
|
+
* // Filter roles by name
|
|
101
|
+
* idmService.getRoles('ADMIN').subscribe(roles => console.log(roles));
|
|
102
|
+
* ```
|
|
28
103
|
*/
|
|
29
104
|
getRoles(role?: string): Observable<{
|
|
30
105
|
name: string;
|
|
31
106
|
description: string;
|
|
32
107
|
}[]>;
|
|
33
108
|
/**
|
|
34
|
-
* Retrieves user
|
|
35
|
-
*
|
|
109
|
+
* Retrieves a user by ID with automatic caching.
|
|
110
|
+
*
|
|
111
|
+
* Checks the in-memory cache first. If a valid (non-expired) entry exists,
|
|
112
|
+
* it is returned immediately without an HTTP call. Otherwise, the user is
|
|
113
|
+
* fetched from the backend, stored in both the in-memory and persistent
|
|
114
|
+
* cache, and returned as a {@link YuvUser}.
|
|
115
|
+
*
|
|
116
|
+
* Returns `null` if the user is not found or the request fails,
|
|
117
|
+
* so consumers can safely use the result without try/catch.
|
|
118
|
+
*
|
|
119
|
+
* @param id - Unique user identifier (UUID)
|
|
120
|
+
* @returns Observable of the user model, or `null` if not found / on error
|
|
36
121
|
*
|
|
37
|
-
* @
|
|
38
|
-
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* idmService.getUserById('550e8400-e29b-41d4-a716-446655440000')
|
|
125
|
+
* .subscribe(user => {
|
|
126
|
+
* if (user) {
|
|
127
|
+
* console.log(user.getFullName());
|
|
128
|
+
* }
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
39
131
|
*/
|
|
40
132
|
getUserById(id: string): Observable<YuvUser | null>;
|
|
41
133
|
static ɵfac: i0.ɵɵFactoryDeclaration<IdmService, never>;
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { IdmUserResponse } from './idm-user-response.model';
|
|
2
2
|
/**
|
|
3
|
-
* Represents a cached user entry with expiration timestamp.
|
|
4
|
-
*
|
|
3
|
+
* Represents a single cached user entry with an expiration timestamp.
|
|
4
|
+
*
|
|
5
|
+
* Used internally by {@link IdmService} to wrap raw {@link IdmUserResponse} data
|
|
6
|
+
* with a TTL-based expiry. When `Date.now()` exceeds {@link expires}, the entry
|
|
7
|
+
* is considered stale and the service re-fetches from the backend.
|
|
8
|
+
*
|
|
9
|
+
* **Cache Lifecycle:**
|
|
10
|
+
* ```
|
|
11
|
+
* [User fetched] ──► IdmCachedUser created ──► stored in IdmUserCache
|
|
12
|
+
* (expires = now + 1h)
|
|
13
|
+
*
|
|
14
|
+
* [Next access]
|
|
15
|
+
* ├─ Date.now() < expires → return from cache (no HTTP call)
|
|
16
|
+
* └─ Date.now() ≥ expires → re-fetch from backend, update cache
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @see {@link IdmUserCache} for the dictionary that holds these entries
|
|
20
|
+
* @see {@link IdmService.getUserById} for the caching logic
|
|
5
21
|
*/
|
|
6
22
|
export interface IdmCachedUser {
|
|
7
|
-
/**
|
|
23
|
+
/** Unix timestamp (ms) when this cache entry expires (`Date.now() + TTL`) */
|
|
8
24
|
expires: number;
|
|
9
|
-
/**
|
|
25
|
+
/** Raw user data from the IDM backend API */
|
|
10
26
|
user: IdmUserResponse;
|
|
11
27
|
}
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import { IdmCachedUser } from './idm-cached-user.model';
|
|
2
2
|
/**
|
|
3
|
-
* Dictionary
|
|
4
|
-
*
|
|
3
|
+
* Dictionary structure for the in-memory and persisted IDM user cache.
|
|
4
|
+
*
|
|
5
|
+
* Maps user IDs (UUIDs) to their {@link IdmCachedUser} entries. The entire map
|
|
6
|
+
* is persisted as a single blob in {@link ClientCacheService} under the key
|
|
7
|
+
* `yuv-idm-user-cache`, so all cached users are loaded/saved atomically.
|
|
8
|
+
*
|
|
9
|
+
* **Storage Strategy:**
|
|
10
|
+
* ```
|
|
11
|
+
* ClientCacheService (IndexedDB / localStorage)
|
|
12
|
+
* └─ "yuv-idm-user-cache" → IdmUserCache
|
|
13
|
+
* ├─ "user-uuid-1" → { expires: ..., user: { ... } }
|
|
14
|
+
* ├─ "user-uuid-2" → { expires: ..., user: { ... } }
|
|
15
|
+
* └─ "user-uuid-N" → { expires: ..., user: { ... } }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* **Why a single map instead of per-user cache entries:**
|
|
19
|
+
* - Atomic load on startup — one read populates the entire in-memory cache
|
|
20
|
+
* - Avoids N+1 storage reads when resolving multiple users (e.g. audit trails, comment threads)
|
|
21
|
+
* - Per-entry TTL is still enforced via {@link IdmCachedUser.expires}
|
|
22
|
+
*
|
|
23
|
+
* @see {@link IdmCachedUser} for the per-user entry structure
|
|
24
|
+
* @see {@link IdmService} for the cache management logic
|
|
5
25
|
*/
|
|
6
26
|
export interface IdmUserCache {
|
|
7
|
-
/** Map of user IDs to their cached user data */
|
|
8
27
|
[userId: string]: IdmCachedUser;
|
|
9
28
|
}
|
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Represents the user data structure returned from the IDM API.
|
|
3
|
-
*
|
|
2
|
+
* Represents the user data structure returned from the IDM backend API (`/idm/users/{id}`).
|
|
3
|
+
*
|
|
4
|
+
* This is the raw transport interface — it maps 1:1 to the JSON payload from the identity
|
|
5
|
+
* management service. The application-level model {@link YuvUser} wraps this interface and
|
|
6
|
+
* adds computed properties (display name, locale, settings).
|
|
7
|
+
*
|
|
8
|
+
* **Typical Flow:**
|
|
9
|
+
* ```
|
|
10
|
+
* Backend API ──► IdmUserResponse (raw JSON) ──► YuvUser (app model)
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* **Usage:**
|
|
14
|
+
* - Consumed internally by {@link IdmService.getUserById} to type the HTTP response
|
|
15
|
+
* - Passed into the {@link YuvUser} constructor for hydration
|
|
16
|
+
* - Stored in {@link IdmCachedUser} for persistence in the client cache
|
|
17
|
+
*
|
|
18
|
+
* @see {@link IdmService.getUserById} for the fetching and caching logic
|
|
19
|
+
* @see {@link YuvUser} for the application-level user model
|
|
4
20
|
*/
|
|
5
21
|
export interface IdmUserResponse {
|
|
6
|
-
/**
|
|
22
|
+
/** Unique username used for login / authentication */
|
|
7
23
|
username: string;
|
|
8
|
-
/**
|
|
24
|
+
/** Unique, immutable user identifier (UUID) */
|
|
9
25
|
id: string;
|
|
10
|
-
/**
|
|
26
|
+
/** User's email address */
|
|
11
27
|
email: string;
|
|
12
|
-
/**
|
|
28
|
+
/** User's first name */
|
|
13
29
|
firstname: string;
|
|
14
|
-
/**
|
|
30
|
+
/** User's last name */
|
|
15
31
|
lastname: string;
|
|
16
|
-
/** Whether the user account is enabled
|
|
32
|
+
/** Whether the user account is currently enabled and active */
|
|
17
33
|
enabled: boolean;
|
|
18
|
-
/**
|
|
34
|
+
/** Tenant identifier this user belongs to (multi-tenancy discriminator) */
|
|
19
35
|
tenant: string;
|
|
20
|
-
/**
|
|
36
|
+
/** Granted authorities / permission roles assigned to the user */
|
|
21
37
|
authorities: string[];
|
|
22
|
-
/**
|
|
38
|
+
/** Pre-formatted display name from the backend (e.g. "Lastname, Firstname (username)") */
|
|
23
39
|
displayName: string;
|
|
24
40
|
}
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Represents an
|
|
3
|
-
*
|
|
2
|
+
* Represents an entity in the organization set — either a user or a role.
|
|
3
|
+
*
|
|
4
|
+
* This is the unified result type returned by {@link IdmService.queryOrganizationEntity}.
|
|
5
|
+
* It normalizes both user and role search results into a common shape so consumers
|
|
6
|
+
* (autocomplete fields, assignment dialogs, permission pickers) can treat them uniformly.
|
|
7
|
+
*
|
|
8
|
+
* **Mapping from backend:**
|
|
9
|
+
* ```
|
|
10
|
+
* User → { id: user.id, title: "Lastname, Firstname (username)", type: 'user' }
|
|
11
|
+
* Role → { id: role.name, title: role.name, type: 'role' }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **Usage:**
|
|
15
|
+
* Typically used in autocomplete/search components where a user can assign
|
|
16
|
+
* a document or task to either a person or a role:
|
|
17
|
+
* ```ts
|
|
18
|
+
* idmService.queryOrganizationEntity('admin', ['user', 'role'])
|
|
19
|
+
* .subscribe((entries: OrganizationSetEntry[]) => {
|
|
20
|
+
* // entries contains both matching users and roles
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @see {@link IdmService.queryOrganizationEntity} for the search method
|
|
4
25
|
*/
|
|
5
26
|
export interface OrganizationSetEntry {
|
|
6
|
-
/**
|
|
27
|
+
/** Unique identifier — user UUID for users, role name for roles */
|
|
7
28
|
id: string;
|
|
8
|
-
/**
|
|
29
|
+
/** Human-readable display title for the entity */
|
|
9
30
|
title: string;
|
|
10
|
-
/**
|
|
31
|
+
/** Discriminator indicating whether this entry is a user or a role */
|
|
11
32
|
type: 'user' | 'role';
|
|
12
33
|
}
|