@7365admin1/layer-common 4.82.1-staging.472 → 4.82.1-staging.473

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.
@@ -2331,11 +2331,59 @@ function applyFacialResult(
2331
2331
  }
2332
2332
  }
2333
2333
 
2334
+ /**
2335
+ * THE IDENTITY ROW BEHIND A ROSTER ROW.
2336
+ *
2337
+ * This list is read from the READER (`getReaderUsers`), and `listReaderUsers`
2338
+ * builds each row out of the device's own `users` columns - it carries no
2339
+ * identity `_id` at all, so `toHidUser` leaves `_id` undefined for every row on
2340
+ * screen. A delete that only acted when `_id` was present therefore never
2341
+ * touched the database: the device user was destroyed and the identity stayed
2342
+ * `active`, with no error, because the guard was an `if` rather than a failure.
2343
+ *
2344
+ * That left the row orphaned, and an orphan is not inert. `findIdentityBySubject`
2345
+ * excludes only `status: "deleted"`, so it still answers "this person is already
2346
+ * enrolled on this reader" and REFUSES to enrol somebody the operator can no
2347
+ * longer see. The reconcile reads it too: `findProfileIdentity` finds the active
2348
+ * row and reuses its stored `hidUserId`, so the device user is rebuilt with the
2349
+ * same UID the operator just deleted, from an access grant that was never
2350
+ * removed because `deleteIdentity` never ran.
2351
+ *
2352
+ * Resolved the same way `saveUser` already resolves it, by the reader's own keys.
2353
+ */
2354
+ async function findIdentityIdForUser(readerId: string, user: HidUser) {
2355
+ if (user._id) return user._id;
2356
+ if (!readerId) return "";
2357
+ try {
2358
+ const identity = await findExistingIdentityOnReader(readerId, {
2359
+ hidUserId: String(user.hidUserId ?? ""),
2360
+ registration: String(user.registration ?? ""),
2361
+ cardNo: String(user.cardNo ?? ""),
2362
+ metadata: {},
2363
+ });
2364
+ return identity?._id ?? "";
2365
+ } catch (error: unknown) {
2366
+ // A lookup failure must not stop the device delete the operator asked for.
2367
+ // The row stays behind and the next delete can clear it.
2368
+ console.error(
2369
+ "Unable to resolve the stored identity for this HID user:",
2370
+ error
2371
+ );
2372
+ return "";
2373
+ }
2374
+ }
2375
+
2334
2376
  async function deleteUser() {
2335
2377
  if (!selectedUser.value) return;
2336
2378
 
2337
2379
  deleting.value = true;
2338
2380
  try {
2381
+ // Resolved BEFORE the device user is destroyed: this reads our own records,
2382
+ // and doing it first means a failure here costs nothing.
2383
+ const identityId = await findIdentityIdForUser(
2384
+ selectedReaderId.value,
2385
+ selectedUser.value
2386
+ );
2339
2387
  await syncHidUserRole(
2340
2388
  selectedReaderId.value,
2341
2389
  toHidNumericId(selectedUser.value.hidUserId),
@@ -2346,8 +2394,10 @@ async function deleteUser() {
2346
2394
  toHidNumericId(selectedUser.value.hidUserId)
2347
2395
  );
2348
2396
  await deleteHidUserFromReader(selectedReaderId.value, selectedUser.value);
2349
- if (selectedUser.value._id) {
2350
- await deleteIdentity(selectedUser.value._id);
2397
+ if (identityId) {
2398
+ // Soft-deletes the row AND removes the access grant that let this person
2399
+ // through, which is what stops the next reconcile rebuilding them.
2400
+ await deleteIdentity(identityId);
2351
2401
  }
2352
2402
  deleteDialog.value = false;
2353
2403
  await loadUsers();
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.82.1-staging.472",
5
+ "version": "4.82.1-staging.473",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",