@byline/host-tanstack-start 3.10.1 → 3.11.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.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { createServerFn } from '@tanstack/react-start'
10
+
11
+ import { assertAdminActor } from '@byline/admin'
12
+ import { ADMIN_ACTIVITY_ABILITIES } from '@byline/admin/admin-activity'
13
+ import { ERR_AUDIT_UNSUPPORTED, getLogger, getServerConfig } from '@byline/core'
14
+
15
+ import { getAdminRequestContext } from '../../auth/auth-context.js'
16
+ import { type ActorLabelMap, resolveActorLabels } from '../collections/actors.js'
17
+ import type { AuditLogEntryDto } from '../collections/audit.js'
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // System-wide activity report (docs/AUDIT.md — Workstream 4)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** Filters for the activity feed. `from` / `to` are ISO strings over the wire. */
24
+ export interface SystemActivitySearchParams {
25
+ actorId?: string
26
+ /** Collection **path** (the admin works in paths; the handler resolves it to the stored collection id). */
27
+ collection?: string
28
+ action?: string
29
+ from?: string
30
+ to?: string
31
+ page?: number
32
+ page_size?: number
33
+ }
34
+
35
+ /** Collection display info, captured at query time (a row may name a collection that was later renamed/removed). */
36
+ export interface ActivityCollectionInfo {
37
+ path: string
38
+ singular: string
39
+ plural: string
40
+ }
41
+ export type ActivityCollectionMap = Record<string, ActivityCollectionInfo>
42
+
43
+ export interface SystemActivityResponse {
44
+ entries: AuditLogEntryDto[]
45
+ meta: { total: number; page: number; pageSize: number; totalPages: number }
46
+ /** Acting-user id → display label. Absent ids (system rows, deleted users) render a tombstone. */
47
+ actors: ActorLabelMap
48
+ /** Collection id → display info. Absent ids name a removed collection. */
49
+ collections: ActivityCollectionMap
50
+ }
51
+
52
+ /**
53
+ * The system-wide activity feed: the union of the version stream (content
54
+ * saves) and the audit log (status / path / locale changes, deletions, and
55
+ * future admin-realm events). Unlike the per-document audit fn, this is NOT
56
+ * routed through `CollectionHandle` — it is cross-collection and includes
57
+ * admin-realm rows with no `document_id`, so it reads the adapter's audit
58
+ * queries directly and is gated system-wide by `admin.activity.read` rather
59
+ * than by any document's own read gate.
60
+ */
61
+ export const getSystemActivityLog = createServerFn({ method: 'GET' })
62
+ .validator((input: SystemActivitySearchParams) => input ?? {})
63
+ .handler(async ({ data }): Promise<SystemActivityResponse> => {
64
+ const context = await getAdminRequestContext()
65
+ // System-wide gate — independent of any collection ability. An auditor
66
+ // role holds this without holding content read/write.
67
+ assertAdminActor(context, ADMIN_ACTIVITY_ABILITIES.read)
68
+
69
+ const queries = getServerConfig().db.queries
70
+ if (queries.audit == null) {
71
+ throw ERR_AUDIT_UNSUPPORTED({
72
+ message: 'the configured db adapter does not support audit-log reads (queries.audit)',
73
+ }).log(getLogger())
74
+ }
75
+
76
+ // The admin works in collection paths; the audit rows store the collection
77
+ // id. Resolve path → id here. An unknown path yields no id, so the filter
78
+ // simply matches nothing rather than erroring.
79
+ let collectionId: string | undefined
80
+ if (data.collection) {
81
+ const col = await queries.collections.getCollectionByPath(data.collection)
82
+ collectionId = col?.id
83
+ if (collectionId == null) {
84
+ return {
85
+ entries: [],
86
+ meta: { total: 0, page: 1, pageSize: 0, totalPages: 0 },
87
+ actors: {},
88
+ collections: {},
89
+ }
90
+ }
91
+ }
92
+
93
+ const result = await queries.audit.findAuditLog({
94
+ actorId: data.actorId,
95
+ collectionId,
96
+ action: data.action,
97
+ from: data.from ? new Date(data.from) : undefined,
98
+ to: data.to ? new Date(data.to) : undefined,
99
+ page: data.page,
100
+ page_size: data.page_size,
101
+ })
102
+
103
+ // Acting-user labels (admin realm), resolved here from each row's raw
104
+ // actorId — same helper the per-document audit view uses. System/tooling
105
+ // rows (NULL actorId) and deleted users are absent from the map; the UI
106
+ // renders the tombstone label.
107
+ const actors: ActorLabelMap = await resolveActorLabels(result.entries.map((e) => e.actorId))
108
+
109
+ // Collection display info, keyed by the raw collectionId carried on each
110
+ // row. Resolved once from the current collection set; a row whose
111
+ // collection was since removed is simply absent (the log outlives it).
112
+ const collections: ActivityCollectionMap = {}
113
+ const collectionIds = new Set(
114
+ result.entries.map((e) => e.collectionId).filter((id): id is string => id != null)
115
+ )
116
+ if (collectionIds.size > 0) {
117
+ const all = await queries.collections.getAllCollections()
118
+ for (const c of all) {
119
+ if (collectionIds.has(c.id)) {
120
+ collections[c.id] = { path: c.path, singular: c.singular, plural: c.plural }
121
+ }
122
+ }
123
+ }
124
+
125
+ const entries: AuditLogEntryDto[] = result.entries.map((e) => ({
126
+ id: e.id,
127
+ documentId: e.documentId,
128
+ collectionId: e.collectionId,
129
+ actorId: e.actorId,
130
+ actorRealm: e.actorRealm,
131
+ action: e.action,
132
+ field: e.field,
133
+ before: e.before as string | string[] | null,
134
+ after: e.after as string | string[] | null,
135
+ occurredAt: e.occurredAt instanceof Date ? e.occurredAt.toISOString() : String(e.occurredAt),
136
+ }))
137
+
138
+ return { entries, meta: result.meta, actors, collections }
139
+ })
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Admin-activity server fns (docs/AUDIT.md — Workstream 4) — the
11
+ * system-wide activity report. Reads the adapter's audit queries directly
12
+ * (cross-collection, includes admin-realm rows) behind the
13
+ * `admin.activity.read` gate, rather than routing through a per-document
14
+ * read like the collections audit fn.
15
+ */
16
+
17
+ export {
18
+ type ActivityCollectionInfo,
19
+ type ActivityCollectionMap,
20
+ getSystemActivityLog,
21
+ type SystemActivityResponse,
22
+ type SystemActivitySearchParams,
23
+ } from './get.js'