@byline/host-tanstack-start 4.15.0 → 4.17.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.
- package/dist/admin-shell/chrome/admin-menu-drawer.d.ts +2 -1
- package/dist/admin-shell/chrome/admin-menu-drawer.js +17 -5
- package/dist/admin-shell/chrome/dashboard.js +41 -5
- package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
- package/dist/admin-shell/collections/api.d.ts +2 -2
- package/dist/admin-shell/collections/create.d.ts +2 -2
- package/dist/admin-shell/collections/create.js +1 -0
- package/dist/admin-shell/collections/edit.d.ts +2 -2
- package/dist/admin-shell/collections/edit.js +1 -0
- package/dist/admin-shell/collections/history.d.ts +4 -12
- package/dist/admin-shell/collections/history.js +112 -303
- package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
- package/dist/admin-shell/collections/reindex-button.js +5 -1
- package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
- package/dist/admin-shell/collections/restore-version-modal.js +8 -24
- package/dist/admin-shell/collections/version-history.d.ts +72 -0
- package/dist/admin-shell/collections/version-history.js +259 -0
- package/dist/admin-shell/singletons/history.d.ts +19 -0
- package/dist/admin-shell/singletons/history.js +170 -0
- package/dist/admin-shell/singletons/history.test.d.ts +8 -0
- package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
- package/dist/admin-shell/singletons/preview-link.js +54 -0
- package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
- package/dist/admin-shell/singletons/view-menu.js +116 -0
- package/dist/admin-shell/singletons/view.d.ts +18 -0
- package/dist/admin-shell/singletons/view.js +259 -0
- package/dist/admin-shell/singletons/view.test.d.ts +8 -0
- package/dist/integrations/analytics-agent.d.ts +20 -0
- package/dist/integrations/analytics-agent.js +57 -0
- package/dist/integrations/analytics-events.d.ts +51 -0
- package/dist/integrations/analytics-events.js +129 -0
- package/dist/integrations/api-utils.d.ts +18 -4
- package/dist/integrations/api-utils.js +10 -3
- package/dist/integrations/serve-analytics-agent.d.ts +8 -0
- package/dist/integrations/serve-analytics-agent.js +11 -0
- package/dist/routes/analytics-range.d.ts +12 -0
- package/dist/routes/analytics-range.js +35 -0
- package/dist/routes/create-admin-analytics-route.d.ts +8 -0
- package/dist/routes/create-admin-analytics-route.js +108 -0
- package/dist/routes/create-admin-dashboard-route.js +2 -2
- package/dist/routes/create-admin-layout-route.js +10 -4
- package/dist/routes/create-collection-api-route.js +3 -3
- package/dist/routes/create-collection-create-route.js +4 -3
- package/dist/routes/create-collection-edit-route.js +4 -3
- package/dist/routes/create-collection-history-route.js +4 -3
- package/dist/routes/create-collection-list-route.js +4 -3
- package/dist/routes/create-singleton-history-route.d.ts +8 -0
- package/dist/routes/create-singleton-history-route.js +99 -0
- package/dist/routes/create-singleton-route.d.ts +35 -0
- package/dist/routes/create-singleton-route.js +130 -0
- package/dist/routes/get-multi-collection-definition.d.ts +10 -0
- package/dist/routes/get-multi-collection-definition.js +6 -0
- package/dist/routes/get-singleton-definition.d.ts +10 -0
- package/dist/routes/get-singleton-definition.js +6 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +3 -0
- package/dist/server-fns/actors.d.ts +16 -0
- package/dist/server-fns/actors.js +25 -0
- package/dist/server-fns/admin-document-presentation.d.ts +30 -0
- package/dist/server-fns/admin-document-presentation.js +32 -0
- package/dist/server-fns/analytics/index.d.ts +9 -0
- package/dist/server-fns/analytics/index.js +2 -0
- package/dist/server-fns/analytics/maintenance.d.ts +22 -0
- package/dist/server-fns/analytics/maintenance.js +42 -0
- package/dist/server-fns/analytics/queries.d.ts +25 -0
- package/dist/server-fns/analytics/queries.js +54 -0
- package/dist/server-fns/collections/actors.d.ts +1 -10
- package/dist/server-fns/collections/actors.js +1 -25
- package/dist/server-fns/collections/get.js +9 -26
- package/dist/server-fns/collections/reorder.js +1 -1
- package/dist/server-fns/collections/scheduled-publication.js +2 -2
- package/dist/server-fns/collections/upload.js +3 -3
- package/dist/server-fns/collections/utils.d.ts +1 -7
- package/dist/server-fns/collections/utils.js +1 -4
- package/dist/server-fns/serialise.d.ts +14 -0
- package/dist/server-fns/serialise.js +4 -0
- package/dist/server-fns/singleton-document-read.d.ts +20 -0
- package/dist/server-fns/singleton-document-read.js +10 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/change-status.d.ts +14 -0
- package/dist/server-fns/singletons/change-status.js +7 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
- package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
- package/dist/server-fns/singletons/copy-to-locale.js +11 -0
- package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
- package/dist/server-fns/singletons/find-by-version.js +9 -0
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/get.d.ts +24 -0
- package/dist/server-fns/singletons/get.js +6 -0
- package/dist/server-fns/singletons/history.d.ts +31 -0
- package/dist/server-fns/singletons/history.js +22 -0
- package/dist/server-fns/singletons/index.d.ts +19 -0
- package/dist/server-fns/singletons/index.js +12 -0
- package/dist/server-fns/singletons/restore-version.d.ts +14 -0
- package/dist/server-fns/singletons/restore-version.js +7 -0
- package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
- package/dist/server-fns/singletons/schedule-publish.js +10 -0
- package/dist/server-fns/singletons/unpublish.d.ts +12 -0
- package/dist/server-fns/singletons/unpublish.js +7 -0
- package/dist/server-fns/singletons/update.d.ts +14 -0
- package/dist/server-fns/singletons/update.js +14 -0
- package/dist/vite/analytics-dev.d.ts +22 -0
- package/dist/vite/analytics-dev.js +28 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +1 -0
- package/dist/vitest-jsdom.test.d.ts +8 -0
- package/package.json +28 -16
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +19 -5
- package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
- package/src/admin-shell/chrome/dashboard.tsx +43 -9
- package/src/admin-shell/collections/api.tsx +2 -2
- package/src/admin-shell/collections/create.tsx +6 -2
- package/src/admin-shell/collections/edit.tsx +6 -2
- package/src/admin-shell/collections/history.module.css +1 -1
- package/src/admin-shell/collections/history.tsx +162 -442
- package/src/admin-shell/collections/reindex-button.tsx +4 -2
- package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
- package/src/admin-shell/collections/version-history.tsx +349 -0
- package/src/admin-shell/singletons/history.test.tsx +407 -0
- package/src/admin-shell/singletons/history.tsx +185 -0
- package/src/admin-shell/singletons/preview-link.tsx +75 -0
- package/src/admin-shell/singletons/view-menu.tsx +136 -0
- package/src/admin-shell/singletons/view.test.tsx +400 -0
- package/src/admin-shell/singletons/view.tsx +351 -0
- package/src/integrations/analytics-agent.tsx +91 -0
- package/src/integrations/analytics-events.test.node.ts +245 -0
- package/src/integrations/analytics-events.ts +224 -0
- package/src/integrations/api-utils.ts +34 -9
- package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
- package/src/integrations/serve-analytics-agent.ts +19 -0
- package/src/routes/analytics-range.ts +55 -0
- package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
- package/src/routes/create-admin-analytics-route.tsx +109 -0
- package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
- package/src/routes/create-admin-dashboard-route.tsx +10 -2
- package/src/routes/create-admin-layout-route.tsx +15 -8
- package/src/routes/create-collection-api-route.tsx +4 -4
- package/src/routes/create-collection-create-route.tsx +5 -8
- package/src/routes/create-collection-edit-route.tsx +5 -4
- package/src/routes/create-collection-history-route.tsx +5 -8
- package/src/routes/create-collection-list-route.tsx +5 -8
- package/src/routes/create-singleton-history-route.test.node.ts +148 -0
- package/src/routes/create-singleton-history-route.tsx +116 -0
- package/src/routes/create-singleton-route.test.node.ts +172 -0
- package/src/routes/create-singleton-route.tsx +176 -0
- package/src/routes/get-multi-collection-definition.ts +16 -0
- package/src/routes/get-singleton-definition.ts +16 -0
- package/src/routes/index.ts +3 -0
- package/src/server-fns/actors.ts +43 -0
- package/src/server-fns/admin-document-presentation.ts +78 -0
- package/src/server-fns/analytics/index.ts +23 -0
- package/src/server-fns/analytics/maintenance.ts +54 -0
- package/src/server-fns/analytics/queries.ts +86 -0
- package/src/server-fns/collections/actors.ts +1 -52
- package/src/server-fns/collections/get.ts +10 -49
- package/src/server-fns/collections/reorder.ts +1 -1
- package/src/server-fns/collections/scheduled-publication.ts +7 -1
- package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
- package/src/server-fns/collections/upload.ts +7 -3
- package/src/server-fns/collections/utils.ts +1 -9
- package/src/server-fns/serialise.ts +17 -0
- package/src/server-fns/singleton-document-read.ts +32 -0
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/change-status.ts +19 -0
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
- package/src/server-fns/singletons/copy-to-locale.ts +32 -0
- package/src/server-fns/singletons/find-by-version.ts +23 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/get.ts +32 -0
- package/src/server-fns/singletons/history.ts +41 -0
- package/src/server-fns/singletons/index.ts +20 -0
- package/src/server-fns/singletons/restore-version.ts +19 -0
- package/src/server-fns/singletons/schedule-publish.ts +24 -0
- package/src/server-fns/singletons/singletons.test.node.ts +298 -0
- package/src/server-fns/singletons/unpublish.ts +19 -0
- package/src/server-fns/singletons/update.ts +31 -0
- package/src/vite/analytics-dev.test.node.ts +126 -0
- package/src/vite/analytics-dev.ts +56 -0
- package/src/vite/index.ts +1 -0
- package/src/vitest-jsdom.test.tsx +27 -0
|
@@ -0,0 +1,172 @@
|
|
|
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 { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
10
|
+
|
|
11
|
+
const mocks = vi.hoisted(() => ({
|
|
12
|
+
definition: {
|
|
13
|
+
singleton: true,
|
|
14
|
+
path: 'site-settings',
|
|
15
|
+
label: 'Site settings',
|
|
16
|
+
fields: [{ name: 'title', type: 'text', defaultValue: 'Default title' }],
|
|
17
|
+
},
|
|
18
|
+
read: vi.fn(),
|
|
19
|
+
buildInitialData: vi.fn(),
|
|
20
|
+
getPublishedVersion: vi.fn(),
|
|
21
|
+
getRequestContext: vi.fn(),
|
|
22
|
+
getScheduledPublish: vi.fn(),
|
|
23
|
+
getCollectionRecord: vi.fn(),
|
|
24
|
+
scheduledPublicationEnabled: true,
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
vi.mock('@tanstack/react-start', () => ({
|
|
28
|
+
createServerFn: () => {
|
|
29
|
+
let validate = (input: unknown) => input
|
|
30
|
+
const chain = {
|
|
31
|
+
validator(validator: (input: unknown) => unknown) {
|
|
32
|
+
validate = validator
|
|
33
|
+
return chain
|
|
34
|
+
},
|
|
35
|
+
handler(handler: (options: { data: any }) => Promise<unknown>) {
|
|
36
|
+
return async (options: { data: unknown }) => handler({ data: validate(options.data) })
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
return chain
|
|
40
|
+
},
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
vi.mock('@tanstack/react-router', () => ({
|
|
44
|
+
createFileRoute: () => (options: Record<string, unknown>) => ({ options }),
|
|
45
|
+
notFound: () => new Error('not found'),
|
|
46
|
+
}))
|
|
47
|
+
|
|
48
|
+
vi.mock('@byline/client/server', () => ({
|
|
49
|
+
getAdminBylineClient: () => ({
|
|
50
|
+
singleton: () => ({ getScheduledPublish: mocks.getScheduledPublish }),
|
|
51
|
+
}),
|
|
52
|
+
getAdminRequestContext: mocks.getRequestContext,
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
vi.mock('@byline/core', () => ({
|
|
56
|
+
buildInitialDataFromFields: mocks.buildInitialData,
|
|
57
|
+
documentAbilityKey: (_definition: unknown, verb: string) => `singletons.site-settings.${verb}`,
|
|
58
|
+
getCollectionDefinition: (path: string) =>
|
|
59
|
+
path === mocks.definition.path ? mocks.definition : null,
|
|
60
|
+
isSingleton: (definition: { singleton?: boolean }) => definition.singleton === true,
|
|
61
|
+
getServerConfig: () => ({
|
|
62
|
+
i18n: { content: { defaultLocale: 'en' } },
|
|
63
|
+
scheduledPublication: { enabled: mocks.scheduledPublicationEnabled },
|
|
64
|
+
db: { queries: { documents: { getPublishedVersion: mocks.getPublishedVersion } } },
|
|
65
|
+
}),
|
|
66
|
+
getSingletonAdminConfig: () => null,
|
|
67
|
+
}))
|
|
68
|
+
|
|
69
|
+
vi.mock('../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js', () => ({
|
|
70
|
+
BreadcrumbsClient: () => null,
|
|
71
|
+
}))
|
|
72
|
+
vi.mock('../admin-shell/singletons/view.js', () => ({ SingletonView: () => null }))
|
|
73
|
+
vi.mock('../integrations/byline-core.js', () => ({
|
|
74
|
+
bylineCore: () => ({ getCollectionRecord: mocks.getCollectionRecord }),
|
|
75
|
+
}))
|
|
76
|
+
vi.mock('../server-fns/singleton-document-read.js', () => ({
|
|
77
|
+
readSingletonDocument: mocks.read,
|
|
78
|
+
}))
|
|
79
|
+
vi.mock('../server-fns/serialise.js', () => ({ serialise: (value: unknown) => value }))
|
|
80
|
+
vi.mock('@byline/i18n/react', () => ({ useTranslation: () => ({ t: (key: string) => key }) }))
|
|
81
|
+
vi.mock('./admin-path.js', () => ({ getAdminRoutePath: () => '/admin' }))
|
|
82
|
+
vi.mock('./get-content-locale-route-config.js', () => ({
|
|
83
|
+
getContentLocaleRouteConfig: () => ({ contentLocales: [], defaultContentLocale: 'en' }),
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
import { getSingletonRouteDocument } from './create-singleton-route.js'
|
|
87
|
+
|
|
88
|
+
type ServerFunction = (options: { data: { singleton: string; locale?: string } }) => Promise<any>
|
|
89
|
+
|
|
90
|
+
const load = (locale?: string) =>
|
|
91
|
+
(getSingletonRouteDocument as ServerFunction)({
|
|
92
|
+
data: { singleton: 'site-settings', locale },
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('singleton editor route loader', () => {
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
vi.clearAllMocks()
|
|
98
|
+
mocks.scheduledPublicationEnabled = true
|
|
99
|
+
mocks.buildInitialData.mockResolvedValue({ title: 'Default title' })
|
|
100
|
+
mocks.getCollectionRecord.mockReturnValue({ collectionId: 'collection-settings' })
|
|
101
|
+
mocks.getRequestContext.mockResolvedValue({
|
|
102
|
+
actor: {
|
|
103
|
+
hasAbility: (ability: string) =>
|
|
104
|
+
ability === 'singletons.site-settings.changeStatus' ||
|
|
105
|
+
ability === 'singletons.site-settings.publish',
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('resolves schema defaults without inventing a document for an empty slot', async () => {
|
|
111
|
+
mocks.read.mockResolvedValue(null)
|
|
112
|
+
|
|
113
|
+
await expect(load('fr')).resolves.toEqual({
|
|
114
|
+
document: null,
|
|
115
|
+
initialData: { title: 'Default title' },
|
|
116
|
+
})
|
|
117
|
+
expect(mocks.read).toHaveBeenCalledWith({
|
|
118
|
+
singleton: 'site-settings',
|
|
119
|
+
locale: 'fr',
|
|
120
|
+
populateRelations: true,
|
|
121
|
+
})
|
|
122
|
+
expect(mocks.buildInitialData).toHaveBeenCalledWith(mocks.definition.fields, { locale: 'fr' })
|
|
123
|
+
expect(mocks.getCollectionRecord).not.toHaveBeenCalled()
|
|
124
|
+
expect(mocks.getRequestContext).not.toHaveBeenCalled()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('attaches publication and schedule metadata to a mapped document server-side', async () => {
|
|
128
|
+
mocks.read.mockResolvedValue({
|
|
129
|
+
id: 'document-settings',
|
|
130
|
+
versionId: 'version-current',
|
|
131
|
+
status: 'draft',
|
|
132
|
+
fields: { title: 'Loaded' },
|
|
133
|
+
})
|
|
134
|
+
mocks.getPublishedVersion.mockResolvedValue({
|
|
135
|
+
document_id: 'document-settings',
|
|
136
|
+
document_version_id: 'version-published',
|
|
137
|
+
status: 'published',
|
|
138
|
+
created_at: '2026-08-25T00:00:00.000Z',
|
|
139
|
+
updated_at: '2026-08-25T01:00:00.000Z',
|
|
140
|
+
})
|
|
141
|
+
mocks.getScheduledPublish.mockResolvedValue({
|
|
142
|
+
state: 'armed',
|
|
143
|
+
publishAt: '2026-09-01T00:00:00.000Z',
|
|
144
|
+
targetVersionId: 'version-current',
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const result = await load()
|
|
148
|
+
|
|
149
|
+
expect(result.initialData).toBeUndefined()
|
|
150
|
+
expect(result.document).toMatchObject({
|
|
151
|
+
id: 'document-settings',
|
|
152
|
+
_publishedVersion: {
|
|
153
|
+
id: 'document-settings',
|
|
154
|
+
versionId: 'version-published',
|
|
155
|
+
status: 'published',
|
|
156
|
+
},
|
|
157
|
+
_scheduledPublicationEnabled: true,
|
|
158
|
+
_canSchedulePublication: true,
|
|
159
|
+
_scheduledPublish: {
|
|
160
|
+
state: 'armed',
|
|
161
|
+
targetVersionId: 'version-current',
|
|
162
|
+
},
|
|
163
|
+
scheduledPublishAt: '2026-09-01T00:00:00.000Z',
|
|
164
|
+
scheduledPublishVersionId: 'version-current',
|
|
165
|
+
})
|
|
166
|
+
expect(mocks.getPublishedVersion).toHaveBeenCalledWith({
|
|
167
|
+
collection_id: 'collection-settings',
|
|
168
|
+
document_id: 'document-settings',
|
|
169
|
+
})
|
|
170
|
+
expect(mocks.getScheduledPublish).toHaveBeenCalledTimes(1)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
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 { createFileRoute, notFound } from '@tanstack/react-router'
|
|
10
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
11
|
+
|
|
12
|
+
import type { DocumentPublishScheduleInfo } from '@byline/client'
|
|
13
|
+
import { getAdminBylineClient, getAdminRequestContext } from '@byline/client/server'
|
|
14
|
+
import {
|
|
15
|
+
buildInitialDataFromFields,
|
|
16
|
+
documentAbilityKey,
|
|
17
|
+
getServerConfig,
|
|
18
|
+
getSingletonAdminConfig,
|
|
19
|
+
} from '@byline/core'
|
|
20
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
21
|
+
import { z } from 'zod'
|
|
22
|
+
|
|
23
|
+
import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
|
|
24
|
+
import { SingletonView } from '../admin-shell/singletons/view.js'
|
|
25
|
+
import { bylineCore } from '../integrations/byline-core.js'
|
|
26
|
+
import { serialise } from '../server-fns/serialise.js'
|
|
27
|
+
import { readSingletonDocument } from '../server-fns/singleton-document-read.js'
|
|
28
|
+
import { getAdminRoutePath } from './admin-path.js'
|
|
29
|
+
import { getContentLocaleRouteConfig } from './get-content-locale-route-config.js'
|
|
30
|
+
import { getSingletonDefinition } from './get-singleton-definition.js'
|
|
31
|
+
|
|
32
|
+
const searchSchema = z.object({ locale: z.string().optional() })
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load the mapped singleton document and attach the admin-only lifecycle
|
|
36
|
+
* metadata consumed by the editor. Keeping this composition in the route
|
|
37
|
+
* layer leaves ordinary singleton reads free of publication timing and actor
|
|
38
|
+
* capability details.
|
|
39
|
+
*/
|
|
40
|
+
export const getSingletonRouteDocument = createServerFn({ method: 'GET' })
|
|
41
|
+
.validator((input: { singleton: string; locale?: string }) => input)
|
|
42
|
+
.handler(async ({ data }) => {
|
|
43
|
+
const definition = getSingletonDefinition(data.singleton)
|
|
44
|
+
if (definition == null) return null
|
|
45
|
+
|
|
46
|
+
const document = await readSingletonDocument({
|
|
47
|
+
singleton: data.singleton,
|
|
48
|
+
locale: data.locale,
|
|
49
|
+
populateRelations: true,
|
|
50
|
+
})
|
|
51
|
+
if (document == null) {
|
|
52
|
+
return {
|
|
53
|
+
document: null,
|
|
54
|
+
initialData: serialise(
|
|
55
|
+
await buildInitialDataFromFields(definition.fields, {
|
|
56
|
+
locale: data.locale ?? getServerConfig().i18n.content.defaultLocale,
|
|
57
|
+
})
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const config = getServerConfig()
|
|
63
|
+
const record = bylineCore().getCollectionRecord(data.singleton)
|
|
64
|
+
|
|
65
|
+
// A current draft may coexist with an older published version. That fact
|
|
66
|
+
// is editorial metadata, so it is intentionally attached only here rather
|
|
67
|
+
// than to every singleton read.
|
|
68
|
+
let publishedVersion: Record<string, any> | null = null
|
|
69
|
+
if ((document as Record<string, unknown>).status !== 'published') {
|
|
70
|
+
const published = await config.db.queries.documents.getPublishedVersion({
|
|
71
|
+
collection_id: record.collectionId,
|
|
72
|
+
document_id: String((document as Record<string, unknown>).id),
|
|
73
|
+
})
|
|
74
|
+
publishedVersion = published
|
|
75
|
+
? serialise({
|
|
76
|
+
id: published.document_id,
|
|
77
|
+
versionId: published.document_version_id,
|
|
78
|
+
status: published.status,
|
|
79
|
+
createdAt: published.created_at,
|
|
80
|
+
updatedAt: published.updated_at,
|
|
81
|
+
})
|
|
82
|
+
: null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Scheduling metadata is revealed only when the feature is enabled and
|
|
86
|
+
// the authenticated actor holds both abilities needed to arm a publish.
|
|
87
|
+
const scheduledPublicationEnabled = config.scheduledPublication?.enabled === true
|
|
88
|
+
let canSchedulePublication = false
|
|
89
|
+
let scheduledPublish: DocumentPublishScheduleInfo | null = null
|
|
90
|
+
if (scheduledPublicationEnabled) {
|
|
91
|
+
const actor = (await getAdminRequestContext()).actor
|
|
92
|
+
canSchedulePublication =
|
|
93
|
+
actor?.hasAbility(documentAbilityKey(definition, 'changeStatus')) === true &&
|
|
94
|
+
actor.hasAbility(documentAbilityKey(definition, 'publish'))
|
|
95
|
+
if (canSchedulePublication) {
|
|
96
|
+
scheduledPublish = await getAdminBylineClient()
|
|
97
|
+
.singleton(data.singleton)
|
|
98
|
+
.getScheduledPublish()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const serializedSchedule = scheduledPublish == null ? null : serialise(scheduledPublish)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
document: {
|
|
105
|
+
...(document as Record<string, unknown>),
|
|
106
|
+
_publishedVersion: publishedVersion,
|
|
107
|
+
_scheduledPublicationEnabled: scheduledPublicationEnabled,
|
|
108
|
+
_canSchedulePublication: canSchedulePublication,
|
|
109
|
+
_scheduledPublish: serializedSchedule,
|
|
110
|
+
...(serializedSchedule == null
|
|
111
|
+
? {}
|
|
112
|
+
: {
|
|
113
|
+
scheduledPublishAt: serializedSchedule.publishAt,
|
|
114
|
+
scheduledPublishVersionId: serializedSchedule.targetVersionId,
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
initialData: undefined,
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
export function createSingletonRoute(path: string) {
|
|
122
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
|
|
123
|
+
const Route: any = createFileRoute(path as never)({
|
|
124
|
+
validateSearch: searchSchema,
|
|
125
|
+
loaderDeps: ({ search }: { search: z.infer<typeof searchSchema> }) => ({
|
|
126
|
+
locale: search.locale,
|
|
127
|
+
}),
|
|
128
|
+
loader: async ({
|
|
129
|
+
params,
|
|
130
|
+
deps,
|
|
131
|
+
}: {
|
|
132
|
+
params: { singleton: string }
|
|
133
|
+
deps: { locale?: string }
|
|
134
|
+
}) => {
|
|
135
|
+
if (getSingletonDefinition(params.singleton) == null) throw notFound()
|
|
136
|
+
return getSingletonRouteDocument({
|
|
137
|
+
data: { singleton: params.singleton, locale: deps.locale },
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
staleTime: 0,
|
|
141
|
+
gcTime: 0,
|
|
142
|
+
shouldReload: true,
|
|
143
|
+
component: function SingletonRouteComponent() {
|
|
144
|
+
const routeState = Route.useLoaderData()
|
|
145
|
+
const { singleton } = Route.useParams() as { singleton: string }
|
|
146
|
+
const { locale } = Route.useSearch() as z.infer<typeof searchSchema>
|
|
147
|
+
const definition = getSingletonDefinition(singleton)
|
|
148
|
+
if (definition == null) throw notFound()
|
|
149
|
+
|
|
150
|
+
const { t } = useTranslation('byline-admin')
|
|
151
|
+
const { contentLocales, defaultContentLocale } = getContentLocaleRouteConfig()
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<>
|
|
155
|
+
<BreadcrumbsClient
|
|
156
|
+
breadcrumbs={[
|
|
157
|
+
{ label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
|
|
158
|
+
{ label: definition.label, href: getAdminRoutePath('singletons', singleton) },
|
|
159
|
+
]}
|
|
160
|
+
/>
|
|
161
|
+
<SingletonView
|
|
162
|
+
singletonDefinition={definition}
|
|
163
|
+
adminConfig={getSingletonAdminConfig(singleton) ?? undefined}
|
|
164
|
+
document={routeState.document}
|
|
165
|
+
initialData={routeState.initialData}
|
|
166
|
+
locale={locale}
|
|
167
|
+
contentLocales={contentLocales}
|
|
168
|
+
defaultContentLocale={defaultContentLocale}
|
|
169
|
+
/>
|
|
170
|
+
</>
|
|
171
|
+
)
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return Route
|
|
176
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 type { MultiCollectionDefinition } from '@byline/core'
|
|
10
|
+
import { getCollectionDefinition, isSingleton } from '@byline/core'
|
|
11
|
+
|
|
12
|
+
/** Resolve only the resource kind supported by the collection route family. */
|
|
13
|
+
export function getMultiCollectionDefinition(path: string): MultiCollectionDefinition | null {
|
|
14
|
+
const definition = getCollectionDefinition(path)
|
|
15
|
+
return definition != null && !isSingleton(definition) ? definition : null
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 type { SingletonDefinition } from '@byline/core'
|
|
10
|
+
import { getCollectionDefinition, isSingleton } from '@byline/core'
|
|
11
|
+
|
|
12
|
+
/** Resolve only the resource kind supported by the singleton route family. */
|
|
13
|
+
export function getSingletonDefinition(path: string): SingletonDefinition | null {
|
|
14
|
+
const definition = getCollectionDefinition(path)
|
|
15
|
+
return definition != null && isSingleton(definition) ? definition : null
|
|
16
|
+
}
|
package/src/routes/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export { getSignInRoutePath } from './sign-in-path.js'
|
|
|
33
33
|
|
|
34
34
|
export { createAdminAccountRoute } from './create-admin-account-route.js'
|
|
35
35
|
export { createAdminActivityRoute } from './create-admin-activity-route.js'
|
|
36
|
+
export { createAdminAnalyticsRoute } from './create-admin-analytics-route.js'
|
|
36
37
|
export { createAdminDashboardRoute } from './create-admin-dashboard-route.js'
|
|
37
38
|
export { createAdminLayoutRoute } from './create-admin-layout-route.js'
|
|
38
39
|
export { createAdminPermissionsRoute } from './create-admin-permissions-route.js'
|
|
@@ -50,3 +51,5 @@ export {
|
|
|
50
51
|
type CreateSignInRouteOptions,
|
|
51
52
|
createSignInRoute,
|
|
52
53
|
} from './create-sign-in-route.js'
|
|
54
|
+
export { createSingletonHistoryRoute } from './create-singleton-history-route.js'
|
|
55
|
+
export { createSingletonRoute } from './create-singleton-route.js'
|
|
@@ -0,0 +1,43 @@
|
|
|
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 type { AdminUserRow } from '@byline/admin/admin-users'
|
|
10
|
+
|
|
11
|
+
import { bylineCore } from '../integrations/byline-core.js'
|
|
12
|
+
|
|
13
|
+
export type ActorLabelMap = Record<string, { label: string }>
|
|
14
|
+
|
|
15
|
+
/** Display label: full name → username → email. */
|
|
16
|
+
function labelFor(row: AdminUserRow): string {
|
|
17
|
+
const name = [row.given_name, row.family_name].filter(Boolean).join(' ')
|
|
18
|
+
return name || row.username || row.email
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Batch-resolve admin-user ids to display labels. The admin server-fn layer
|
|
23
|
+
* owns this presentation concern so both collection and singleton history
|
|
24
|
+
* can share it without teaching document storage about admin-realm tables.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolveActorLabels(
|
|
27
|
+
ids: Iterable<string | null | undefined>
|
|
28
|
+
): Promise<ActorLabelMap> {
|
|
29
|
+
const unique = [
|
|
30
|
+
...new Set([...ids].filter((id): id is string => typeof id === 'string' && id.length > 0)),
|
|
31
|
+
]
|
|
32
|
+
if (unique.length === 0) return {}
|
|
33
|
+
|
|
34
|
+
const store = bylineCore().adminStore
|
|
35
|
+
if (store == null) return {}
|
|
36
|
+
|
|
37
|
+
const rows = await store.adminUsers.getByIds(unique)
|
|
38
|
+
const map: ActorLabelMap = {}
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
map[row.id] = { label: labelFor(row) }
|
|
41
|
+
}
|
|
42
|
+
return map
|
|
43
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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 {
|
|
10
|
+
buildRelationSummaryPopulateMap,
|
|
11
|
+
type CollectionDefinition,
|
|
12
|
+
getCollectionAdminConfig,
|
|
13
|
+
getCollectionDefinition,
|
|
14
|
+
type PopulateSpec,
|
|
15
|
+
} from '@byline/core'
|
|
16
|
+
|
|
17
|
+
export interface AdminDocumentReadInput {
|
|
18
|
+
locale?: string
|
|
19
|
+
depth?: number
|
|
20
|
+
populateRelations?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AdminDocumentReadOptions {
|
|
24
|
+
locale: string
|
|
25
|
+
populate?: PopulateSpec
|
|
26
|
+
depth?: number
|
|
27
|
+
status: 'any'
|
|
28
|
+
onMissingLocale: 'empty'
|
|
29
|
+
lenient: true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the shared read controls used by collection and singleton editors.
|
|
34
|
+
* Explicit depth is the API-preview mode; otherwise relation-summary mode
|
|
35
|
+
* projects only each target's picker/title fields for first-paint tiles.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveAdminDocumentRead(
|
|
38
|
+
definition: CollectionDefinition | null,
|
|
39
|
+
input: AdminDocumentReadInput
|
|
40
|
+
): { options: AdminDocumentReadOptions; populatedTree: boolean } {
|
|
41
|
+
const populateRequested = typeof input.depth === 'number' && input.depth > 0
|
|
42
|
+
const autoRelationsActive = !populateRequested && input.populateRelations === true
|
|
43
|
+
|
|
44
|
+
let populate: PopulateSpec | undefined
|
|
45
|
+
let depth: number | undefined
|
|
46
|
+
if (populateRequested) {
|
|
47
|
+
populate = '*'
|
|
48
|
+
depth = input.depth
|
|
49
|
+
} else if (autoRelationsActive && definition != null) {
|
|
50
|
+
const populateMap = buildRelationSummaryPopulateMap(definition.fields, (targetPath) => ({
|
|
51
|
+
def: getCollectionDefinition(targetPath),
|
|
52
|
+
admin: getCollectionAdminConfig(targetPath),
|
|
53
|
+
}))
|
|
54
|
+
if (Object.keys(populateMap).length > 0) {
|
|
55
|
+
populate = populateMap
|
|
56
|
+
depth = 1
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
options: {
|
|
62
|
+
locale: input.locale ?? 'en',
|
|
63
|
+
populate,
|
|
64
|
+
depth,
|
|
65
|
+
status: 'any',
|
|
66
|
+
// Editors need the raw requested-locale values. Untranslated localized
|
|
67
|
+
// fields stay empty instead of falling back, which is the signal that
|
|
68
|
+
// enables the Copy to Locale workflow. Using `omit` would hide the whole
|
|
69
|
+
// resource and make a missing translation look like a missing slot.
|
|
70
|
+
onMissingLocale: 'empty',
|
|
71
|
+
// Older rows may no longer reconstruct exactly against the current
|
|
72
|
+
// schema. Keep the editor loadable and surface those mismatches through
|
|
73
|
+
// `_restoreWarnings`, which the form renders as an alert.
|
|
74
|
+
lenient: true,
|
|
75
|
+
},
|
|
76
|
+
populatedTree: populateRequested || autoRelationsActive,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -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
|
+
export {
|
|
10
|
+
type DeleteAnalyticsEventsInput,
|
|
11
|
+
deleteAnalyticsEvents,
|
|
12
|
+
rebuildAnalyticsDay,
|
|
13
|
+
} from './maintenance.js'
|
|
14
|
+
export {
|
|
15
|
+
type AnalyticsRuntimeState,
|
|
16
|
+
type AnalyticsTopInput,
|
|
17
|
+
getAnalyticsCountries,
|
|
18
|
+
getAnalyticsReferrers,
|
|
19
|
+
getAnalyticsReportCoverage,
|
|
20
|
+
getAnalyticsRuntime,
|
|
21
|
+
getAnalyticsSummary,
|
|
22
|
+
getAnalyticsTop,
|
|
23
|
+
} from './queries.js'
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { ANALYTICS_ABILITIES } from '@byline/admin/analytics'
|
|
13
|
+
import { getAdminRequestContext } from '@byline/client/server'
|
|
14
|
+
|
|
15
|
+
export interface DeleteAnalyticsEventsInput {
|
|
16
|
+
from: string
|
|
17
|
+
to: string
|
|
18
|
+
visitorHash?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const deleteAnalyticsEvents = createServerFn({ method: 'POST' })
|
|
22
|
+
.validator((input: DeleteAnalyticsEventsInput) => input)
|
|
23
|
+
.handler(async ({ data }): Promise<{ deleted: number }> => {
|
|
24
|
+
await assertAnalyticsMaintenance()
|
|
25
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
26
|
+
const from = parseInstant(data.from, 'from')
|
|
27
|
+
const to = parseInstant(data.to, 'to')
|
|
28
|
+
const deleted = await getAnalytics().deleteEvents({
|
|
29
|
+
from,
|
|
30
|
+
to,
|
|
31
|
+
...(data.visitorHash == null ? {} : { visitorHash: data.visitorHash }),
|
|
32
|
+
})
|
|
33
|
+
return { deleted }
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const rebuildAnalyticsDay = createServerFn({ method: 'POST' })
|
|
37
|
+
.validator((input: { day: string }) => input)
|
|
38
|
+
.handler(async ({ data }): Promise<{ rebuilt: string }> => {
|
|
39
|
+
await assertAnalyticsMaintenance()
|
|
40
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
41
|
+
await getAnalytics().rebuildDay(data.day)
|
|
42
|
+
return { rebuilt: data.day }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
async function assertAnalyticsMaintenance(): Promise<void> {
|
|
46
|
+
const context = await getAdminRequestContext()
|
|
47
|
+
assertAdminActor(context, ANALYTICS_ABILITIES.maintain)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseInstant(value: string, label: string): Date {
|
|
51
|
+
const date = new Date(value)
|
|
52
|
+
if (Number.isNaN(date.valueOf())) throw new Error(`analytics ${label} must be an ISO instant`)
|
|
53
|
+
return date
|
|
54
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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, requireAdminActor } from '@byline/admin'
|
|
12
|
+
import { ANALYTICS_ABILITIES } from '@byline/admin/analytics'
|
|
13
|
+
import type {
|
|
14
|
+
AnalyticsCountryTotal,
|
|
15
|
+
AnalyticsDateRange,
|
|
16
|
+
AnalyticsEventKind,
|
|
17
|
+
AnalyticsPathTotal,
|
|
18
|
+
AnalyticsRankedTotals,
|
|
19
|
+
AnalyticsReferrerTotal,
|
|
20
|
+
AnalyticsReportCoverage,
|
|
21
|
+
AnalyticsSummary,
|
|
22
|
+
} from '@byline/analytics'
|
|
23
|
+
import { getAdminRequestContext } from '@byline/client/server'
|
|
24
|
+
|
|
25
|
+
export interface AnalyticsTopInput extends AnalyticsDateRange {
|
|
26
|
+
kind: AnalyticsEventKind
|
|
27
|
+
limit?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AnalyticsRuntimeState {
|
|
31
|
+
enabled: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const getAnalyticsRuntime = createServerFn({ method: 'GET' }).handler(
|
|
35
|
+
async (): Promise<AnalyticsRuntimeState> => {
|
|
36
|
+
const context = await getAdminRequestContext()
|
|
37
|
+
requireAdminActor(context, 'reading analytics runtime state')
|
|
38
|
+
const { isAnalyticsRegistered } = await import('@byline/analytics')
|
|
39
|
+
return { enabled: isAnalyticsRegistered() }
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export const getAnalyticsSummary = createServerFn({ method: 'GET' })
|
|
44
|
+
.validator((input: AnalyticsDateRange) => input)
|
|
45
|
+
.handler(async ({ data }): Promise<AnalyticsSummary> => {
|
|
46
|
+
await assertAnalyticsRead()
|
|
47
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
48
|
+
return getAnalytics().getSummary(data)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const getAnalyticsReportCoverage = createServerFn({ method: 'GET' }).handler(
|
|
52
|
+
async (): Promise<AnalyticsReportCoverage> => {
|
|
53
|
+
await assertAnalyticsRead()
|
|
54
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
55
|
+
return getAnalytics().getReportCoverage()
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
export const getAnalyticsTop = createServerFn({ method: 'GET' })
|
|
60
|
+
.validator((input: AnalyticsTopInput) => input)
|
|
61
|
+
.handler(async ({ data }): Promise<AnalyticsRankedTotals<AnalyticsPathTotal>> => {
|
|
62
|
+
await assertAnalyticsRead()
|
|
63
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
64
|
+
return getAnalytics().getTopPaths(data)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export const getAnalyticsReferrers = createServerFn({ method: 'GET' })
|
|
68
|
+
.validator((input: AnalyticsDateRange & { limit?: number }) => input)
|
|
69
|
+
.handler(async ({ data }): Promise<AnalyticsRankedTotals<AnalyticsReferrerTotal>> => {
|
|
70
|
+
await assertAnalyticsRead()
|
|
71
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
72
|
+
return getAnalytics().getReferrers(data)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export const getAnalyticsCountries = createServerFn({ method: 'GET' })
|
|
76
|
+
.validator((input: AnalyticsDateRange) => input)
|
|
77
|
+
.handler(async ({ data }): Promise<AnalyticsCountryTotal[]> => {
|
|
78
|
+
await assertAnalyticsRead()
|
|
79
|
+
const { getAnalytics } = await import('@byline/analytics')
|
|
80
|
+
return getAnalytics().getCountries(data)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
async function assertAnalyticsRead(): Promise<void> {
|
|
84
|
+
const context = await getAdminRequestContext()
|
|
85
|
+
assertAdminActor(context, ANALYTICS_ABILITIES.read)
|
|
86
|
+
}
|