@byline/host-tanstack-start 4.19.0 → 5.1.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-app-bar.d.ts +7 -0
- package/dist/admin-shell/chrome/admin-app-bar.js +10 -2
- package/dist/admin-shell/chrome/session-change-boundary.d.ts +14 -0
- package/dist/admin-shell/chrome/session-change-boundary.js +87 -0
- package/dist/admin-shell/chrome/session-change-boundary.test.d.ts +8 -0
- package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -1
- package/dist/admin-shell/chrome/sign-in-page.js +60 -3
- package/dist/admin-shell/collections/edit.d.ts +1 -1
- package/dist/admin-shell/collections/edit.js +151 -31
- package/dist/admin-shell/collections/history.js +4 -2
- package/dist/admin-shell/collections/list.js +30 -6
- package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
- package/dist/admin-shell/collections/restore-version-modal.js +9 -4
- package/dist/admin-shell/collections/tree-list.js +16 -6
- package/dist/admin-shell/collections/version-history.d.ts +2 -1
- package/dist/admin-shell/collections/version-history.js +11 -2
- package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
- package/dist/admin-shell/document-mutation-notice.js +53 -0
- package/dist/admin-shell/document-mutation-state.d.ts +22 -0
- package/dist/admin-shell/document-mutation-state.js +71 -0
- package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
- package/dist/admin-shell/scheduled-publications/list.js +15 -5
- package/dist/admin-shell/singletons/history.js +4 -2
- package/dist/admin-shell/singletons/view.d.ts +1 -1
- package/dist/admin-shell/singletons/view.js +98 -19
- package/dist/integrations/byline-admin-services.js +7 -1
- package/dist/integrations/byline-field-services.js +2 -9
- package/dist/integrations/client-ip.d.ts +19 -0
- package/dist/integrations/client-ip.js +34 -0
- package/dist/integrations/session-coordination.d.ts +24 -0
- package/dist/integrations/session-coordination.js +85 -0
- package/dist/integrations/session-middleware.d.ts +11 -0
- package/dist/integrations/session-middleware.js +94 -0
- package/dist/integrations/session-request-kind.d.ts +9 -0
- package/dist/integrations/session-request-kind.js +10 -0
- package/dist/integrations/session-request-middleware.d.ts +9 -0
- package/dist/integrations/session-request-middleware.js +7 -0
- package/dist/integrations/sign-in-body.d.ts +12 -0
- package/dist/integrations/sign-in-body.js +52 -0
- package/dist/integrations/sign-in-middleware.d.ts +9 -0
- package/dist/integrations/sign-in-middleware.js +10 -0
- package/dist/routes/create-admin-layout-route.js +46 -31
- package/dist/routes/create-collection-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.d.ts +0 -7
- package/dist/routes/create-collection-list-route.js +7 -9
- package/dist/routes/create-sign-in-route.js +4 -2
- package/dist/routes/create-singleton-route.d.ts +2 -0
- package/dist/routes/create-singleton-route.js +4 -3
- package/dist/server-fns/admin-account/change-password.d.ts +3 -1
- package/dist/server-fns/admin-account/change-password.js +9 -3
- package/dist/server-fns/admin-account/get.d.ts +3 -1
- package/dist/server-fns/admin-account/get.js +4 -1
- package/dist/server-fns/admin-account/index.d.ts +5 -5
- package/dist/server-fns/admin-account/update.d.ts +3 -1
- package/dist/server-fns/admin-account/update.js +4 -1
- package/dist/server-fns/admin-activity/get.d.ts +3 -1
- package/dist/server-fns/admin-activity/get.js +4 -1
- package/dist/server-fns/admin-permissions/get-role-abilities.d.ts +3 -1
- package/dist/server-fns/admin-permissions/get-role-abilities.js +4 -1
- package/dist/server-fns/admin-permissions/index.d.ts +4 -4
- package/dist/server-fns/admin-permissions/list-registered.d.ts +3 -1
- package/dist/server-fns/admin-permissions/list-registered.js +4 -1
- package/dist/server-fns/admin-permissions/set-role-abilities.d.ts +3 -1
- package/dist/server-fns/admin-permissions/set-role-abilities.js +4 -1
- package/dist/server-fns/admin-permissions/who-has.d.ts +3 -1
- package/dist/server-fns/admin-permissions/who-has.js +4 -1
- package/dist/server-fns/admin-roles/create.d.ts +3 -1
- package/dist/server-fns/admin-roles/create.js +4 -1
- package/dist/server-fns/admin-roles/delete.d.ts +3 -1
- package/dist/server-fns/admin-roles/delete.js +4 -1
- package/dist/server-fns/admin-roles/get.d.ts +3 -1
- package/dist/server-fns/admin-roles/get.js +4 -1
- package/dist/server-fns/admin-roles/index.d.ts +6 -6
- package/dist/server-fns/admin-roles/list.d.ts +3 -1
- package/dist/server-fns/admin-roles/list.js +4 -1
- package/dist/server-fns/admin-roles/reorder.d.ts +3 -1
- package/dist/server-fns/admin-roles/reorder.js +4 -1
- package/dist/server-fns/admin-roles/update.d.ts +3 -1
- package/dist/server-fns/admin-roles/update.js +4 -1
- package/dist/server-fns/admin-users/create.d.ts +3 -1
- package/dist/server-fns/admin-users/create.js +4 -1
- package/dist/server-fns/admin-users/delete.d.ts +3 -1
- package/dist/server-fns/admin-users/delete.js +4 -1
- package/dist/server-fns/admin-users/disable.d.ts +3 -1
- package/dist/server-fns/admin-users/disable.js +4 -1
- package/dist/server-fns/admin-users/enable.d.ts +3 -1
- package/dist/server-fns/admin-users/enable.js +4 -1
- package/dist/server-fns/admin-users/get-user-roles.d.ts +3 -1
- package/dist/server-fns/admin-users/get-user-roles.js +4 -1
- package/dist/server-fns/admin-users/get.d.ts +3 -1
- package/dist/server-fns/admin-users/get.js +4 -1
- package/dist/server-fns/admin-users/index.d.ts +10 -10
- package/dist/server-fns/admin-users/list.d.ts +3 -1
- package/dist/server-fns/admin-users/list.js +4 -1
- package/dist/server-fns/admin-users/set-password.d.ts +3 -1
- package/dist/server-fns/admin-users/set-password.js +4 -1
- package/dist/server-fns/admin-users/set-user-roles.d.ts +3 -1
- package/dist/server-fns/admin-users/set-user-roles.js +4 -1
- package/dist/server-fns/admin-users/update.d.ts +3 -1
- package/dist/server-fns/admin-users/update.js +4 -1
- package/dist/server-fns/ai/execute.d.ts +3 -1
- package/dist/server-fns/ai/execute.js +4 -1
- package/dist/server-fns/ai/index.d.ts +1 -1
- package/dist/server-fns/analytics/maintenance.d.ts +6 -2
- package/dist/server-fns/analytics/maintenance.js +7 -2
- package/dist/server-fns/analytics/queries.d.ts +18 -6
- package/dist/server-fns/analytics/queries.js +19 -6
- package/dist/server-fns/auth/current-user.d.ts +1 -0
- package/dist/server-fns/auth/current-user.js +5 -2
- package/dist/server-fns/auth/renew.d.ts +13 -0
- package/dist/server-fns/auth/renew.js +45 -0
- package/dist/server-fns/auth/sign-in.d.ts +2 -1
- package/dist/server-fns/auth/sign-in.js +88 -17
- package/dist/server-fns/auth/sign-out.d.ts +3 -1
- package/dist/server-fns/auth/sign-out.js +38 -6
- package/dist/server-fns/collections/audit.d.ts +3 -1
- package/dist/server-fns/collections/audit.js +4 -1
- package/dist/server-fns/collections/copy-to-locale.d.ts +5 -1
- package/dist/server-fns/collections/copy-to-locale.js +7 -2
- package/dist/server-fns/collections/create.d.ts +4 -1
- package/dist/server-fns/collections/create.js +8 -3
- package/dist/server-fns/collections/delete-locale.d.ts +5 -1
- package/dist/server-fns/collections/delete-locale.js +7 -2
- package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
- package/dist/server-fns/collections/delete-outcome.js +4 -0
- package/dist/server-fns/collections/delete.d.ts +5 -1
- package/dist/server-fns/collections/delete.js +8 -3
- package/dist/server-fns/collections/duplicate.d.ts +5 -1
- package/dist/server-fns/collections/duplicate.js +8 -3
- package/dist/server-fns/collections/get.d.ts +1 -0
- package/dist/server-fns/collections/get.js +12 -6
- package/dist/server-fns/collections/history.d.ts +3 -1
- package/dist/server-fns/collections/history.js +4 -1
- package/dist/server-fns/collections/index.d.ts +21 -21
- package/dist/server-fns/collections/list.d.ts +3 -1
- package/dist/server-fns/collections/list.js +15 -4
- package/dist/server-fns/collections/reindex.d.ts +3 -1
- package/dist/server-fns/collections/reindex.js +4 -1
- package/dist/server-fns/collections/reorder.d.ts +10 -2
- package/dist/server-fns/collections/reorder.js +23 -76
- package/dist/server-fns/collections/restore-version.d.ts +6 -1
- package/dist/server-fns/collections/restore-version.js +8 -2
- package/dist/server-fns/collections/save-outcome.d.ts +1 -0
- package/dist/server-fns/collections/save-outcome.js +1 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +64 -7
- package/dist/server-fns/collections/scheduled-publication.js +42 -21
- package/dist/server-fns/collections/set-list-view-preference.d.ts +3 -1
- package/dist/server-fns/collections/set-list-view-preference.js +4 -1
- package/dist/server-fns/collections/stats.js +4 -1
- package/dist/server-fns/collections/status.d.ts +14 -2
- package/dist/server-fns/collections/status.js +18 -5
- package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
- package/dist/server-fns/collections/tree-mutation.js +17 -5
- package/dist/server-fns/collections/tree.d.ts +29 -6
- package/dist/server-fns/collections/tree.js +32 -17
- package/dist/server-fns/collections/update.d.ts +18 -6
- package/dist/server-fns/collections/update.js +39 -20
- package/dist/server-fns/collections/upload.d.ts +11 -1
- package/dist/server-fns/collections/upload.js +6 -2
- package/dist/server-fns/document-mutation-errors.d.ts +5 -0
- package/dist/server-fns/document-mutation-errors.js +50 -0
- package/dist/server-fns/editable-revision.d.ts +9 -0
- package/dist/server-fns/editable-revision.js +11 -0
- package/dist/server-fns/i18n/set-locale.d.ts +3 -1
- package/dist/server-fns/i18n/set-locale.js +18 -3
- package/dist/server-fns/preview/disable.d.ts +3 -1
- package/dist/server-fns/preview/disable.js +4 -1
- package/dist/server-fns/preview/enable.d.ts +3 -1
- package/dist/server-fns/preview/enable.js +4 -1
- package/dist/server-fns/singleton-document-read.d.ts +1 -1
- package/dist/server-fns/singleton-document-read.js +7 -3
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +9 -2
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -1
- package/dist/server-fns/singletons/change-status.d.ts +5 -1
- package/dist/server-fns/singletons/change-status.js +7 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +8 -2
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +7 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +5 -1
- package/dist/server-fns/singletons/copy-to-locale.js +7 -2
- package/dist/server-fns/singletons/find-by-version.d.ts +3 -1
- package/dist/server-fns/singletons/find-by-version.js +4 -1
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +3 -1
- package/dist/server-fns/singletons/get-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/get.d.ts +6 -4
- package/dist/server-fns/singletons/get.js +4 -1
- package/dist/server-fns/singletons/history.d.ts +3 -1
- package/dist/server-fns/singletons/history.js +4 -1
- package/dist/server-fns/singletons/restore-version.d.ts +5 -1
- package/dist/server-fns/singletons/restore-version.js +9 -3
- package/dist/server-fns/singletons/schedule-publish.d.ts +8 -2
- package/dist/server-fns/singletons/schedule-publish.js +7 -2
- package/dist/server-fns/singletons/unpublish.d.ts +5 -1
- package/dist/server-fns/singletons/unpublish.js +7 -1
- package/dist/server-fns/singletons/update.d.ts +6 -4
- package/dist/server-fns/singletons/update.js +8 -4
- package/dist/server-fns/structural-receipt.d.ts +4 -0
- package/dist/server-fns/structural-receipt.js +31 -0
- package/package.json +20 -20
- package/src/admin-shell/chrome/admin-app-bar.tsx +11 -6
- package/src/admin-shell/chrome/session-change-boundary.test.tsx +78 -0
- package/src/admin-shell/chrome/session-change-boundary.tsx +94 -0
- package/src/admin-shell/chrome/sign-in-page.tsx +68 -3
- package/src/admin-shell/collections/edit.tsx +173 -54
- package/src/admin-shell/collections/history.tsx +11 -2
- package/src/admin-shell/collections/list.tsx +22 -6
- package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
- package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
- package/src/admin-shell/collections/tree-list.tsx +16 -6
- package/src/admin-shell/collections/version-history.tsx +13 -3
- package/src/admin-shell/document-mutation-notice.tsx +45 -0
- package/src/admin-shell/document-mutation-state.test.tsx +146 -0
- package/src/admin-shell/document-mutation-state.ts +95 -0
- package/src/admin-shell/scheduled-publications/list.tsx +16 -6
- package/src/admin-shell/singletons/history.test.tsx +3 -1
- package/src/admin-shell/singletons/history.tsx +10 -2
- package/src/admin-shell/singletons/view.test.tsx +59 -18
- package/src/admin-shell/singletons/view.tsx +110 -29
- package/src/integrations/byline-admin-services.ts +7 -1
- package/src/integrations/byline-field-services.ts +2 -3
- package/src/integrations/client-ip.test.node.ts +45 -0
- package/src/integrations/client-ip.ts +54 -0
- package/src/integrations/framework-fetch-shape.test.node.ts +56 -0
- package/src/integrations/session-client.test.node.ts +121 -0
- package/src/integrations/session-coordination.test.node.ts +77 -0
- package/src/integrations/session-coordination.ts +96 -0
- package/src/integrations/session-middleware.test.node.ts +87 -0
- package/src/integrations/session-middleware.ts +107 -0
- package/src/integrations/session-request-kind.ts +18 -0
- package/src/integrations/session-request-middleware.ts +17 -0
- package/src/integrations/sign-in-body.test.node.ts +59 -0
- package/src/integrations/sign-in-body.ts +50 -0
- package/src/integrations/sign-in-middleware.ts +24 -0
- package/src/routes/create-admin-layout-route.tsx +48 -30
- package/src/routes/create-collection-edit-route.tsx +1 -0
- package/src/routes/create-collection-list-route.tsx +13 -2
- package/src/routes/create-sign-in-route.tsx +10 -2
- package/src/routes/create-singleton-route.test.node.ts +10 -2
- package/src/routes/create-singleton-route.tsx +6 -4
- package/src/server-fns/admin-account/change-password.test.node.ts +69 -0
- package/src/server-fns/admin-account/change-password.ts +13 -2
- package/src/server-fns/admin-account/get.ts +2 -0
- package/src/server-fns/admin-account/update.ts +2 -0
- package/src/server-fns/admin-activity/get.ts +2 -0
- package/src/server-fns/admin-permissions/get-role-abilities.ts +2 -0
- package/src/server-fns/admin-permissions/list-registered.ts +5 -4
- package/src/server-fns/admin-permissions/set-role-abilities.ts +2 -0
- package/src/server-fns/admin-permissions/who-has.ts +2 -0
- package/src/server-fns/admin-roles/create.ts +2 -0
- package/src/server-fns/admin-roles/delete.ts +2 -0
- package/src/server-fns/admin-roles/get.ts +2 -0
- package/src/server-fns/admin-roles/list.ts +5 -4
- package/src/server-fns/admin-roles/reorder.ts +2 -0
- package/src/server-fns/admin-roles/update.ts +2 -0
- package/src/server-fns/admin-users/create.ts +2 -0
- package/src/server-fns/admin-users/delete.ts +2 -0
- package/src/server-fns/admin-users/disable.ts +2 -0
- package/src/server-fns/admin-users/enable.ts +2 -0
- package/src/server-fns/admin-users/get-user-roles.ts +2 -0
- package/src/server-fns/admin-users/get.ts +2 -0
- package/src/server-fns/admin-users/list.ts +2 -0
- package/src/server-fns/admin-users/set-password.ts +2 -0
- package/src/server-fns/admin-users/set-user-roles.ts +2 -0
- package/src/server-fns/admin-users/update.ts +2 -0
- package/src/server-fns/ai/execute.ts +3 -0
- package/src/server-fns/analytics/maintenance.ts +4 -0
- package/src/server-fns/analytics/queries.ts +14 -8
- package/src/server-fns/auth/current-user.ts +7 -3
- package/src/server-fns/auth/renew.test.node.ts +85 -0
- package/src/server-fns/auth/renew.ts +54 -0
- package/src/server-fns/auth/sign-in.test.node.ts +114 -0
- package/src/server-fns/auth/sign-in.ts +80 -27
- package/src/server-fns/auth/sign-out.test.node.ts +85 -0
- package/src/server-fns/auth/sign-out.ts +44 -19
- package/src/server-fns/collections/audit.ts +2 -0
- package/src/server-fns/collections/copy-to-locale.ts +43 -36
- package/src/server-fns/collections/create.ts +56 -50
- package/src/server-fns/collections/delete-locale.ts +43 -32
- package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
- package/src/server-fns/collections/delete-outcome.ts +27 -16
- package/src/server-fns/collections/delete.ts +46 -38
- package/src/server-fns/collections/duplicate.ts +40 -32
- package/src/server-fns/collections/get.ts +11 -4
- package/src/server-fns/collections/history.ts +2 -0
- package/src/server-fns/collections/list.ts +10 -3
- package/src/server-fns/collections/reindex.ts +2 -0
- package/src/server-fns/collections/reorder.ts +34 -124
- package/src/server-fns/collections/restore-version.ts +49 -39
- package/src/server-fns/collections/revision-input.test.node.ts +122 -0
- package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
- package/src/server-fns/collections/save-outcome.ts +2 -0
- package/src/server-fns/collections/scheduled-publication.ts +149 -115
- package/src/server-fns/collections/set-list-view-preference.ts +2 -0
- package/src/server-fns/collections/stats.ts +2 -0
- package/src/server-fns/collections/status.ts +89 -64
- package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
- package/src/server-fns/collections/tree-mutation.ts +28 -11
- package/src/server-fns/collections/tree.ts +156 -122
- package/src/server-fns/collections/update.ts +113 -86
- package/src/server-fns/collections/upload-singleton.test.node.ts +10 -7
- package/src/server-fns/collections/upload.ts +91 -77
- package/src/server-fns/document-mutation-errors.ts +67 -0
- package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
- package/src/server-fns/editable-revision.test.node.ts +23 -0
- package/src/server-fns/editable-revision.ts +18 -0
- package/src/server-fns/i18n/set-locale.ts +19 -3
- package/src/server-fns/preview/disable.ts +8 -4
- package/src/server-fns/preview/enable.ts +9 -5
- package/src/server-fns/singleton-document-read.ts +7 -3
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +12 -3
- package/src/server-fns/singletons/change-status.ts +12 -3
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +14 -6
- package/src/server-fns/singletons/copy-to-locale.ts +22 -15
- package/src/server-fns/singletons/find-by-version.ts +2 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +2 -0
- package/src/server-fns/singletons/get.ts +5 -3
- package/src/server-fns/singletons/history.ts +2 -0
- package/src/server-fns/singletons/restore-version.ts +19 -12
- package/src/server-fns/singletons/schedule-publish.ts +20 -7
- package/src/server-fns/singletons/singletons.test.node.ts +69 -25
- package/src/server-fns/singletons/unpublish.ts +12 -3
- package/src/server-fns/singletons/update.ts +23 -16
- package/src/server-fns/structural-receipt.ts +41 -0
|
@@ -1,3 +1,11 @@
|
|
|
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
|
+
|
|
1
9
|
import { createServerFn } from '@tanstack/react-start'
|
|
2
10
|
|
|
3
11
|
import { getAdminRequestContext } from '@byline/client/server'
|
|
@@ -14,6 +22,8 @@ import { getLogger, withLogContext } from '@byline/core/logger'
|
|
|
14
22
|
import { uploadField as coreUploadField } from '@byline/core/services'
|
|
15
23
|
|
|
16
24
|
import { ensureDocumentResource } from '../../integrations/api-utils.js'
|
|
25
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
26
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
29
|
* Result of an upload through the host transport. The legacy top-level
|
|
@@ -25,6 +35,7 @@ export interface UploadFieldResult {
|
|
|
25
35
|
/** Present when the upload endpoint created a document (createDocument=true, the default). */
|
|
26
36
|
documentId?: string
|
|
27
37
|
documentVersionId?: string
|
|
38
|
+
revision?: number
|
|
28
39
|
storedFile: StoredFileValue
|
|
29
40
|
}
|
|
30
41
|
|
|
@@ -148,89 +159,92 @@ function resolveUploadField(
|
|
|
148
159
|
* without depending on TanStack Start server-function transport details.
|
|
149
160
|
*/
|
|
150
161
|
export const uploadCollectionField = createServerFn({ method: 'POST' })
|
|
162
|
+
.middleware([adminSessionMiddleware])
|
|
151
163
|
.validator(parseUploadFormData)
|
|
152
|
-
.handler(
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
.handler(
|
|
165
|
+
withDocumentMutationErrors(async ({ data }) => {
|
|
166
|
+
const { collectionPath, shouldCreateDocument, fieldName, file, fields } = data
|
|
167
|
+
const logger = getLogger()
|
|
155
168
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
return withLogContext(
|
|
170
|
+
{ domain: 'api', module: 'upload', function: 'uploadCollectionField' },
|
|
171
|
+
async () => {
|
|
172
|
+
// Upload is the one field transport shared by both document-resource
|
|
173
|
+
// kinds. The core service selects the kind-aware ability and rejects
|
|
174
|
+
// singleton document creation before hooks or storage; the admin form
|
|
175
|
+
// always requests the field-only branch (`createDocument=false`).
|
|
176
|
+
const config = await ensureDocumentResource(collectionPath)
|
|
177
|
+
if (config == null) {
|
|
178
|
+
throw ERR_NOT_FOUND(
|
|
179
|
+
{
|
|
180
|
+
message: 'Document resource not found.',
|
|
181
|
+
details: { collectionPath },
|
|
182
|
+
},
|
|
183
|
+
uploadCollectionField
|
|
184
|
+
).log(logger)
|
|
185
|
+
}
|
|
173
186
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
const serverConfig = getServerConfig()
|
|
188
|
+
const targetField = resolveUploadField(config.definition, collectionPath, fieldName)
|
|
189
|
+
// Per-field storage routing falls through to the site-wide default.
|
|
190
|
+
const storage = targetField.upload?.storage ?? serverConfig.storage
|
|
191
|
+
if (!storage) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`No storage provider configured for field '${targetField.name}' on collection ` +
|
|
194
|
+
`'${collectionPath}'. Set either field.upload.storage or the site-wide ` +
|
|
195
|
+
'ServerConfig.storage.'
|
|
196
|
+
)
|
|
197
|
+
}
|
|
185
198
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
let buffer: Buffer
|
|
200
|
+
try {
|
|
201
|
+
buffer = Buffer.from(await file.arrayBuffer())
|
|
202
|
+
} catch (err: unknown) {
|
|
203
|
+
logger.error({ err, collectionPath }, 'failed to read file buffer')
|
|
204
|
+
throw new Error('Failed to read uploaded file.')
|
|
205
|
+
}
|
|
193
206
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
207
|
+
const ctx: FieldUploadContext = {
|
|
208
|
+
db: serverConfig.db,
|
|
209
|
+
definition: config.definition,
|
|
210
|
+
collectionId: config.collection.id,
|
|
211
|
+
collectionVersion: config.collection.version,
|
|
212
|
+
collectionPath,
|
|
213
|
+
fieldName: targetField.name,
|
|
214
|
+
storage,
|
|
215
|
+
logger,
|
|
216
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
217
|
+
slugifier: serverConfig.slugifier,
|
|
218
|
+
filenameSlugifier: serverConfig.uploads?.filenameSlugifier,
|
|
219
|
+
requestContext: await getAdminRequestContext(),
|
|
220
|
+
imageProcessor: {
|
|
221
|
+
extractMeta: extractImageMeta,
|
|
222
|
+
isBypassMimeType,
|
|
223
|
+
generateVariants: ({ buffer, mimeType, storedFile, storage, upload, logger }) =>
|
|
224
|
+
generateImageVariants(
|
|
225
|
+
buffer,
|
|
226
|
+
mimeType,
|
|
227
|
+
storedFile,
|
|
228
|
+
storage,
|
|
229
|
+
upload.sizes ?? [],
|
|
230
|
+
logger
|
|
231
|
+
),
|
|
232
|
+
},
|
|
233
|
+
}
|
|
221
234
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
235
|
+
return coreUploadField(ctx, {
|
|
236
|
+
buffer,
|
|
237
|
+
originalFilename: file.name || 'upload',
|
|
238
|
+
mimeType: file.type || 'application/octet-stream',
|
|
239
|
+
fileSize: file.size,
|
|
240
|
+
fields,
|
|
241
|
+
shouldCreateDocument,
|
|
242
|
+
locale: serverConfig.i18n.content.defaultLocale,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
)
|
|
234
248
|
|
|
235
249
|
/**
|
|
236
250
|
* Upload a file to an upload-enabled collection.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BylineError,
|
|
3
|
+
ErrorCodes,
|
|
4
|
+
getDocumentRevisionValidationDetails,
|
|
5
|
+
getDocumentStaleDetails,
|
|
6
|
+
getLockConflictDetails,
|
|
7
|
+
} from '@byline/core'
|
|
8
|
+
import { getDocumentHookCommittedDetails } from '@byline/core/services'
|
|
9
|
+
|
|
10
|
+
import { committedTreeReceipt } from './structural-receipt.js'
|
|
11
|
+
|
|
12
|
+
/** Error subclasses lose custom properties in the server-function serializer.
|
|
13
|
+
* Throw only validated plain data for document failures; never transport causes,
|
|
14
|
+
* SQL, hook messages or an unverified current revision. */
|
|
15
|
+
export function documentMutationError(error: unknown): unknown {
|
|
16
|
+
const stale = getDocumentStaleDetails(error)
|
|
17
|
+
if (stale)
|
|
18
|
+
return {
|
|
19
|
+
code: ErrorCodes.DOCUMENT_STALE,
|
|
20
|
+
message: 'This document has changed. Reload before making changes.',
|
|
21
|
+
details: stale,
|
|
22
|
+
}
|
|
23
|
+
const validation = getDocumentRevisionValidationDetails(error)
|
|
24
|
+
if (validation)
|
|
25
|
+
return {
|
|
26
|
+
code: ErrorCodes.VALIDATION,
|
|
27
|
+
message: 'Reload this document before making changes.',
|
|
28
|
+
details: validation,
|
|
29
|
+
}
|
|
30
|
+
const lock = getLockConflictDetails(error)
|
|
31
|
+
if (lock)
|
|
32
|
+
return {
|
|
33
|
+
code: ErrorCodes.LOCK_CONFLICT,
|
|
34
|
+
message:
|
|
35
|
+
'This change could not be saved because another operation was using the document. Reload before trying again.',
|
|
36
|
+
details: lock,
|
|
37
|
+
}
|
|
38
|
+
const tree = committedTreeReceipt(error)
|
|
39
|
+
if (tree)
|
|
40
|
+
return {
|
|
41
|
+
code: ErrorCodes.TREE_HOOK_COMMITTED,
|
|
42
|
+
message: 'The structure was saved, but a follow-up action failed.',
|
|
43
|
+
details: tree,
|
|
44
|
+
}
|
|
45
|
+
const committed = getDocumentHookCommittedDetails(error)
|
|
46
|
+
if (committed)
|
|
47
|
+
return {
|
|
48
|
+
code: ErrorCodes.DOCUMENT_HOOK_COMMITTED,
|
|
49
|
+
message: 'The document was saved, but a follow-up action failed.',
|
|
50
|
+
details: committed,
|
|
51
|
+
}
|
|
52
|
+
if (error instanceof BylineError && error.code === ErrorCodes.NOT_FOUND)
|
|
53
|
+
return { code: ErrorCodes.NOT_FOUND, message: 'This document is no longer available.' }
|
|
54
|
+
return error
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function withDocumentMutationErrors<T, R>(
|
|
58
|
+
handler: (input: T) => Promise<R>
|
|
59
|
+
): (input: T) => Promise<R> {
|
|
60
|
+
return async (input) => {
|
|
61
|
+
try {
|
|
62
|
+
return await handler(input)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw documentMutationError(error)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ERR_CONFLICT,
|
|
6
|
+
ERR_DOCUMENT_HOOK_COMMITTED,
|
|
7
|
+
ERR_DOCUMENT_STALE,
|
|
8
|
+
ERR_LOCK_CONFLICT,
|
|
9
|
+
ERR_NOT_FOUND,
|
|
10
|
+
ERR_TREE_HOOK_COMMITTED,
|
|
11
|
+
ERR_VALIDATION,
|
|
12
|
+
getDocumentRevisionValidationDetails,
|
|
13
|
+
getDocumentStaleDetails,
|
|
14
|
+
getLockConflictDetails,
|
|
15
|
+
} from '@byline/core'
|
|
16
|
+
import { getDocumentHookCommittedDetails } from '@byline/core/services'
|
|
17
|
+
import { beforeAll, describe, expect, it, vi } from 'vitest'
|
|
18
|
+
|
|
19
|
+
import { documentMutationError, withDocumentMutationErrors } from './document-mutation-errors.js'
|
|
20
|
+
|
|
21
|
+
// Exercise the installed Start HTTP handler and its real Seroval protocol. Only
|
|
22
|
+
// manifest lookup and request-local response state are supplied by this harness;
|
|
23
|
+
// the production application's compiled actions are covered by browser tests.
|
|
24
|
+
const require = createRequire(import.meta.url)
|
|
25
|
+
const startRequire = createRequire(require.resolve('@tanstack/react-start/package.json'))
|
|
26
|
+
const serverPackage = startRequire.resolve('@tanstack/start-server-core/package.json')
|
|
27
|
+
const esm = join(dirname(serverPackage), 'src')
|
|
28
|
+
const serverRequire = createRequire(serverPackage)
|
|
29
|
+
let handler: (input: { request: Request; context: object; serverFnId: string }) => Promise<Response>
|
|
30
|
+
let seroval: {
|
|
31
|
+
toJSONAsync(value: unknown): Promise<unknown>
|
|
32
|
+
fromCrossJSON(value: unknown, options: object): unknown
|
|
33
|
+
}
|
|
34
|
+
let runWithStartContext: (context: object, fn: () => Promise<Response>) => Promise<Response>
|
|
35
|
+
let action: (input: { data: unknown }) => Promise<unknown>
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
vi.doMock(join(esm, 'getServerFnById.ts'), () => ({ getServerFnById: async () => action }))
|
|
39
|
+
vi.doMock(join(esm, 'request-response.ts'), () => ({
|
|
40
|
+
getResponse: () => ({ status: 200, headers: new Headers() }),
|
|
41
|
+
}))
|
|
42
|
+
handler = (await import(join(esm, 'server-functions-handler.ts'))).handleServerAction
|
|
43
|
+
seroval = await import(serverRequire.resolve('seroval'))
|
|
44
|
+
runWithStartContext = (
|
|
45
|
+
await import(
|
|
46
|
+
join(
|
|
47
|
+
dirname(serverRequire.resolve('@tanstack/start-storage-context/package.json')),
|
|
48
|
+
'dist/esm/index.js'
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
).runWithStartContext
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
async function roundTrip(data: unknown, operation: (data: unknown) => Promise<unknown>) {
|
|
55
|
+
const guarded = withDocumentMutationErrors(operation)
|
|
56
|
+
action = async ({ data }) => {
|
|
57
|
+
try {
|
|
58
|
+
return { result: await guarded(data) }
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return { error }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const request = new Request('http://localhost/_serverFn/document-mutation', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'content-type': 'application/json', 'x-tsr-serverFn': 'true' },
|
|
66
|
+
body: JSON.stringify(await seroval.toJSONAsync({ data })),
|
|
67
|
+
})
|
|
68
|
+
const response = await runWithStartContext(
|
|
69
|
+
{
|
|
70
|
+
request,
|
|
71
|
+
startOptions: {},
|
|
72
|
+
contextAfterGlobalMiddlewares: {},
|
|
73
|
+
executedRequestMiddlewares: new Set(),
|
|
74
|
+
handlerType: 'serverFn',
|
|
75
|
+
getRouter: () => {
|
|
76
|
+
throw new Error('Router not used')
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
() => handler({ request, context: {}, serverFnId: 'document-mutation' })
|
|
80
|
+
)
|
|
81
|
+
const wire = await response.text()
|
|
82
|
+
const decoded = seroval.fromCrossJSON(JSON.parse(wire), {})
|
|
83
|
+
if (typeof decoded !== 'object' || decoded === null) throw new Error('Expected wire envelope')
|
|
84
|
+
return {
|
|
85
|
+
value: { error: Reflect.get(decoded, 'error'), result: Reflect.get(decoded, 'result') },
|
|
86
|
+
wire,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const staleCases = [
|
|
91
|
+
{ reason: 'revision_mismatch', documentId: 'doc', expectedRevision: 1, currentRevision: 2 },
|
|
92
|
+
{
|
|
93
|
+
reason: 'version_parent_mismatch',
|
|
94
|
+
documentId: 'doc',
|
|
95
|
+
previousVersionId: 'old',
|
|
96
|
+
currentVersionId: 'new',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
reason: 'singleton_slot_changed',
|
|
100
|
+
singletonPath: 'settings',
|
|
101
|
+
expectedState: 'empty',
|
|
102
|
+
currentState: 'document',
|
|
103
|
+
},
|
|
104
|
+
] as const
|
|
105
|
+
|
|
106
|
+
describe('document failures over Start HTTP serialization', () => {
|
|
107
|
+
for (const details of staleCases) {
|
|
108
|
+
it(`retains ${details.reason} and excludes diagnostics`, async () => {
|
|
109
|
+
const { value, wire } = await roundTrip({}, async () => {
|
|
110
|
+
throw ERR_DOCUMENT_STALE({
|
|
111
|
+
message: 'private driver detail',
|
|
112
|
+
details,
|
|
113
|
+
cause: new Error('SELECT secret'),
|
|
114
|
+
logExtra: { private: true },
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
expect(getDocumentStaleDetails(value.error)).toEqual(details)
|
|
118
|
+
expect(wire).not.toMatch(/private driver|SELECT secret|logExtra/)
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
for (const reason of ['missing_document_revision', 'invalid_document_revision'] as const) {
|
|
122
|
+
it(`retains reload-required validation: ${reason}`, async () => {
|
|
123
|
+
const { value } = await roundTrip({}, async () => {
|
|
124
|
+
throw ERR_VALIDATION({ message: 'internal', details: { reason } })
|
|
125
|
+
})
|
|
126
|
+
expect(getDocumentRevisionValidationDetails(value.error)).toEqual({ reason })
|
|
127
|
+
expect(getDocumentStaleDetails(value.error)).toBeNull()
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
it('retains a confirmed rollback without relabelling it stale', async () => {
|
|
131
|
+
const details = { reason: 'lock_conflict', rolledBack: true, retryable: true }
|
|
132
|
+
const { value, wire } = await roundTrip({}, async () => {
|
|
133
|
+
throw ERR_LOCK_CONFLICT({
|
|
134
|
+
message: 'driver message',
|
|
135
|
+
details,
|
|
136
|
+
cause: new Error('SQL private'),
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
expect(getLockConflictDetails(value.error)).toEqual(details)
|
|
140
|
+
expect(getDocumentStaleDetails(value.error)).toBeNull()
|
|
141
|
+
expect(wire).not.toMatch(/driver message|SQL private/)
|
|
142
|
+
})
|
|
143
|
+
it('keeps committed hook warnings separate and preserves their committed revision', async () => {
|
|
144
|
+
const details = {
|
|
145
|
+
phase: 'afterUpdate',
|
|
146
|
+
documentId: 'doc',
|
|
147
|
+
documentVersionId: 'version',
|
|
148
|
+
revision: 3,
|
|
149
|
+
sideEffectCode: 'ERR_UNHANDLED',
|
|
150
|
+
}
|
|
151
|
+
const { value, wire } = await roundTrip({}, async () => {
|
|
152
|
+
throw ERR_DOCUMENT_HOOK_COMMITTED({
|
|
153
|
+
message: 'private hook failure',
|
|
154
|
+
details,
|
|
155
|
+
cause: new Error('secret hook'),
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
expect(getDocumentHookCommittedDetails(value.error)).toEqual(details)
|
|
159
|
+
expect(getDocumentStaleDetails(value.error)).toBeNull()
|
|
160
|
+
expect(getLockConflictDetails(value.error)).toBeNull()
|
|
161
|
+
expect(wire).not.toMatch(/private hook failure|secret hook/)
|
|
162
|
+
})
|
|
163
|
+
it('round-trips safe integer observations and receipts as numbers', async () => {
|
|
164
|
+
const { value } = await roundTrip(
|
|
165
|
+
{ expectedRevision: Number.MAX_SAFE_INTEGER - 1 },
|
|
166
|
+
async (data) => {
|
|
167
|
+
expect(data).toEqual({ expectedRevision: Number.MAX_SAFE_INTEGER - 1 })
|
|
168
|
+
return { documentId: 'doc', revision: Number.MAX_SAFE_INTEGER }
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
expect(value.result).toEqual({ documentId: 'doc', revision: Number.MAX_SAFE_INTEGER })
|
|
172
|
+
})
|
|
173
|
+
it('does not classify unrelated or malformed conflicts by message', () => {
|
|
174
|
+
const conflict = ERR_CONFLICT({ message: 'This document has changed.' })
|
|
175
|
+
expect(documentMutationError(conflict)).toBe(conflict)
|
|
176
|
+
const malformed = { code: 'ERR_DOCUMENT_STALE', details: { reason: 'revision_mismatch' } }
|
|
177
|
+
expect(documentMutationError(malformed)).toBe(malformed)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('transports the authorized structural receipt on committed tree hook failure without diagnostics', async () => {
|
|
182
|
+
const receipt = {
|
|
183
|
+
documentId: 'doc',
|
|
184
|
+
revision: 3,
|
|
185
|
+
affectedDocuments: [{ documentId: 'allowed-sibling', revision: 4 }],
|
|
186
|
+
scheduledPublicationsNeedReconfirmation: true,
|
|
187
|
+
}
|
|
188
|
+
const { value, wire } = await roundTrip({}, async () => {
|
|
189
|
+
throw ERR_TREE_HOOK_COMMITTED({
|
|
190
|
+
message: 'private hook diagnostics',
|
|
191
|
+
cause: new Error('SQL secret'),
|
|
192
|
+
details: { ...receipt, change: 'reorder', affectedDocumentIds: ['private'], internal: true },
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
expect(value.error).toEqual({
|
|
196
|
+
code: 'ERR_TREE_HOOK_COMMITTED',
|
|
197
|
+
message: 'The structure was saved, but a follow-up action failed.',
|
|
198
|
+
details: receipt,
|
|
199
|
+
})
|
|
200
|
+
expect(wire).not.toMatch(/private|SQL secret|internal/)
|
|
201
|
+
})
|
|
202
|
+
it('transports unavailable-document errors without provider details', async () => {
|
|
203
|
+
const { value, wire } = await roundTrip({}, async () => {
|
|
204
|
+
throw ERR_NOT_FOUND({ message: 'private unavailable reason', details: { internal: true } })
|
|
205
|
+
})
|
|
206
|
+
expect(value.error).toEqual({
|
|
207
|
+
code: 'ERR_NOT_FOUND',
|
|
208
|
+
message: 'This document is no longer available.',
|
|
209
|
+
})
|
|
210
|
+
expect(wire).not.toMatch(/private|internal/)
|
|
211
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { withEditableRevision } from './editable-revision.js'
|
|
4
|
+
|
|
5
|
+
describe('editable response revision binding', () => {
|
|
6
|
+
const observed = { id: 'document', versionId: 'current', revision: 7 }
|
|
7
|
+
it('restores only the source revision without changing populated targets', () => {
|
|
8
|
+
const related = { id: 'related', versionId: 'published', fields: {} }
|
|
9
|
+
expect(
|
|
10
|
+
withEditableRevision({ id: 'document', versionId: 'current', fields: { related } }, observed)
|
|
11
|
+
).toEqual({ id: 'document', versionId: 'current', revision: 7, fields: { related } })
|
|
12
|
+
expect(related).not.toHaveProperty('revision')
|
|
13
|
+
})
|
|
14
|
+
it.each([
|
|
15
|
+
{ id: 'document', versionId: 'historical' },
|
|
16
|
+
{ id: 'another-document', versionId: 'current' },
|
|
17
|
+
])('rejects a transferred observation: %o', (document) => {
|
|
18
|
+
expect(() => withEditableRevision(document, observed)).toThrow('source observation')
|
|
19
|
+
})
|
|
20
|
+
it('rejects a row removed from the editable result', () => {
|
|
21
|
+
expect(() => withEditableRevision(observed, undefined)).toThrow('source observation')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ERR_DATABASE, isDocumentRevision } from '@byline/core'
|
|
2
|
+
|
|
3
|
+
/** Restore metadata stripped by schema parsing only onto the exact source version.
|
|
4
|
+
* Never attach by array position: filtering/reordering must not transfer tokens. */
|
|
5
|
+
export function withEditableRevision<T extends object>(
|
|
6
|
+
document: T,
|
|
7
|
+
observation: { id: string; versionId: string; revision: number } | undefined
|
|
8
|
+
): T & { revision: number } {
|
|
9
|
+
if (
|
|
10
|
+
!observation ||
|
|
11
|
+
!isDocumentRevision(observation.revision) ||
|
|
12
|
+
Reflect.get(document, 'id') !== observation.id ||
|
|
13
|
+
Reflect.get(document, 'versionId') !== observation.versionId
|
|
14
|
+
) {
|
|
15
|
+
throw ERR_DATABASE({ message: 'Editable response no longer matches its source observation' })
|
|
16
|
+
}
|
|
17
|
+
return { ...document, revision: observation.revision }
|
|
18
|
+
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* switcher works on the sign-in surface — the DB write simply skips.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
28
|
+
import { createMiddleware, createServerFn } from '@tanstack/react-start'
|
|
29
29
|
|
|
30
30
|
import type { AccountResponse } from '@byline/admin/admin-account'
|
|
31
31
|
import { setPreferredLocaleCommand } from '@byline/admin/admin-account'
|
|
@@ -34,6 +34,7 @@ import { getAdminRequestContext } from '@byline/client/server'
|
|
|
34
34
|
|
|
35
35
|
import { clearAdminLocaleCookie, setAdminLocaleCookie } from '../../i18n/locale-cookie.js'
|
|
36
36
|
import { bylineCore } from '../../integrations/byline-core.js'
|
|
37
|
+
import { expectedSession } from '../../integrations/session-coordination.js'
|
|
37
38
|
|
|
38
39
|
export interface SetAdminLocaleInput {
|
|
39
40
|
/** BCP 47 tag, or `null` to clear the preference. */
|
|
@@ -53,9 +54,18 @@ export interface SetAdminLocaleResult {
|
|
|
53
54
|
account: AccountResponse | null
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
const localeSession = createMiddleware({ type: 'function' }).client(({ next }) =>
|
|
58
|
+
next({
|
|
59
|
+
sendContext: {
|
|
60
|
+
expectedSessionId: expectedSession(),
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
|
|
56
65
|
export const setAdminLocaleFn = createServerFn({ method: 'POST' })
|
|
66
|
+
.middleware([localeSession])
|
|
57
67
|
.validator((input: SetAdminLocaleInput) => input)
|
|
58
|
-
.handler(async ({ data }): Promise<SetAdminLocaleResult> => {
|
|
68
|
+
.handler(async ({ data, context: caller }): Promise<SetAdminLocaleResult> => {
|
|
59
69
|
const core = bylineCore()
|
|
60
70
|
const locales = core.config.i18n.admin.locales
|
|
61
71
|
|
|
@@ -87,6 +97,9 @@ export const setAdminLocaleFn = createServerFn({ method: 'POST' })
|
|
|
87
97
|
|
|
88
98
|
try {
|
|
89
99
|
const context = await getAdminRequestContext()
|
|
100
|
+
if (!caller.expectedSessionId || caller.expectedSessionId !== context.sessionId) {
|
|
101
|
+
return { ok: true as const, locale: data.locale, account: null }
|
|
102
|
+
}
|
|
90
103
|
const account = await setPreferredLocaleCommand(
|
|
91
104
|
context,
|
|
92
105
|
{ locale: data.locale },
|
|
@@ -94,7 +107,10 @@ export const setAdminLocaleFn = createServerFn({ method: 'POST' })
|
|
|
94
107
|
)
|
|
95
108
|
return { ok: true as const, locale: data.locale, account }
|
|
96
109
|
} catch (err) {
|
|
97
|
-
if (
|
|
110
|
+
if (
|
|
111
|
+
err instanceof AuthError &&
|
|
112
|
+
(err.code === AuthErrorCodes.UNAUTHENTICATED || err.code === AuthErrorCodes.ACCESS_EXPIRED)
|
|
113
|
+
) {
|
|
98
114
|
// Expected on the pre-auth path. Cookie already written.
|
|
99
115
|
return { ok: true as const, locale: data.locale, account: null }
|
|
100
116
|
}
|
|
@@ -18,7 +18,11 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
18
18
|
|
|
19
19
|
import { clearPreviewCookie } from '@byline/client/server'
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
22
|
+
|
|
23
|
+
export const disablePreviewModeFn = createServerFn({ method: 'POST' })
|
|
24
|
+
.middleware([adminSessionMiddleware])
|
|
25
|
+
.handler(async () => {
|
|
26
|
+
clearPreviewCookie()
|
|
27
|
+
return { status: 'ok' as const, preview: false as const }
|
|
28
|
+
})
|
|
@@ -19,8 +19,12 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
19
19
|
|
|
20
20
|
import { getAdminRequestContext, setPreviewCookie } from '@byline/client/server'
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
23
|
+
|
|
24
|
+
export const enablePreviewModeFn = createServerFn({ method: 'POST' })
|
|
25
|
+
.middleware([adminSessionMiddleware])
|
|
26
|
+
.handler(async () => {
|
|
27
|
+
await getAdminRequestContext()
|
|
28
|
+
setPreviewCookie()
|
|
29
|
+
return { status: 'ok' as const, preview: true as const }
|
|
30
|
+
})
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { getAdminBylineClient } from '@byline/client/server'
|
|
10
|
-
import { getCollectionDefinition } from '@byline/core'
|
|
10
|
+
import { ERR_NOT_FOUND, getCollectionDefinition } from '@byline/core'
|
|
11
11
|
|
|
12
12
|
import { resolveAdminDocumentRead } from './admin-document-presentation.js'
|
|
13
13
|
import { serialise } from './serialise.js'
|
|
@@ -27,6 +27,10 @@ export interface SingletonDocumentReadInput {
|
|
|
27
27
|
*/
|
|
28
28
|
export async function readSingletonDocument(data: SingletonDocumentReadInput) {
|
|
29
29
|
const handle = getAdminBylineClient().singleton(data.singleton)
|
|
30
|
-
const {
|
|
31
|
-
|
|
30
|
+
const {
|
|
31
|
+
options: { status: _status, ...options },
|
|
32
|
+
} = resolveAdminDocumentRead(getCollectionDefinition(data.singleton), data)
|
|
33
|
+
const snapshot = await handle.getForEdit(options)
|
|
34
|
+
if (snapshot == null) throw ERR_NOT_FOUND({ message: 'Singleton document is unavailable' })
|
|
35
|
+
return snapshot.state === 'empty' ? null : serialise(snapshot.document)
|
|
32
36
|
}
|
|
@@ -10,10 +10,19 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
10
10
|
|
|
11
11
|
import { getAdminBylineClient } from '@byline/client/server'
|
|
12
12
|
|
|
13
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
14
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
13
15
|
import { serialise } from '../serialise.js'
|
|
14
16
|
|
|
15
17
|
export const cancelSingletonScheduledPublish = createServerFn({ method: 'POST' })
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
|
|
18
|
+
.middleware([adminSessionMiddleware])
|
|
19
|
+
.validator((input: { expectedRevision: number; singleton: string }) => input)
|
|
20
|
+
.handler(
|
|
21
|
+
withDocumentMutationErrors(async ({ data }) =>
|
|
22
|
+
serialise(
|
|
23
|
+
await getAdminBylineClient()
|
|
24
|
+
.singleton(data.singleton)
|
|
25
|
+
.cancelScheduledPublish({ expectedRevision: data.expectedRevision })
|
|
26
|
+
)
|
|
27
|
+
)
|
|
19
28
|
)
|