@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 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAI5E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAI5D,OAAO,EACL,qBAAqB,EACrB,KAAK,+BAA+B,EACrC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,kBAAkB,EACvB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,GAClD,SAAS,CAAC;IACd,QAAQ,CAAC,sBAAsB,EAAE,0BAA0B,CAAC;IAC5D,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;IACtD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;IACtD,QAAQ,CAAC,wBAAwB,EAAE,wBAAwB,CAAC;IAC5D;;;;;;;OAOG;IACH,QAAQ,CAAC,sBAAsB,EAAE,aAAa,CAAC,+BAA+B,CAAC,CAAC;IAChF,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;CAC7C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiIvD;AAGD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,KAAK,EAAO,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { effectiveState } from '@endora-commerce/platform/kernel';
3
+ import { ModuleDisabledError } from '@endora-commerce/platform/kernel';
4
+ import { registerAuditLogAdminRoutes } from './routes.admin.js';
5
+ import { registerRecentActivityRoutes } from './routes.admin.recent-activity.js';
6
+ import { AuditReferenceRegistry } from './services/audit-reference-registry.js';
7
+ import { RecentActivityCatalog, } from './services/recent-activity-catalog.js';
8
+ import { RecentActivityService } from './services/recent-activity-service.js';
9
+ import { RecentActivityVisibility } from './services/recent-activity-visibility.js';
10
+ export function registerModule(ctx) {
11
+ /**
12
+ * Turning an actor id into a name, through the contribution point above.
13
+ *
14
+ * One closure, two readers — the audit-log list and the dashboard card — so
15
+ * there is exactly one place that decides what an absent directory means, and
16
+ * exactly one `catch` for a reviewer to check. The dashboard card used to
17
+ * decide it for itself, by running `select … from admin_users` (feature 075,
18
+ * D-87).
19
+ */
20
+ const resolveActors = async (ids) => {
21
+ const resolve = ctx.cradle().auditActorResolver;
22
+ if (resolve === undefined)
23
+ return [];
24
+ try {
25
+ return await resolve(ids);
26
+ }
27
+ catch (error) {
28
+ // The enrichment is optional; the record is not. Anything else is
29
+ // a real failure and must not be swallowed into a blank column.
30
+ if (error instanceof ModuleDisabledError)
31
+ return [];
32
+ throw error;
33
+ }
34
+ };
35
+ ctx.di.register({
36
+ /**
37
+ * Who can turn one of *their* ids into a name and a link (feature 075,
38
+ * D-87). A **contribution seam**, so a plain `ctx.di.register`: contributors
39
+ * push from their own boot hooks, and a gate here would refuse them at a
40
+ * point in the boot where there is nothing to answer.
41
+ *
42
+ * The presence predicate is this module's enumeration policy — skip — and it
43
+ * is asked per read rather than per registration, so an operator's flip
44
+ * takes effect without a restart.
45
+ */
46
+ auditReferenceRegistry: ctx
47
+ .asFunction(() => new AuditReferenceRegistry((moduleId) => effectiveState.isPresent(moduleId)))
48
+ .singleton(),
49
+ /**
50
+ * The four host-owned action tables, as one derivation (D-163.1).
51
+ *
52
+ * A singleton, and legitimately so: the composed manifest set does not
53
+ * change while the process runs — an installed package is discovered before
54
+ * the first module registers — so what is captured here is a fact about the
55
+ * build. The *operator's* half is captured nowhere and is asked per request,
56
+ * which is the half that moves.
57
+ */
58
+ recentActivityCatalog: ctx
59
+ .asFunction(({ resolvedModuleRegistry }) => new RecentActivityCatalog(resolvedModuleRegistry))
60
+ .singleton(),
61
+ /**
62
+ * The operator's half, and the reason `settingsReadPort` arrives as a
63
+ * `lazyPort` rather than off the cradle: this registration is a singleton,
64
+ * and a cradle parameter would resolve the settings reader **once**, when
65
+ * the registration is first constructed. Awilix's strict mode refuses that
66
+ * capture and `check:port-dependencies` reports it — rightly, since a
67
+ * captured reader is a preference frozen at composition, which is exactly
68
+ * what an operator flipping a switch must not meet.
69
+ */
70
+ recentActivityVisibility: ctx
71
+ .asFunction(({ recentActivityCatalog }) => new RecentActivityVisibility(recentActivityCatalog, lazyPort(ctx, 'settingsReadPort')))
72
+ .singleton(),
73
+ recentActivityService: ctx
74
+ .asFunction(({ emFactory, auditReferenceRegistry, recentActivityCatalog, recentActivityVisibility, }) => new RecentActivityService(emFactory, recentActivityCatalog, recentActivityVisibility, resolveActors, auditReferenceRegistry))
75
+ .singleton(),
76
+ // Contribution point, defaulted to absent by its owner. A deployment
77
+ // without `admin_users` reads the audit log with raw actor ids rather than
78
+ // failing to read it.
79
+ auditActorResolver: ctx
80
+ .asFunction(() => undefined)
81
+ .singleton(),
82
+ });
83
+ ctx.routes(async (app) => {
84
+ const { auditLogService, recentActivityService, recentActivityCatalog, recentActivityVisibility, requireAdmin, commandBus, } = ctx.cradle();
85
+ await registerAuditLogAdminRoutes(app, {
86
+ auditLogService,
87
+ requireAdmin,
88
+ // Resolved per request, and per request is what makes the degradation
89
+ // work: a contributor captured once would keep naming actors long after
90
+ // its module went away.
91
+ resolveActors,
92
+ });
93
+ await registerRecentActivityRoutes(app, {
94
+ recentActivityService,
95
+ recentActivityCatalog,
96
+ recentActivityVisibility,
97
+ requireAdmin,
98
+ ...(commandBus ? { commandBus } : {}),
99
+ });
100
+ });
101
+ }
102
+ /**
103
+ * This module owns **no persisted entity**, and says so with an empty array
104
+ * rather than by omission (D-168).
105
+ *
106
+ * The two are not the same thing to the platform. When the package is
107
+ * *installed*, `src/packages/package-runtime.ts` reads `exported['entities']`
108
+ * and answers a missing export with `[]` — so "this module has no table" and
109
+ * "somebody forgot the array" arrive at the host as the same silence, and the
110
+ * only symptom of the second is a query against a table nobody created. The
111
+ * declaration is what makes the first case a statement.
112
+ *
113
+ * A first entity added here goes in this array in the same merge request, and
114
+ * `test/unit/packages/module-package-entity-surface.test.ts` is what says so.
115
+ */
116
+ export const entities = [];
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ /**
5
+ * Admin Audit Log query route (T195 / FR-084).
6
+ *
7
+ * Always reads — never mutates — the append-only audit log. Filters are
8
+ * accepted as query params (`filter[actor]`, `filter[action]`, …); a
9
+ * default `limit=100` keeps the page lightweight, capped at 500.
10
+ */
11
+ /** Minimal actor identity used to enrich the audit-log actor column. */
12
+ export interface AuditActorIdentity {
13
+ id: string;
14
+ firstName: string;
15
+ lastName: string;
16
+ email: string;
17
+ }
18
+ export interface AuditLogAdminDeps {
19
+ auditLogService: AuditPort;
20
+ requireAdmin: RequireAdminFactory;
21
+ /**
22
+ * Resolves admin-user identities for the actor column (name + email instead
23
+ * of a bare id). Unknown ids may be omitted. Optional — when absent the
24
+ * column falls back to the raw id.
25
+ */
26
+ resolveActors?: (ids: string[]) => Promise<AuditActorIdentity[]>;
27
+ }
28
+ export declare function registerAuditLogAdminRoutes(app: FastifyInstance, deps: AuditLogAdminDeps): Promise<void>;
29
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;GAMG;AAEH,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,SAAS,CAAC;IAC3B,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;CAClE;AAED,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
@@ -0,0 +1,85 @@
1
+ export async function registerAuditLogAdminRoutes(app, deps) {
2
+ const { auditLogService, requireAdmin, resolveActors } = deps;
3
+ app.get('/api/v1/admin/audit-log', { preHandler: requireAdmin('audit_log:read') }, async (request) => {
4
+ const q = (request.query ?? {});
5
+ const limitNum = q['limit'] ? Number.parseInt(q['limit'], 10) : undefined;
6
+ const entries = await auditLogService.query({
7
+ ...(q['filter[actor]'] ? { actorAdminUserId: q['filter[actor]'] } : {}),
8
+ ...(q['filter[customer]']
9
+ ? { impersonatedCustomerAccountId: q['filter[customer]'] }
10
+ : {}),
11
+ ...(q['filter[action]'] ? { action: q['filter[action]'] } : {}),
12
+ ...(q['filter[objectType]'] ? { objectType: q['filter[objectType]'] } : {}),
13
+ ...(q['filter[objectId]'] ? { objectId: q['filter[objectId]'] } : {}),
14
+ ...(limitNum !== undefined && Number.isFinite(limitNum) ? { limit: limitNum } : {}),
15
+ });
16
+ // Enrich the actor column with name + email (one batched lookup).
17
+ const actorIds = entries
18
+ .map((e) => e.actorAdminUserId)
19
+ .filter((id) => Boolean(id));
20
+ const actors = resolveActors ? await resolveActors(actorIds) : [];
21
+ const actorById = new Map(actors.map((a) => [a.id, a]));
22
+ return {
23
+ data: entries.map((e) => serialize(e, actorById)),
24
+ pagination: { cursor: null, hasMore: false, limit: entries.length },
25
+ };
26
+ });
27
+ }
28
+ function serialize(e, actorById) {
29
+ const actor = e.actorAdminUserId ? actorById.get(e.actorAdminUserId) : undefined;
30
+ return {
31
+ id: e.id,
32
+ actorAdminUserId: e.actorAdminUserId ?? null,
33
+ actorName: actor ? `${actor.firstName} ${actor.lastName}`.trim() : null,
34
+ actorEmail: actor?.email ?? null,
35
+ impersonatedCustomerAccountId: e.impersonatedCustomerAccountId ?? null,
36
+ actedAt: e.actedAt.toISOString(),
37
+ action: e.action,
38
+ actionModuleId: moduleIdForAuditAction(e.action),
39
+ objectType: e.objectType,
40
+ objectId: e.objectId,
41
+ stateBefore: e.stateBefore ?? null,
42
+ stateAfter: e.stateAfter ?? null,
43
+ ipAddress: e.ipAddress ?? null,
44
+ userAgent: e.userAgent ?? null,
45
+ requestId: e.requestId ?? null,
46
+ };
47
+ }
48
+ function moduleIdForAuditAction(action) {
49
+ if (action.startsWith('setting.'))
50
+ return 'settings';
51
+ if (action.startsWith('setting_group.'))
52
+ return 'settings';
53
+ if (action.startsWith('module.'))
54
+ return 'core';
55
+ if (action.startsWith('product.'))
56
+ return 'catalog';
57
+ if (action.startsWith('attribute_set.'))
58
+ return 'catalog';
59
+ if (action.startsWith('gallery.'))
60
+ return 'catalog';
61
+ if (action.startsWith('attachment.'))
62
+ return 'catalog';
63
+ if (action.startsWith('attachment_type.'))
64
+ return 'catalog';
65
+ if (action.startsWith('product_link.'))
66
+ return 'catalog';
67
+ if (action.startsWith('grouped_item.'))
68
+ return 'catalog';
69
+ if (action.startsWith('bundle_slot.'))
70
+ return 'catalog';
71
+ if (action.startsWith('bundle_slot_option.'))
72
+ return 'catalog';
73
+ if (action.startsWith('sales_channel.'))
74
+ return 'sales_channels';
75
+ if (action.startsWith('api_key.'))
76
+ return 'core';
77
+ if (action.startsWith('impersonation.'))
78
+ return 'core';
79
+ if (action.startsWith('order.'))
80
+ return 'core';
81
+ if (action.startsWith('organization.'))
82
+ return 'core';
83
+ return 'core';
84
+ }
85
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { z } from 'zod';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { RecentActivityCatalog } from './services/recent-activity-catalog.js';
5
+ import type { RecentActivityService } from './services/recent-activity-service.js';
6
+ import type { RecentActivityVisibility } from './services/recent-activity-visibility.js';
7
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
8
+ /**
9
+ * Feature 024 — `GET /api/v1/admin/audit-log/recent-activity`, and since
10
+ * feature 080's T042j the two routes behind the operator's control over it.
11
+ *
12
+ * **The response schema is derived** (D-163.1). `action` and `module` used to
13
+ * be `z.enum` over two host-written arrays — the second three members long,
14
+ * against a server that could emit four — so the contract and the server
15
+ * disagreed about `prompt_action.execute` for as long as feature 043 had
16
+ * shipped. Both enums are built from the composed modules' `recentActivity`
17
+ * declarations now, which is the same input the query's `$in` is built from, so
18
+ * they cannot disagree with it and a packaged module is in both.
19
+ *
20
+ * The **declaration** axis decides the schema and the **operator** axis decides
21
+ * the data, deliberately: a schema that narrowed to the currently-visible set
22
+ * would change shape when an operator flipped a switch, and a client holding
23
+ * the OpenAPI document would see a breaking change in a display preference.
24
+ *
25
+ * Permission: the same `audit_log:read` scope as the wider audit-log read
26
+ * endpoint for the card itself; `platform.modules.activate` for the visibility
27
+ * surface, which is the code that already governs this operator's per-module
28
+ * switches on the screen the control renders on.
29
+ */
30
+ export interface RecentActivityAdminDeps {
31
+ recentActivityService: RecentActivityService;
32
+ recentActivityCatalog: RecentActivityCatalog;
33
+ recentActivityVisibility: RecentActivityVisibility;
34
+ requireAdmin: RequireAdminFactory;
35
+ /**
36
+ * Omitted in a composition with no Command Bus — the card still renders,
37
+ * because reading the dashboard must not depend on being able to reconfigure
38
+ * it. The same shape `_lifecycle`'s activation write uses.
39
+ */
40
+ commandBus?: CommandBus;
41
+ }
42
+ export declare function buildRecentActivityResponseSchema(catalog: RecentActivityCatalog): z.ZodObject<{
43
+ data: z.ZodArray<z.ZodObject<{
44
+ id: z.ZodString;
45
+ actedAt: z.ZodString;
46
+ action: z.ZodType<string, unknown, z.core.$ZodTypeInternals<string, unknown>>;
47
+ module: z.ZodType<string, unknown, z.core.$ZodTypeInternals<string, unknown>>;
48
+ icon: z.ZodEnum<{
49
+ Plus: "Plus";
50
+ Sparkles: "Sparkles";
51
+ Settings: "Settings";
52
+ Search: "Search";
53
+ Boxes: "Boxes";
54
+ Layers: "Layers";
55
+ Menu: "Menu";
56
+ PlusCircle: "PlusCircle";
57
+ PlusSquare: "PlusSquare";
58
+ FilePlus: "FilePlus";
59
+ FolderPlus: "FolderPlus";
60
+ Upload: "Upload";
61
+ FileUp: "FileUp";
62
+ CloudUpload: "CloudUpload";
63
+ Download: "Download";
64
+ FileDown: "FileDown";
65
+ FileText: "FileText";
66
+ BookOpen: "BookOpen";
67
+ Rss: "Rss";
68
+ Package: "Package";
69
+ Tag: "Tag";
70
+ ShoppingCart: "ShoppingCart";
71
+ Receipt: "Receipt";
72
+ CreditCard: "CreditCard";
73
+ Users: "Users";
74
+ UserPlus: "UserPlus";
75
+ Inbox: "Inbox";
76
+ ListChecks: "ListChecks";
77
+ ClipboardList: "ClipboardList";
78
+ Image: "Image";
79
+ Video: "Video";
80
+ LayoutDashboard: "LayoutDashboard";
81
+ PanelLeft: "PanelLeft";
82
+ KeyRound: "KeyRound";
83
+ ShieldCheck: "ShieldCheck";
84
+ Edit: "Edit";
85
+ Archive: "Archive";
86
+ Box: "Box";
87
+ Truck: "Truck";
88
+ CircleDollarSign: "CircleDollarSign";
89
+ Activity: "Activity";
90
+ LineChart: "LineChart";
91
+ Smartphone: "Smartphone";
92
+ Webhook: "Webhook";
93
+ Scale: "Scale";
94
+ PlugZap: "PlugZap";
95
+ PercentDiamond: "PercentDiamond";
96
+ Newspaper: "Newspaper";
97
+ Languages: "Languages";
98
+ Eraser: "Eraser";
99
+ Warehouse: "Warehouse";
100
+ TrendingDown: "TrendingDown";
101
+ Bell: "Bell";
102
+ PackageOpen: "PackageOpen";
103
+ Building2: "Building2";
104
+ Store: "Store";
105
+ ClipboardCheck: "ClipboardCheck";
106
+ }>;
107
+ labelKey: z.ZodString;
108
+ actorDisplayName: z.ZodString;
109
+ actorKind: z.ZodEnum<{
110
+ admin: "admin";
111
+ system: "system";
112
+ }>;
113
+ targetType: z.ZodString;
114
+ targetId: z.ZodString;
115
+ targetDisplayName: z.ZodString;
116
+ targetUrl: z.ZodNullable<z.ZodString>;
117
+ summary: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
118
+ }, z.core.$strip>>;
119
+ pagination: z.ZodObject<{
120
+ limit: z.ZodNumber;
121
+ fetchedAt: z.ZodString;
122
+ }, z.core.$strip>;
123
+ }, z.core.$strip>;
124
+ export type RecentActivityRouteResponse = z.infer<ReturnType<typeof buildRecentActivityResponseSchema>>;
125
+ export declare function registerRecentActivityRoutes(app: FastifyInstance, deps: RecentActivityAdminDeps): Promise<void>;
126
+ //# sourceMappingURL=routes.admin.recent-activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.recent-activity.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.recent-activity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAGrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AACzF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAmBD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwB/E;AAED,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAC/C,UAAU,CAAC,OAAO,iCAAiC,CAAC,CACrD,CAAC;AAEF,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAuFf"}
@@ -0,0 +1,106 @@
1
+ import { z } from 'zod';
2
+ import { KnownIconNameSchema, RecentActivityVisibilityListSchema, RecentActivityVisibilityRequestSchema, RecentActivityVisibilityResponseSchema, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { makeSetRecentActivityVisibilityCommand } from './commands/recent-activity-visibility.commands.js';
5
+ const querySchema = z.object({
6
+ limit: z.coerce.number().int().min(1).max(12).optional(),
7
+ });
8
+ /**
9
+ * A closed enum where the composition declares something, a plain string where
10
+ * it declares nothing.
11
+ *
12
+ * `z.enum([])` is not a schema, and a bare-core build with every declaring
13
+ * module removed is a legitimate composition. The fallback describes the shape
14
+ * honestly rather than pretending to a closed set of nothing.
15
+ */
16
+ function tokenSchema(values) {
17
+ if (values.length === 0)
18
+ return z.string().min(1);
19
+ return z.enum(values);
20
+ }
21
+ export function buildRecentActivityResponseSchema(catalog) {
22
+ const itemSchema = z.object({
23
+ id: z.string().uuid(),
24
+ actedAt: z.string(),
25
+ action: tokenSchema(catalog.actions()),
26
+ module: tokenSchema(catalog.moduleIds()),
27
+ icon: KnownIconNameSchema,
28
+ labelKey: z.string().min(1),
29
+ actorDisplayName: z.string().min(1),
30
+ actorKind: z.enum(['admin', 'system']),
31
+ targetType: z.string().min(1),
32
+ targetId: z.string().min(1),
33
+ targetDisplayName: z.string().min(1),
34
+ targetUrl: z.string().nullable(),
35
+ summary: z.record(z.string(), z.unknown()).nullable(),
36
+ });
37
+ return z.object({
38
+ data: z.array(itemSchema),
39
+ pagination: z.object({
40
+ limit: z.number().int().min(1).max(12),
41
+ fetchedAt: z.string(),
42
+ }),
43
+ });
44
+ }
45
+ export async function registerRecentActivityRoutes(app, deps) {
46
+ const { recentActivityService, recentActivityCatalog, recentActivityVisibility, requireAdmin, } = deps;
47
+ app.get('/api/v1/admin/audit-log/recent-activity', { preHandler: requireAdmin('audit_log:read') }, async (request, reply) => {
48
+ const parsed = querySchema.safeParse(request.query ?? {});
49
+ if (!parsed.success) {
50
+ return reply.status(400).send({
51
+ error: {
52
+ code: 'validation_error',
53
+ message: 'Invalid query parameters',
54
+ details: parsed.error.flatten(),
55
+ },
56
+ });
57
+ }
58
+ const input = {};
59
+ if (parsed.data.limit !== undefined)
60
+ input.limit = parsed.data.limit;
61
+ return recentActivityService.list(input);
62
+ });
63
+ /**
64
+ * The operator axis of D-163.1, read.
65
+ *
66
+ * Gated on `platform.modules.activate` rather than on `audit_log:read`: this
67
+ * is a per-module operator switch, it renders beside the activation control
68
+ * on `/platform/modules`, and whoever may switch a module off may certainly
69
+ * decide whether its rows reach a dashboard. Declaring a second code for the
70
+ * same operator on the same screen would be a checkbox that grants nothing
71
+ * anybody would grant separately.
72
+ */
73
+ app.get('/api/v1/admin/audit-log/recent-activity/visibility', {
74
+ preHandler: requireAdmin('platform.modules.activate'),
75
+ schema: { response: { 200: RecentActivityVisibilityListSchema } },
76
+ }, async () => ({
77
+ modules: await recentActivityVisibility.list(),
78
+ }));
79
+ const commandBus = deps.commandBus;
80
+ if (!commandBus)
81
+ return;
82
+ app.post('/api/v1/admin/audit-log/recent-activity/visibility/:moduleId', {
83
+ preHandler: requireAdmin('platform.modules.activate'),
84
+ schema: {
85
+ body: RecentActivityVisibilityRequestSchema,
86
+ response: { 200: RecentActivityVisibilityResponseSchema },
87
+ },
88
+ }, async (request) => {
89
+ const body = RecentActivityVisibilityRequestSchema.parse(request.body);
90
+ const { moduleId } = request.params;
91
+ const result = await commandBus.run(makeSetRecentActivityVisibilityCommand(recentActivityCatalog, {
92
+ moduleId,
93
+ visible: body.visible,
94
+ }));
95
+ // Re-read rather than echo, for the reason the activation control does
96
+ // not update optimistically: what the operator sees is what the platform
97
+ // resolved, not what this handler assumed.
98
+ const row = (await recentActivityVisibility.list()).find((entry) => entry.moduleId === result.moduleId);
99
+ if (!row) {
100
+ throw new HttpError(500, 'INTERNAL', `Module "${result.moduleId}" vanished from the recent-activity catalog after its ` +
101
+ `dashboard visibility was written.`);
102
+ }
103
+ return { module: row };
104
+ });
105
+ }
106
+ //# sourceMappingURL=routes.admin.recent-activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.recent-activity.js","sourceRoot":"","sources":["../../src/backend/routes.admin.recent-activity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { AuditReferenceLabel, AuditReferenceRegistryPort, AuditReferenceResolver } from '@endora-commerce/contracts';
2
+ /**
3
+ * In-process registry of "what is this audit row about, and where does it live?"
4
+ * resolvers (feature 075, D-87 drain).
5
+ *
6
+ * Registered under `auditReferenceRegistry` and contributed to from other
7
+ * modules' boot hooks. `RecentActivityService` is the only reader today.
8
+ *
9
+ * **Enumeration policy: an absent contributor's resolver is skipped.** The
10
+ * reasoning is at `AuditReferenceRegistryPort` in `@endora-commerce/contracts`, and the
11
+ * short of it is that the descriptor carries a deep link into the contributor's
12
+ * own admin screen: a module that is not present contributes no screen, so
13
+ * nothing may link to one. The audit row still renders — with the label the
14
+ * entry's own snapshot carries, or the raw id, and no link.
15
+ *
16
+ * The predicate is injected rather than read from `effectiveState` here, because
17
+ * a registry that answers a presence question is exactly the thing a unit test
18
+ * has to be able to put in either state.
19
+ */
20
+ export declare class AuditReferenceRegistry implements AuditReferenceRegistryPort {
21
+ private readonly isModulePresent;
22
+ private readonly resolvers;
23
+ constructor(isModulePresent: (moduleId: string) => boolean);
24
+ register(resolver: AuditReferenceResolver): void;
25
+ owners(): readonly string[];
26
+ resolve(referenceType: string, ids: readonly string[]): Promise<ReadonlyMap<string, AuditReferenceLabel>>;
27
+ }
28
+ //# sourceMappingURL=audit-reference-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-reference-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/audit-reference-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,sBAAuB,YAAW,0BAA0B;IAG3D,OAAO,CAAC,QAAQ,CAAC,eAAe;IAF5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE7B,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO;IAE3E,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAehD,MAAM,IAAI,SAAS,MAAM,EAAE;IAIrB,OAAO,CACX,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAcrD"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * In-process registry of "what is this audit row about, and where does it live?"
3
+ * resolvers (feature 075, D-87 drain).
4
+ *
5
+ * Registered under `auditReferenceRegistry` and contributed to from other
6
+ * modules' boot hooks. `RecentActivityService` is the only reader today.
7
+ *
8
+ * **Enumeration policy: an absent contributor's resolver is skipped.** The
9
+ * reasoning is at `AuditReferenceRegistryPort` in `@endora-commerce/contracts`, and the
10
+ * short of it is that the descriptor carries a deep link into the contributor's
11
+ * own admin screen: a module that is not present contributes no screen, so
12
+ * nothing may link to one. The audit row still renders — with the label the
13
+ * entry's own snapshot carries, or the raw id, and no link.
14
+ *
15
+ * The predicate is injected rather than read from `effectiveState` here, because
16
+ * a registry that answers a presence question is exactly the thing a unit test
17
+ * has to be able to put in either state.
18
+ */
19
+ export class AuditReferenceRegistry {
20
+ isModulePresent;
21
+ resolvers = [];
22
+ constructor(isModulePresent) {
23
+ this.isModulePresent = isModulePresent;
24
+ }
25
+ register(resolver) {
26
+ if (this.resolvers.includes(resolver))
27
+ return;
28
+ const claimed = this.resolvers.find((r) => r.referenceType === resolver.referenceType);
29
+ if (claimed) {
30
+ // Two answers for one type is not a degrade to pick a winner from: one of
31
+ // the two modules is wrong about what it owns, and the reader would be
32
+ // rendering whichever boot hook happened to run first.
33
+ throw new Error(`Audit reference type '${resolver.referenceType}' is already resolved by ` +
34
+ `'${claimed.ownerModuleId}'; '${resolver.ownerModuleId}' may not claim it too.`);
35
+ }
36
+ this.resolvers.push(resolver);
37
+ }
38
+ owners() {
39
+ return this.resolvers.map((r) => r.ownerModuleId);
40
+ }
41
+ async resolve(referenceType, ids) {
42
+ const out = new Map();
43
+ if (ids.length === 0)
44
+ return out;
45
+ const resolver = this.resolvers.find((r) => r.referenceType === referenceType);
46
+ // Enumeration policy, applied at the read rather than at registration: an
47
+ // operator's flip has to take effect without a restart.
48
+ if (!resolver || !this.isModulePresent(resolver.ownerModuleId))
49
+ return out;
50
+ for (const row of await resolver.resolve(ids)) {
51
+ if (row.label.length > 0)
52
+ out.set(row.id, row);
53
+ }
54
+ return out;
55
+ }
56
+ }
57
+ //# sourceMappingURL=audit-reference-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-reference-registry.js","sourceRoot":"","sources":["../../../src/backend/services/audit-reference-registry.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,sBAAsB;IAGJ;IAFZ,SAAS,GAA6B,EAAE,CAAC;IAE1D,YAA6B,eAA8C;QAA9C,oBAAe,GAAf,eAAe,CAA+B;IAAG,CAAC;IAE/E,QAAQ,CAAC,QAAgC;QACvC,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,QAAQ,CAAC,aAAa,CAAC,CAAC;QACvF,IAAI,OAAO,EAAE,CAAC;YACZ,0EAA0E;YAC1E,uEAAuE;YACvE,uDAAuD;YACvD,MAAM,IAAI,KAAK,CACb,yBAAyB,QAAQ,CAAC,aAAa,2BAA2B;gBACxE,IAAI,OAAO,CAAC,aAAa,OAAO,QAAQ,CAAC,aAAa,yBAAyB,CAClF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,OAAO,CACX,aAAqB,EACrB,GAAsB;QAEtB,MAAM,GAAG,GAAG,IAAI,GAAG,EAA+B,CAAC;QACnD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;QAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,aAAa,CAAC,CAAC;QAC/E,0EAA0E;QAC1E,wDAAwD;QACxD,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,aAAa,CAAC;YAAE,OAAO,GAAG,CAAC;QAE3E,KAAK,MAAM,GAAG,IAAI,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9C,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}