@endora-commerce/mod-audit-logs 0.100.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/admin/index.d.ts +34 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +39 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/AuditLogViewer.d.ts +3 -0
  8. package/dist/admin/pages/AuditLogViewer.d.ts.map +1 -0
  9. package/dist/admin/pages/AuditLogViewer.js +112 -0
  10. package/dist/admin/pages/AuditLogViewer.js.map +1 -0
  11. package/dist/backend/cli/read.d.ts +20 -0
  12. package/dist/backend/cli/read.d.ts.map +1 -0
  13. package/dist/backend/cli/read.js +94 -0
  14. package/dist/backend/cli/read.js.map +1 -0
  15. package/dist/backend/commands/recent-activity-visibility.commands.d.ts +31 -0
  16. package/dist/backend/commands/recent-activity-visibility.commands.d.ts.map +1 -0
  17. package/dist/backend/commands/recent-activity-visibility.commands.js +55 -0
  18. package/dist/backend/commands/recent-activity-visibility.commands.js.map +1 -0
  19. package/dist/backend/index.d.ts +91 -0
  20. package/dist/backend/index.d.ts.map +1 -0
  21. package/dist/backend/index.js +117 -0
  22. package/dist/backend/index.js.map +1 -0
  23. package/dist/backend/routes.admin.d.ts +29 -0
  24. package/dist/backend/routes.admin.d.ts.map +1 -0
  25. package/dist/backend/routes.admin.js +85 -0
  26. package/dist/backend/routes.admin.js.map +1 -0
  27. package/dist/backend/routes.admin.recent-activity.d.ts +126 -0
  28. package/dist/backend/routes.admin.recent-activity.d.ts.map +1 -0
  29. package/dist/backend/routes.admin.recent-activity.js +106 -0
  30. package/dist/backend/routes.admin.recent-activity.js.map +1 -0
  31. package/dist/backend/services/audit-reference-registry.d.ts +28 -0
  32. package/dist/backend/services/audit-reference-registry.d.ts.map +1 -0
  33. package/dist/backend/services/audit-reference-registry.js +57 -0
  34. package/dist/backend/services/audit-reference-registry.js.map +1 -0
  35. package/dist/backend/services/recent-activity-catalog.d.ts +92 -0
  36. package/dist/backend/services/recent-activity-catalog.d.ts.map +1 -0
  37. package/dist/backend/services/recent-activity-catalog.js +82 -0
  38. package/dist/backend/services/recent-activity-catalog.js.map +1 -0
  39. package/dist/backend/services/recent-activity-service.d.ts +105 -0
  40. package/dist/backend/services/recent-activity-service.d.ts.map +1 -0
  41. package/dist/backend/services/recent-activity-service.js +308 -0
  42. package/dist/backend/services/recent-activity-service.js.map +1 -0
  43. package/dist/backend/services/recent-activity-visibility.d.ts +37 -0
  44. package/dist/backend/services/recent-activity-visibility.d.ts.map +1 -0
  45. package/dist/backend/services/recent-activity-visibility.js +81 -0
  46. package/dist/backend/services/recent-activity-visibility.js.map +1 -0
  47. package/dist/manifest.d.ts +191 -0
  48. package/dist/manifest.d.ts.map +1 -0
  49. package/dist/manifest.js +123 -0
  50. package/dist/manifest.js.map +1 -0
  51. package/docs/audit_logs.md +47 -0
  52. package/i18n/en.json +5 -0
  53. package/i18n/pl.json +5 -0
  54. package/package.json +86 -0
  55. package/tailwind.css +14 -0
@@ -0,0 +1,92 @@
1
+ import { type KnownIconName, type ModuleManifest, type ModuleRecentActivity } from '@endora-commerce/contracts';
2
+ /**
3
+ * The recent-activity catalog — feature 080, T042j / D-163.1.
4
+ *
5
+ * **This file is what replaced four hand-maintained tables**, and it is worth
6
+ * saying which four, because their shapes were different and only one of them
7
+ * looked like a list:
8
+ *
9
+ * 1. `action-catalog.ts`'s `RECENT_ACTIVITY_ACTIONS` — 23 tokens, the
10
+ * dashboard query's `$in`. A token missing from it was **silently absent**
11
+ * from the card rather than refused, which is the whole of D-163's finding:
12
+ * a package could never appear there.
13
+ * 2. The same file's `PREFIX_TO_MODULE` plus `RecentActivityModule`, a **closed
14
+ * union of four core module ids**. A closed union is a list written down,
15
+ * and a package's id could not be in it.
16
+ * 3. `routes.admin.recent-activity.ts`'s `module: z.enum([...])`, three members.
17
+ * 4. The admin's `ACTIVITY_RENDERING`, 22 entries mapping a token to a
18
+ * lucide icon and an i18n key.
19
+ *
20
+ * All four are computed from one input now: the `recentActivity` export of each
21
+ * composed module's `manifest.ts`. The **drift already standing inside core**
22
+ * is repaired by that rather than by a fifth entry — `prompt_action.execute`
23
+ * was in (1) and (2) and absent from (3) and (4), so a prompt-assistant row was
24
+ * fetched, classified, and then rendered as an unknown verb against a response
25
+ * schema that did not list the `module` value the server emitted.
26
+ *
27
+ * What this class does **not** decide is whether a declared module's rows
28
+ * appear. That is the operator's axis (see {@link RecentActivityVisibility}),
29
+ * read per request from the module's own Setting and defaulting to visible.
30
+ * Neither axis is derivable from the other, which is Constitution XVII's shape
31
+ * applied to a narrower object.
32
+ */
33
+ /** The two properties of a resolved registry entry this derivation reads. */
34
+ export interface RecentActivityDeclarationSource {
35
+ readonly manifest: Pick<ModuleManifest, 'id' | 'name'>;
36
+ readonly recentActivity?: ModuleRecentActivity | undefined;
37
+ }
38
+ /** One declared action, with everything the card and the route need about it. */
39
+ export interface RecentActivityDescriptor {
40
+ /** The `audit_log_entries.action` token. */
41
+ readonly action: string;
42
+ /** The module that declared it — what used to be `RecentActivityModule`. */
43
+ readonly moduleId: string;
44
+ /** Operator-facing module name, for the visibility screen. */
45
+ readonly moduleName: string;
46
+ readonly icon: KnownIconName;
47
+ /** Relative to `moduleId`'s i18n namespace — the admin resolves it there. */
48
+ readonly labelKey: string;
49
+ }
50
+ /** One eligible module, and the Setting that decides whether it is shown. */
51
+ export interface RecentActivityModuleEntry {
52
+ readonly moduleId: string;
53
+ readonly moduleName: string;
54
+ readonly settingCode: string;
55
+ readonly actions: readonly string[];
56
+ }
57
+ /**
58
+ * Two modules declaring one action token.
59
+ *
60
+ * Refused rather than resolved by declaration order, for the reason a package
61
+ * claiming a taken module id is refused (D-155.7): the token decides which
62
+ * module's visibility Setting governs a row and which namespace its verb is
63
+ * looked up in, so picking a winner silently attributes one module's activity
64
+ * to another, and an operator switching the loser off would change nothing.
65
+ */
66
+ export declare class RecentActivityActionConflict extends Error {
67
+ readonly name = "RecentActivityActionConflict";
68
+ }
69
+ export declare class RecentActivityCatalog {
70
+ private readonly byAction;
71
+ private readonly modules;
72
+ constructor(sources: ReadonlyArray<RecentActivityDeclarationSource>);
73
+ /** Every declared action token, sorted so the derived route enum is stable. */
74
+ actions(): string[];
75
+ /** Every declaring module's id — what `RecentActivityModule` used to enumerate. */
76
+ moduleIds(): string[];
77
+ /** Every eligible module, in composition order. */
78
+ eligibleModules(): readonly RecentActivityModuleEntry[];
79
+ /**
80
+ * The descriptor for an action token, or `undefined` for one nobody declared.
81
+ *
82
+ * `undefined` rather than a throw, unlike the `moduleForAction` this replaced:
83
+ * that function's own comment called an unmapped token "defensive:
84
+ * unreachable as long as the allowlist and the prefix table stay aligned",
85
+ * and they were not aligned. A row can only get here by being in the query's
86
+ * `$in`, which is built from this same map — but an operator flipping a
87
+ * module off between the two is a real interleaving, and taking the dashboard
88
+ * down for it would be worse than omitting a row.
89
+ */
90
+ descriptorFor(action: string): RecentActivityDescriptor | undefined;
91
+ }
92
+ //# sourceMappingURL=recent-activity-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-activity-catalog.d.ts","sourceRoot":"","sources":["../../../src/backend/services/recent-activity-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC5D;AAED,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACvC,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,6EAA6E;AAC7E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;;GAQG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,SAAkB,IAAI,kCAAkC;CACzD;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;IACzE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;gBAEnD,OAAO,EAAE,aAAa,CAAC,+BAA+B,CAAC;IAwCnE,+EAA+E;IAC/E,OAAO,IAAI,MAAM,EAAE;IAInB,mFAAmF;IACnF,SAAS,IAAI,MAAM,EAAE;IAIrB,mDAAmD;IACnD,eAAe,IAAI,SAAS,yBAAyB,EAAE;IAIvD;;;;;;;;;;OAUG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS;CAGpE"}
@@ -0,0 +1,82 @@
1
+ import { recentActivityVisibilitySettingCode, } from '@endora-commerce/contracts';
2
+ /**
3
+ * Two modules declaring one action token.
4
+ *
5
+ * Refused rather than resolved by declaration order, for the reason a package
6
+ * claiming a taken module id is refused (D-155.7): the token decides which
7
+ * module's visibility Setting governs a row and which namespace its verb is
8
+ * looked up in, so picking a winner silently attributes one module's activity
9
+ * to another, and an operator switching the loser off would change nothing.
10
+ */
11
+ export class RecentActivityActionConflict extends Error {
12
+ name = 'RecentActivityActionConflict';
13
+ }
14
+ export class RecentActivityCatalog {
15
+ byAction;
16
+ modules;
17
+ constructor(sources) {
18
+ const byAction = new Map();
19
+ const modules = [];
20
+ for (const source of sources) {
21
+ const declaration = source.recentActivity;
22
+ if (!declaration)
23
+ continue;
24
+ const moduleId = source.manifest.id;
25
+ const moduleName = source.manifest.name;
26
+ const actions = [];
27
+ for (const entry of declaration.entries) {
28
+ const claimed = byAction.get(entry.action);
29
+ if (claimed) {
30
+ throw new RecentActivityActionConflict(`[audit_logs] modules "${claimed.moduleId}" and "${moduleId}" both declare the ` +
31
+ `recent-activity action "${entry.action}". The token decides whose visibility ` +
32
+ `Setting governs the row and in whose i18n namespace its verb is resolved, so ` +
33
+ `there is no winner to pick: one of the two declarations is wrong.`);
34
+ }
35
+ byAction.set(entry.action, {
36
+ action: entry.action,
37
+ moduleId,
38
+ moduleName,
39
+ icon: entry.icon,
40
+ labelKey: entry.labelKey,
41
+ });
42
+ actions.push(entry.action);
43
+ }
44
+ modules.push({
45
+ moduleId,
46
+ moduleName,
47
+ // Derived, never declared — see the contracts helper's note.
48
+ settingCode: recentActivityVisibilitySettingCode(moduleId),
49
+ actions,
50
+ });
51
+ }
52
+ this.byAction = byAction;
53
+ this.modules = modules;
54
+ }
55
+ /** Every declared action token, sorted so the derived route enum is stable. */
56
+ actions() {
57
+ return [...this.byAction.keys()].sort();
58
+ }
59
+ /** Every declaring module's id — what `RecentActivityModule` used to enumerate. */
60
+ moduleIds() {
61
+ return this.modules.map((entry) => entry.moduleId).sort();
62
+ }
63
+ /** Every eligible module, in composition order. */
64
+ eligibleModules() {
65
+ return this.modules;
66
+ }
67
+ /**
68
+ * The descriptor for an action token, or `undefined` for one nobody declared.
69
+ *
70
+ * `undefined` rather than a throw, unlike the `moduleForAction` this replaced:
71
+ * that function's own comment called an unmapped token "defensive:
72
+ * unreachable as long as the allowlist and the prefix table stay aligned",
73
+ * and they were not aligned. A row can only get here by being in the query's
74
+ * `$in`, which is built from this same map — but an operator flipping a
75
+ * module off between the two is a real interleaving, and taking the dashboard
76
+ * down for it would be worse than omitting a row.
77
+ */
78
+ descriptorFor(action) {
79
+ return this.byAction.get(action);
80
+ }
81
+ }
82
+ //# sourceMappingURL=recent-activity-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-activity-catalog.js","sourceRoot":"","sources":["../../../src/backend/services/recent-activity-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,GAIpC,MAAM,4BAA4B,CAAC;AA6DpC;;;;;;;;GAQG;AACH,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACnC,IAAI,GAAG,8BAA8B,CAAC;CACzD;AAED,MAAM,OAAO,qBAAqB;IACf,QAAQ,CAAgD;IACxD,OAAO,CAAuC;IAE/D,YAAY,OAAuD;QACjE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoC,CAAC;QAC7D,MAAM,OAAO,GAAgC,EAAE,CAAC;QAChD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,CAAC;YAC1C,IAAI,CAAC,WAAW;gBAAE,SAAS;YAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YACxC,MAAM,OAAO,GAAa,EAAE,CAAC;YAC7B,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;gBACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC3C,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,IAAI,4BAA4B,CACpC,yBAAyB,OAAO,CAAC,QAAQ,UAAU,QAAQ,qBAAqB;wBAC9E,2BAA2B,KAAK,CAAC,MAAM,wCAAwC;wBAC/E,+EAA+E;wBAC/E,mEAAmE,CACtE,CAAC;gBACJ,CAAC;gBACD,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE;oBACzB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,QAAQ;oBACR,UAAU;oBACV,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;iBACzB,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ;gBACR,UAAU;gBACV,6DAA6D;gBAC7D,WAAW,EAAE,mCAAmC,CAAC,QAAQ,CAAC;gBAC1D,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,+EAA+E;IAC/E,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,mFAAmF;IACnF,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,CAAC;IAED,mDAAmD;IACnD,eAAe;QACb,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;;;;;;OAUG;IACH,aAAa,CAAC,MAAc;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,105 @@
1
+ import type { AuditReferenceRegistryPort, KnownIconName } from '@endora-commerce/contracts';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { AuditActorIdentity } from '../routes.admin.js';
4
+ import type { RecentActivityCatalog } from './recent-activity-catalog.js';
5
+ import type { RecentActivityVisibility } from './recent-activity-visibility.js';
6
+ /**
7
+ * Feature 024 — Dashboard "Recent Activity" read service.
8
+ *
9
+ * Curated read view over `audit_log_entries` that powers the admin home
10
+ * dashboard's Recent Activity card. Server-enforced allowlist of action
11
+ * tokens; bulk-resolves actor + target display labels in at most a small
12
+ * fixed number of round-trips (one per object-type bucket).
13
+ *
14
+ * **Where the action list comes from — feature 080, T042j / D-163.1.** It used
15
+ * to come from `action-catalog.ts`: a 23-token array a host maintained by hand,
16
+ * a prefix table beside it, and a `RecentActivityModule` union closed over four
17
+ * core module ids. A module the host had not written could not appear in any of
18
+ * the three, so a package's audit row was **silently absent** from this card
19
+ * rather than refused. Both are derived from the composed manifests now, and
20
+ * what a row's `module` is comes from the module that declared its token.
21
+ *
22
+ * The declaration is only half of it. Whether a declared module's rows appear
23
+ * is the operator's, read per request through {@link RecentActivityVisibility}
24
+ * — per request, because a flip must take effect without a restart, and because
25
+ * a captured answer is a preference frozen at boot.
26
+ *
27
+ * **Where the labels come from — feature 075, D-87.** They used to come from
28
+ * five hand-written SQL statements naming `admin_users`, `products`,
29
+ * `warehouses`, `price_lists`, `customer_accounts` and `organizations`, and the
30
+ * deep-link URLs from a template map spelling three other modules' admin routes.
31
+ * Both are inverted now: the actor name is `auditActorResolver`, the
32
+ * contribution point this module has owned since feature 072, and everything
33
+ * else is `auditReferenceRegistry`, where each owner pushes a resolver for its
34
+ * own object type and supplies its own route. This service knows the audit row
35
+ * and nothing else about anybody's schema.
36
+ */
37
+ export interface RecentActivityItem {
38
+ id: string;
39
+ actedAt: string;
40
+ action: string;
41
+ /** The module that declared this action token. Any composed module's id. */
42
+ module: string;
43
+ /**
44
+ * What the card renders for this row, from the declaring module's manifest.
45
+ *
46
+ * They travel on the item because the admin's `ACTIVITY_RENDERING` — the
47
+ * fourth hand-maintained table — was the only thing that knew them, and it
48
+ * knew nothing about a module the SPA was not built with. `labelKey` is
49
+ * relative to `module`'s i18n namespace, so the card resolves it as
50
+ * `t(module, labelKey)` and a package's verb arrives in the operator's
51
+ * language from the package's own bundle.
52
+ */
53
+ icon: KnownIconName;
54
+ labelKey: string;
55
+ actorDisplayName: string;
56
+ actorKind: 'admin' | 'system';
57
+ targetType: string;
58
+ targetId: string;
59
+ targetDisplayName: string;
60
+ targetUrl: string | null;
61
+ summary: Record<string, unknown> | null;
62
+ }
63
+ export interface RecentActivityResponse {
64
+ data: RecentActivityItem[];
65
+ pagination: {
66
+ limit: number;
67
+ fetchedAt: string;
68
+ };
69
+ }
70
+ export interface ListRecentActivityInput {
71
+ /** Caller-supplied limit. Clamped to [1, 12] server-side. Defaults to 8. */
72
+ limit?: number;
73
+ }
74
+ export declare class RecentActivityService {
75
+ private readonly emFactory;
76
+ /** Every declared action token, derived from the composed manifests. */
77
+ private readonly catalog;
78
+ /** The operator's half of D-163.1, asked per request. */
79
+ private readonly visibility;
80
+ /**
81
+ * The contribution point this module owns and defaults absent. Called per
82
+ * request, never captured: a resolver held once would go on naming actors
83
+ * after the directory behind it went away.
84
+ */
85
+ private readonly resolveActors;
86
+ private readonly references;
87
+ constructor(emFactory: () => EntityManager,
88
+ /** Every declared action token, derived from the composed manifests. */
89
+ catalog: RecentActivityCatalog,
90
+ /** The operator's half of D-163.1, asked per request. */
91
+ visibility: RecentActivityVisibility,
92
+ /**
93
+ * The contribution point this module owns and defaults absent. Called per
94
+ * request, never captured: a resolver held once would go on naming actors
95
+ * after the directory behind it went away.
96
+ */
97
+ resolveActors: (ids: string[]) => Promise<AuditActorIdentity[]>, references: AuditReferenceRegistryPort);
98
+ list(input?: ListRecentActivityInput): Promise<RecentActivityResponse>;
99
+ /**
100
+ * Bulk-resolve actor + target labels for the given rows in at most one
101
+ * SQL round-trip per object-type bucket. Never N+1.
102
+ */
103
+ private enrich;
104
+ }
105
+ //# sourceMappingURL=recent-activity-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-activity-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/recent-activity-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,0BAA0B,EAC1B,aAAa,EACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAXV,SAAS,EAAE,MAAM,aAAa;IAC/C,wEAAwE;IACvD,OAAO,EAAE,qBAAqB;IAC/C,yDAAyD;IACxC,UAAU,EAAE,wBAAwB;IACrD;;;;OAIG;IACc,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,EAC/D,UAAU,EAAE,0BAA0B;IAGnD,IAAI,CAAC,KAAK,GAAE,uBAA4B,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAkChF;;;OAGG;YACW,MAAM;CA8BrB"}
@@ -0,0 +1,308 @@
1
+ import { AuditLogEntry } from '@endora-commerce/platform/kernel';
2
+ const DEFAULT_LIMIT = 8;
3
+ const MAX_LIMIT = 12;
4
+ export class RecentActivityService {
5
+ emFactory;
6
+ catalog;
7
+ visibility;
8
+ resolveActors;
9
+ references;
10
+ constructor(emFactory,
11
+ /** Every declared action token, derived from the composed manifests. */
12
+ catalog,
13
+ /** The operator's half of D-163.1, asked per request. */
14
+ visibility,
15
+ /**
16
+ * The contribution point this module owns and defaults absent. Called per
17
+ * request, never captured: a resolver held once would go on naming actors
18
+ * after the directory behind it went away.
19
+ */
20
+ resolveActors, references) {
21
+ this.emFactory = emFactory;
22
+ this.catalog = catalog;
23
+ this.visibility = visibility;
24
+ this.resolveActors = resolveActors;
25
+ this.references = references;
26
+ }
27
+ async list(input = {}) {
28
+ const limit = clampLimit(input.limit);
29
+ // The conjunction of the two axes. Empty is answered here rather than by
30
+ // an unfiltered query: with every eligible module hidden, `$in: []` and
31
+ // "no filter" differ by the whole audit trail.
32
+ const actions = await this.visibility.visibleActions();
33
+ if (actions.length === 0) {
34
+ return { data: [], pagination: { limit, fetchedAt: new Date().toISOString() } };
35
+ }
36
+ const em = this.emFactory();
37
+ // The existing `actedAt DESC` index handles the sort; the secondary `id DESC`
38
+ // tiebreak (research §R2) gives the dashboard deterministic ordering when
39
+ // two admins act inside the same second. Row count is bounded by `limit`,
40
+ // so the extra sort key has negligible cost at our volumes.
41
+ const rows = await em.find(AuditLogEntry, { action: { $in: actions } }, {
42
+ orderBy: { actedAt: 'desc', id: 'desc' },
43
+ limit,
44
+ });
45
+ const enriched = await this.enrich(rows);
46
+ return {
47
+ data: enriched,
48
+ pagination: { limit, fetchedAt: new Date().toISOString() },
49
+ };
50
+ }
51
+ /**
52
+ * Bulk-resolve actor + target labels for the given rows in at most one
53
+ * SQL round-trip per object-type bucket. Never N+1.
54
+ */
55
+ async enrich(rows) {
56
+ if (rows.length === 0)
57
+ return [];
58
+ // --- Actor labels: this module's own contribution point. ---
59
+ const adminIds = uniqueNonNull(rows.map((r) => r.actorAdminUserId ?? null));
60
+ const adminNames = adminIds.length > 0 ? abbreviate(await this.resolveActors(adminIds)) : new Map();
61
+ // --- Every other label: one bucket per reference type, one round trip each. ---
62
+ const [customerNames, ...targetBuckets] = await Promise.all([
63
+ this.references.resolve('customer_account', uniqueNonNull(rows.map((r) => r.impersonatedCustomerAccountId ?? null))),
64
+ ...TARGET_REFERENCE_TYPES.map((type) => this.references.resolve(type, uniqueByType(rows, type))),
65
+ ]);
66
+ // Ids are UUIDs, so one map across the target types collides with nothing
67
+ // and keeps `resolveTarget` reading the row it was handed.
68
+ const targets = new Map();
69
+ for (const bucket of targetBuckets) {
70
+ for (const [id, label] of bucket)
71
+ targets.set(id, label);
72
+ }
73
+ return rows
74
+ .map((r) => buildItem(r, this.catalog, { adminNames, customerNames, targets }))
75
+ .filter((item) => item !== null);
76
+ }
77
+ }
78
+ /**
79
+ * The audit `objectType` values an owner module can put a name and a link on.
80
+ *
81
+ * A type nobody claims resolves to an empty bucket, which is the same answer as
82
+ * a deleted row: the snapshot label, and no link.
83
+ */
84
+ const TARGET_REFERENCE_TYPES = ['product', 'warehouse', 'price_list'];
85
+ /**
86
+ * "Jan Kowalski" → "Jan K.". The abbreviation is the dashboard card's, which is
87
+ * why it lives here and not in the module that owns the directory: `admin_users`
88
+ * answers who somebody is, and how much of that fits on one line of a widget is
89
+ * this surface's question.
90
+ */
91
+ function abbreviate(identities) {
92
+ const map = new Map();
93
+ for (const identity of identities) {
94
+ const first = (identity.firstName ?? '').trim();
95
+ const last = (identity.lastName ?? '').trim();
96
+ const initial = last.length > 0 ? `${last.slice(0, 1)}.` : '';
97
+ const display = [first, initial].filter((s) => s.length > 0).join(' ').trim() || identity.id;
98
+ map.set(identity.id, display);
99
+ }
100
+ return map;
101
+ }
102
+ function clampLimit(value) {
103
+ if (value === undefined || !Number.isFinite(value))
104
+ return DEFAULT_LIMIT;
105
+ const n = Math.floor(value);
106
+ if (n < 1)
107
+ return 1;
108
+ if (n > MAX_LIMIT)
109
+ return MAX_LIMIT;
110
+ return n;
111
+ }
112
+ function uniqueNonNull(arr) {
113
+ const set = new Set();
114
+ for (const v of arr)
115
+ if (v !== null && v !== undefined)
116
+ set.add(v);
117
+ return [...set];
118
+ }
119
+ function uniqueByType(rows, objectType) {
120
+ const set = new Set();
121
+ for (const r of rows)
122
+ if (r.objectType === objectType && r.objectId)
123
+ set.add(r.objectId);
124
+ return [...set];
125
+ }
126
+ /**
127
+ * `null` for a token no composed module declares.
128
+ *
129
+ * The row can only be here by having matched the `$in` built from the same
130
+ * catalog, so this is unreachable in a stable composition — but the alternative
131
+ * is `moduleForAction`'s, which threw with a comment calling itself
132
+ * "defensive: unreachable as long as the allowlist and the prefix table stay
133
+ * aligned". They were not aligned, and a throw here would take the whole card
134
+ * down for one row.
135
+ */
136
+ function buildItem(r, catalog, lookups) {
137
+ const action = r.action;
138
+ const descriptor = catalog.descriptorFor(action);
139
+ if (!descriptor)
140
+ return null;
141
+ // --- Actor ---
142
+ const actorKind = r.actorAdminUserId ? 'admin' : 'system';
143
+ let actorDisplayName;
144
+ if (r.actorAdminUserId) {
145
+ actorDisplayName = lookups.adminNames.get(r.actorAdminUserId) ?? 'Admin';
146
+ }
147
+ else {
148
+ actorDisplayName = 'System';
149
+ }
150
+ if (r.impersonatedCustomerAccountId) {
151
+ const customerLabel = lookups.customerNames.get(r.impersonatedCustomerAccountId)?.label ??
152
+ r.impersonatedCustomerAccountId;
153
+ actorDisplayName = `${actorDisplayName} (as ${customerLabel})`;
154
+ }
155
+ // --- Target display + URL ---
156
+ const { targetDisplayName, targetUrl } = resolveTarget(r, lookups);
157
+ return {
158
+ id: r.id,
159
+ actedAt: r.actedAt.toISOString(),
160
+ action,
161
+ module: descriptor.moduleId,
162
+ icon: descriptor.icon,
163
+ labelKey: descriptor.labelKey,
164
+ actorDisplayName,
165
+ actorKind,
166
+ targetType: r.objectType,
167
+ targetId: r.objectId,
168
+ targetDisplayName,
169
+ targetUrl,
170
+ summary: extractSummary(r),
171
+ };
172
+ }
173
+ function resolveTarget(r, lookups) {
174
+ const before = (r.stateBefore ?? null);
175
+ const after = (r.stateAfter ?? null);
176
+ switch (r.objectType) {
177
+ case 'product': {
178
+ // The snapshot wins over the live row, and always has: the card reports
179
+ // what the action did, so a rename shows the name the actor typed.
180
+ const fromSnapshot = pickProductLabel(after) ?? pickProductLabel(before);
181
+ const live = lookups.targets.get(r.objectId);
182
+ return {
183
+ targetDisplayName: fromSnapshot ?? live?.label ?? r.objectId,
184
+ targetUrl: live?.url ?? null,
185
+ };
186
+ }
187
+ case 'warehouse':
188
+ case 'price_list': {
189
+ const live = lookups.targets.get(r.objectId);
190
+ const fromSnapshot = pickStr(after, 'name') ?? pickStr(before, 'name');
191
+ const fromSnapshotCode = pickStr(after, 'code') ?? pickStr(before, 'code');
192
+ return {
193
+ targetDisplayName: fromSnapshot ?? live?.label ?? fromSnapshotCode ?? r.objectId,
194
+ targetUrl: live?.url ?? null,
195
+ };
196
+ }
197
+ case 'stock_level': {
198
+ // Composite id "<productId>:<warehouseId>"; snapshot carries the
199
+ // human-readable parts.
200
+ const productSku = pickStr(after, 'productSku') ?? pickStr(before, 'productSku');
201
+ const warehouseCode = pickStr(after, 'warehouseCode') ?? pickStr(before, 'warehouseCode');
202
+ const display = productSku && warehouseCode
203
+ ? `${productSku} @ ${warehouseCode}`
204
+ : productSku ?? warehouseCode ?? r.objectId;
205
+ const productId = pickStr(after, 'productId') ?? pickStr(before, 'productId');
206
+ const url = productId ? `/catalog/products/${productId}` : null;
207
+ return { targetDisplayName: display, targetUrl: url };
208
+ }
209
+ case 'low_stock_threshold': {
210
+ const display = pickStr(after, 'productSku') ?? pickStr(before, 'productSku') ?? 'threshold';
211
+ const productId = pickStr(after, 'productId') ?? pickStr(before, 'productId');
212
+ const url = productId ? `/catalog/products/${productId}` : '/inventory/low-stock';
213
+ return { targetDisplayName: display, targetUrl: url };
214
+ }
215
+ case 'prompt_action_request': {
216
+ // Feature 043 — prompt-assistant execution summary. The snapshot
217
+ // carries the original prompt; surface a trimmed form of it.
218
+ const prompt = pickStr(after, 'prompt');
219
+ const display = prompt
220
+ ? prompt.length > 80
221
+ ? `${prompt.slice(0, 77)}…`
222
+ : prompt
223
+ : 'prompt action';
224
+ // No standalone detail page in v1 — outcomes live in the palette.
225
+ return { targetDisplayName: display, targetUrl: null };
226
+ }
227
+ case 'bulk_operation': {
228
+ const fileName = pickStr(after, 'fileName');
229
+ const warehouseCode = pickStr(after, 'warehouseCode') ?? pickStr(before, 'warehouseCode');
230
+ // The last fallback used to test `r.action === 'product.bulk_update'` and
231
+ // render the untranslated English string "bulk product update" for it.
232
+ // Two things wrong with one line, and both are D-163's shape one layer
233
+ // down: a host file naming another module's action token, and a
234
+ // user-visible sentence with no `pl`. The id is what every other
235
+ // unlabelled row falls back to, including this switch's own `default`.
236
+ const display = fileName && warehouseCode ? `${fileName} → ${warehouseCode}` : fileName ?? r.objectId;
237
+ // Bulk operations don't have a single canonical detail page in v1.
238
+ return { targetDisplayName: display, targetUrl: null };
239
+ }
240
+ default:
241
+ return { targetDisplayName: r.objectId, targetUrl: null };
242
+ }
243
+ }
244
+ function pickProductLabel(snapshot) {
245
+ if (!snapshot)
246
+ return null;
247
+ const name = snapshot['name'];
248
+ if (typeof name === 'string' && name.length > 0)
249
+ return name;
250
+ if (name && typeof name === 'object') {
251
+ const byLang = name;
252
+ // Try common language fallbacks before iterating.
253
+ for (const key of ['en', 'en-US', 'pl', 'pl-PL']) {
254
+ const v = byLang[key];
255
+ if (typeof v === 'string' && v.length > 0)
256
+ return v;
257
+ }
258
+ for (const v of Object.values(byLang)) {
259
+ if (typeof v === 'string' && v.length > 0)
260
+ return v;
261
+ }
262
+ }
263
+ const sku = snapshot['sku'];
264
+ if (typeof sku === 'string' && sku.length > 0)
265
+ return sku;
266
+ return null;
267
+ }
268
+ function pickStr(snapshot, key) {
269
+ if (!snapshot)
270
+ return null;
271
+ const v = snapshot[key];
272
+ return typeof v === 'string' && v.length > 0 ? v : null;
273
+ }
274
+ function extractSummary(r) {
275
+ // Surface only a compact summary for bulk-style rows. Never the full
276
+ // stateBefore/stateAfter blobs (FR-030).
277
+ //
278
+ // **The key list is the filter**, and it always was. There used to be a
279
+ // pre-test above it — `objectType !== 'bulk_operation' && action !==
280
+ // 'price_list.products_replace'` — which is a second, hand-written answer to
281
+ // "which rows are bulk-shaped", drifting against the very list below it and
282
+ // naming another module's action token in this host file (D-163's shape, one
283
+ // layer down from the four tables T042j retired). FR-030 is satisfied by the
284
+ // allow-list: a row that carries none of these keys still returns `null`,
285
+ // and a row that carries one is bulk-shaped by the only evidence there is.
286
+ const after = r.stateAfter;
287
+ if (!after)
288
+ return null;
289
+ const keys = [
290
+ 'fileName',
291
+ 'warehouseId',
292
+ 'warehouseCode',
293
+ 'rowsProcessed',
294
+ 'rowsSkipped',
295
+ 'rowsErrored',
296
+ 'added',
297
+ 'removed',
298
+ 'kept',
299
+ 'touchedFields',
300
+ ];
301
+ const out = {};
302
+ for (const k of keys) {
303
+ if (k in after)
304
+ out[k] = after[k];
305
+ }
306
+ return Object.keys(out).length > 0 ? out : null;
307
+ }
308
+ //# sourceMappingURL=recent-activity-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-activity-service.js","sourceRoot":"","sources":["../../../src/backend/services/recent-activity-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
2
+ import type { RecentActivityCatalog } from './recent-activity-catalog.js';
3
+ /** One module's answer, for the admin screen. */
4
+ export interface RecentActivityModuleVisibility {
5
+ moduleId: string;
6
+ moduleName: string;
7
+ visible: boolean;
8
+ }
9
+ export declare class RecentActivityVisibility {
10
+ private readonly catalog;
11
+ private readonly settings;
12
+ constructor(catalog: RecentActivityCatalog, settings: SettingsReadPort);
13
+ /**
14
+ * Every eligible module with the operator's current answer.
15
+ *
16
+ * One batch read, `salesChannelId: null` — the choice is platform-wide, for
17
+ * the reason activation is (FR-009): a home dashboard is not per-storefront,
18
+ * and joining `setting_values` would reintroduce channel dependence through
19
+ * the back door (Constitution XII).
20
+ *
21
+ * A code the settings store does not know resolves to **visible**, not to
22
+ * hidden. The row is created by the boot reconcile for a shipped module and
23
+ * by `install` for a package, so its absence means one has not run yet — a
24
+ * platform mid-install, not an operator's decision — and the ruling's default
25
+ * is the honest answer to "nobody has chosen".
26
+ */
27
+ list(): Promise<RecentActivityModuleVisibility[]>;
28
+ /**
29
+ * The action tokens the card may query — the conjunction of the two axes.
30
+ *
31
+ * Empty when every eligible module is hidden, and the caller must not turn
32
+ * that into "no filter": an unfiltered query would put every audited write on
33
+ * the dashboard, which is the opposite of what the operator asked for.
34
+ */
35
+ visibleActions(): Promise<string[]>;
36
+ }
37
+ //# sourceMappingURL=recent-activity-visibility.d.ts.map