dineway 0.1.8 → 0.1.11
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/LICENSE +9 -0
- package/README.md +63 -17
- package/dist/activity-events-BsMaXdJa.mjs +540 -0
- package/dist/allowed-origins-DG86sH8U.mjs +68 -0
- package/dist/api/route-utils.d.mts +41 -0
- package/dist/api/route-utils.mjs +26 -0
- package/dist/api/schemas/index.d.mts +3 -0
- package/dist/api/schemas/index.mjs +6 -0
- package/dist/api/schemas/setup.d.mts +42 -0
- package/dist/api/schemas/setup.mjs +39 -0
- package/dist/api-Cmy8Rjk5.mjs +2704 -0
- package/dist/api-tokens-Bu3ez1MO.mjs +153 -0
- package/dist/api-tokens-DzloJxuh.mjs +3 -0
- package/dist/{apply-iVSqz2qs.mjs → apply-Co5imxxT.mjs} +15 -689
- package/dist/astro/index.d.mts +10 -6
- package/dist/astro/index.mjs +86 -11
- package/dist/astro/middleware/auth.d.mts +10 -7
- package/dist/astro/middleware/auth.mjs +19 -104
- package/dist/astro/middleware/redirect.mjs +24 -14
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +86 -145
- package/dist/astro/routes/PluginRegistry.d.mts +14 -0
- package/dist/astro/routes/PluginRegistry.mjs +24 -0
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.d.mts +14 -0
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +65 -0
- package/dist/astro/routes/api/admin/allowed-domains/index.d.mts +14 -0
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +65 -0
- package/dist/astro/routes/api/admin/api-tokens/_id_.d.mts +10 -0
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +33 -0
- package/dist/astro/routes/api/admin/api-tokens/index.d.mts +16 -0
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +59 -0
- package/dist/astro/routes/api/admin/briefing.d.mts +7 -0
- package/dist/astro/routes/api/admin/briefing.mjs +71 -0
- package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts +9 -0
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +74 -0
- package/dist/astro/routes/api/admin/bylines/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/bylines/index.mjs +61 -0
- package/dist/astro/routes/api/admin/comments/_id_/status.d.mts +7 -0
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +80 -0
- package/dist/astro/routes/api/admin/comments/_id_.d.mts +14 -0
- package/dist/astro/routes/api/admin/comments/_id_.mjs +46 -0
- package/dist/astro/routes/api/admin/comments/bulk.d.mts +7 -0
- package/dist/astro/routes/api/admin/comments/bulk.mjs +36 -0
- package/dist/astro/routes/api/admin/comments/counts.d.mts +7 -0
- package/dist/astro/routes/api/admin/comments/counts.mjs +24 -0
- package/dist/astro/routes/api/admin/comments/index.d.mts +10 -0
- package/dist/astro/routes/api/admin/comments/index.mjs +40 -0
- package/dist/astro/routes/api/admin/context/_id_/history.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/history.mjs +45 -0
- package/dist/astro/routes/api/admin/context/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/index.mjs +45 -0
- package/dist/astro/routes/api/admin/context/_id_/review.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/review.mjs +60 -0
- package/dist/astro/routes/api/admin/context/_id_/supersede.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +63 -0
- package/dist/astro/routes/api/admin/context/diff.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/diff.mjs +49 -0
- package/dist/astro/routes/api/admin/context/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/context/index.mjs +71 -0
- package/dist/astro/routes/api/admin/context/stale.d.mts +7 -0
- package/dist/astro/routes/api/admin/context/stale.mjs +49 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +51 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.d.mts +7 -0
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +67 -0
- package/dist/astro/routes/api/admin/hitl-requests/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/hitl-requests/index.mjs +55 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.d.mts +7 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +98 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +33 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.d.mts +18 -0
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +79 -0
- package/dist/astro/routes/api/admin/oauth-clients/index.d.mts +14 -0
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +58 -0
- package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +89 -0
- package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +89 -0
- package/dist/astro/routes/api/admin/plugins/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +54 -0
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +98 -0
- package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +131 -0
- package/dist/astro/routes/api/admin/plugins/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/index.mjs +52 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +36 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +54 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +128 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +61 -0
- package/dist/astro/routes/api/admin/plugins/updates.d.mts +7 -0
- package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +26 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.d.mts +7 -0
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +97 -0
- package/dist/astro/routes/api/admin/review-requests/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/review-requests/index.mjs +31 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +54 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.d.mts +7 -0
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +36 -0
- package/dist/astro/routes/api/admin/themes/marketplace/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +70 -0
- package/dist/astro/routes/api/admin/users/_id_/disable.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +38 -0
- package/dist/astro/routes/api/admin/users/_id_/enable.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +29 -0
- package/dist/astro/routes/api/admin/users/_id_/index.d.mts +8 -0
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +104 -0
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +43 -0
- package/dist/astro/routes/api/admin/users/index.d.mts +7 -0
- package/dist/astro/routes/api/admin/users/index.mjs +54 -0
- package/dist/astro/routes/api/auth/dev-bypass.d.mts +8 -0
- package/dist/astro/routes/api/auth/dev-bypass.mjs +81 -0
- package/dist/astro/routes/api/auth/invite/accept.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/accept.mjs +31 -0
- package/dist/astro/routes/api/auth/invite/complete.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/complete.mjs +54 -0
- package/dist/astro/routes/api/auth/invite/index.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/index.mjs +51 -0
- package/dist/astro/routes/api/auth/invite/register-options.d.mts +7 -0
- package/dist/astro/routes/api/auth/invite/register-options.mjs +44 -0
- package/dist/astro/routes/api/auth/logout.d.mts +7 -0
- package/dist/astro/routes/api/auth/logout.mjs +24 -0
- package/dist/astro/routes/api/auth/magic-link/send.d.mts +7 -0
- package/dist/astro/routes/api/auth/magic-link/send.mjs +48 -0
- package/dist/astro/routes/api/auth/magic-link/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +32 -0
- package/dist/astro/routes/api/auth/me.d.mts +13 -0
- package/dist/astro/routes/api/auth/me.mjs +41 -0
- package/dist/astro/routes/api/auth/mode.d.mts +7 -0
- package/dist/astro/routes/api/auth/mode.mjs +28 -0
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.d.mts +7 -0
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +114 -0
- package/dist/astro/routes/api/auth/oauth/_provider_.d.mts +7 -0
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +58 -0
- package/dist/astro/routes/api/auth/passkey/_id_.d.mts +14 -0
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +62 -0
- package/dist/astro/routes/api/auth/passkey/index.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/index.mjs +25 -0
- package/dist/astro/routes/api/auth/passkey/options.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/options.mjs +46 -0
- package/dist/astro/routes/api/auth/passkey/register/options.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +44 -0
- package/dist/astro/routes/api/auth/passkey/register/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +59 -0
- package/dist/astro/routes/api/auth/passkey/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/passkey/verify.mjs +47 -0
- package/dist/astro/routes/api/auth/signup/complete.d.mts +7 -0
- package/dist/astro/routes/api/auth/signup/complete.mjs +55 -0
- package/dist/astro/routes/api/auth/signup/request.d.mts +7 -0
- package/dist/astro/routes/api/auth/signup/request.mjs +44 -0
- package/dist/astro/routes/api/auth/signup/verify.d.mts +7 -0
- package/dist/astro/routes/api/auth/signup/verify.mjs +32 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts +14 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +193 -0
- package/dist/astro/routes/api/content/_collection_/_id_/compare.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +17 -0
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +36 -0
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +39 -0
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +31 -0
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +78 -0
- package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +92 -0
- package/dist/astro/routes/api/content/_collection_/_id_/restore.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +36 -0
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +19 -0
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +75 -0
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts +14 -0
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +85 -0
- package/dist/astro/routes/api/content/_collection_/_id_/translations.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +40 -0
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +36 -0
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts +9 -0
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +114 -0
- package/dist/astro/routes/api/content/_collection_/index.d.mts +8 -0
- package/dist/astro/routes/api/content/_collection_/index.mjs +74 -0
- package/dist/astro/routes/api/content/_collection_/trash.d.mts +7 -0
- package/dist/astro/routes/api/content/_collection_/trash.mjs +23 -0
- package/dist/astro/routes/api/dashboard.d.mts +7 -0
- package/dist/astro/routes/api/dashboard.mjs +26 -0
- package/dist/astro/routes/api/dev/emails.d.mts +8 -0
- package/dist/astro/routes/api/dev/emails.mjs +17 -0
- package/dist/astro/routes/api/health.d.mts +7 -0
- package/dist/astro/routes/api/health.mjs +34 -0
- package/dist/astro/routes/api/import/probe.d.mts +17 -0
- package/dist/astro/routes/api/import/probe.mjs +33 -0
- package/dist/astro/routes/api/import/wordpress/analyze.d.mts +87 -0
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +305 -0
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +37 -0
- package/dist/astro/routes/api/import/wordpress/execute.mjs +197 -0
- package/dist/astro/routes/api/import/wordpress/media.d.mts +35 -0
- package/dist/astro/routes/api/import/wordpress/media.mjs +222 -0
- package/dist/astro/routes/api/import/wordpress/prepare.d.mts +19 -0
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +155 -0
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +21 -0
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +289 -0
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +15 -0
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +69 -0
- package/dist/astro/routes/api/import/wordpress-plugin/callback.d.mts +7 -0
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +28 -0
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +19 -0
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +268 -0
- package/dist/astro/routes/api/manifest.d.mts +7 -0
- package/dist/astro/routes/api/manifest.mjs +50 -0
- package/dist/astro/routes/api/mcp.d.mts +15 -0
- package/dist/astro/routes/api/mcp.mjs +2700 -0
- package/dist/astro/routes/api/media/_id_/confirm.d.mts +10 -0
- package/dist/astro/routes/api/media/_id_/confirm.mjs +59 -0
- package/dist/astro/routes/api/media/_id_.d.mts +22 -0
- package/dist/astro/routes/api/media/_id_.mjs +81 -0
- package/dist/astro/routes/api/media/file/_...key_.d.mts +7 -0
- package/dist/astro/routes/api/media/file/_...key_.mjs +49 -0
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.d.mts +14 -0
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +49 -0
- package/dist/astro/routes/api/media/providers/_providerId_/index.d.mts +14 -0
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +72 -0
- package/dist/astro/routes/api/media/providers/index.d.mts +10 -0
- package/dist/astro/routes/api/media/providers/index.mjs +18 -0
- package/dist/astro/routes/api/media/upload-url.d.mts +10 -0
- package/dist/astro/routes/api/media/upload-url.mjs +82 -0
- package/dist/astro/routes/api/media.d.mts +16 -0
- package/dist/astro/routes/api/media.mjs +137 -0
- package/dist/astro/routes/api/menus/_name_/items.d.mts +9 -0
- package/{src/astro/routes/api/menus/[name]/items.ts → dist/astro/routes/api/menus/_name_/items.mjs} +63 -105
- package/dist/astro/routes/api/menus/_name_/reorder.d.mts +7 -0
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +77 -0
- package/dist/astro/routes/api/menus/_name_.d.mts +9 -0
- package/dist/astro/routes/api/menus/_name_.mjs +123 -0
- package/dist/astro/routes/api/menus/index.d.mts +8 -0
- package/dist/astro/routes/api/menus/index.mjs +84 -0
- package/dist/astro/routes/api/oauth/authorize.d.mts +8 -0
- package/dist/astro/routes/api/oauth/authorize.mjs +265 -0
- package/dist/astro/routes/api/oauth/device/authorize.d.mts +7 -0
- package/dist/astro/routes/api/oauth/device/authorize.mjs +30 -0
- package/dist/astro/routes/api/oauth/device/code.d.mts +7 -0
- package/dist/astro/routes/api/oauth/device/code.mjs +34 -0
- package/dist/astro/routes/api/oauth/device/token.d.mts +7 -0
- package/dist/astro/routes/api/oauth/device/token.mjs +45 -0
- package/dist/astro/routes/api/oauth/register.d.mts +8 -0
- package/dist/astro/routes/api/oauth/register.mjs +115 -0
- package/dist/astro/routes/api/oauth/token/refresh.d.mts +7 -0
- package/dist/astro/routes/api/oauth/token/refresh.mjs +28 -0
- package/dist/astro/routes/api/oauth/token/revoke.d.mts +7 -0
- package/dist/astro/routes/api/oauth/token/revoke.mjs +25 -0
- package/dist/astro/routes/api/oauth/token.d.mts +8 -0
- package/dist/astro/routes/api/oauth/token.mjs +138 -0
- package/dist/astro/routes/api/openapi.json.d.mts +7 -0
- package/dist/astro/routes/api/openapi.json.mjs +2638 -0
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts +11 -0
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +77 -0
- package/dist/astro/routes/api/redirects/404s/index.d.mts +9 -0
- package/dist/astro/routes/api/redirects/404s/index.mjs +62 -0
- package/dist/astro/routes/api/redirects/404s/summary.d.mts +7 -0
- package/dist/astro/routes/api/redirects/404s/summary.mjs +34 -0
- package/dist/astro/routes/api/redirects/_id_.d.mts +9 -0
- package/dist/astro/routes/api/redirects/_id_.mjs +152 -0
- package/dist/astro/routes/api/redirects/index.d.mts +8 -0
- package/dist/astro/routes/api/redirects/index.mjs +97 -0
- package/dist/astro/routes/api/revisions/_revisionId_/index.d.mts +7 -0
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +16 -0
- package/dist/astro/routes/api/revisions/_revisionId_/restore.d.mts +7 -0
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +23 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts +9 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +98 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts +8 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +80 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.d.mts +7 -0
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +67 -0
- package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts +9 -0
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +97 -0
- package/dist/astro/routes/api/schema/collections/index.d.mts +8 -0
- package/dist/astro/routes/api/schema/collections/index.mjs +77 -0
- package/dist/astro/routes/api/schema/index.d.mts +7 -0
- package/dist/astro/routes/api/schema/index.mjs +79 -0
- package/dist/astro/routes/api/schema/orphans/_slug_.d.mts +7 -0
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +58 -0
- package/dist/astro/routes/api/schema/orphans/index.d.mts +7 -0
- package/dist/astro/routes/api/schema/orphans/index.mjs +53 -0
- package/dist/astro/routes/api/search/enable.d.mts +15 -0
- package/dist/astro/routes/api/search/enable.mjs +55 -0
- package/dist/astro/routes/api/search/index.d.mts +16 -0
- package/dist/astro/routes/api/search/index.mjs +52 -0
- package/dist/astro/routes/api/search/rebuild.d.mts +13 -0
- package/dist/astro/routes/api/search/rebuild.mjs +48 -0
- package/dist/astro/routes/api/search/stats.d.mts +10 -0
- package/dist/astro/routes/api/search/stats.mjs +28 -0
- package/dist/astro/routes/api/search/suggest.d.mts +15 -0
- package/dist/astro/routes/api/search/suggest.mjs +43 -0
- package/dist/astro/routes/api/sections/_slug_.d.mts +9 -0
- package/dist/astro/routes/api/sections/_slug_.mjs +156 -0
- package/dist/astro/routes/api/sections/index.d.mts +8 -0
- package/dist/astro/routes/api/sections/index.mjs +99 -0
- package/dist/astro/routes/api/settings/email.d.mts +17 -0
- package/dist/astro/routes/api/settings/email.mjs +102 -0
- package/dist/astro/routes/api/settings.d.mts +20 -0
- package/dist/astro/routes/api/settings.mjs +101 -0
- package/dist/astro/routes/api/setup/admin-verify.d.mts +7 -0
- package/dist/astro/routes/api/setup/admin-verify.mjs +67 -0
- package/dist/astro/routes/api/setup/admin.d.mts +7 -0
- package/dist/astro/routes/api/setup/admin.mjs +68 -0
- package/dist/astro/routes/api/setup/dev-bypass.d.mts +8 -0
- package/dist/astro/routes/api/setup/dev-bypass.mjs +137 -0
- package/dist/astro/routes/api/setup/dev-reset.d.mts +7 -0
- package/dist/astro/routes/api/setup/dev-reset.mjs +22 -0
- package/dist/astro/routes/api/setup/index.d.mts +7 -0
- package/dist/astro/routes/api/setup/index.mjs +93 -0
- package/dist/astro/routes/api/setup/status.d.mts +7 -0
- package/dist/astro/routes/api/setup/status.mjs +57 -0
- package/dist/astro/routes/api/snapshot.d.mts +7 -0
- package/dist/astro/routes/api/snapshot.mjs +227 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts +18 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +189 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts +14 -0
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +113 -0
- package/dist/astro/routes/api/taxonomies/index.d.mts +14 -0
- package/dist/astro/routes/api/taxonomies/index.mjs +103 -0
- package/dist/astro/routes/api/themes/preview.d.mts +7 -0
- package/dist/astro/routes/api/themes/preview.mjs +47 -0
- package/dist/astro/routes/api/typegen.d.mts +17 -0
- package/dist/astro/routes/api/typegen.mjs +75 -0
- package/dist/astro/routes/api/well-known/auth.d.mts +7 -0
- package/dist/astro/routes/api/well-known/auth.mjs +42 -0
- package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts +7 -0
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +33 -0
- package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts +7 -0
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +21 -0
- package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts +7 -0
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +88 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts +8 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +158 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts +7 -0
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +104 -0
- package/dist/astro/routes/api/widget-areas/_name_.d.mts +8 -0
- package/dist/astro/routes/api/widget-areas/_name_.mjs +99 -0
- package/dist/astro/routes/api/widget-areas/index.d.mts +8 -0
- package/dist/astro/routes/api/widget-areas/index.mjs +108 -0
- package/dist/astro/routes/api/widget-components.d.mts +7 -0
- package/dist/astro/routes/api/widget-components.mjs +15 -0
- package/dist/astro/routes/robots.txt.d.mts +7 -0
- package/dist/astro/routes/robots.txt.mjs +60 -0
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts +7 -0
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +70 -0
- package/dist/astro/routes/sitemap.xml.d.mts +7 -0
- package/dist/astro/routes/sitemap.xml.mjs +63 -0
- package/dist/astro/types.d.mts +41 -9
- package/dist/auth/providers/github-admin.d.mts +9 -0
- package/dist/auth/providers/github-admin.mjs +27 -0
- package/dist/auth/providers/github.d.mts +12 -0
- package/dist/auth/providers/github.mjs +17 -0
- package/dist/auth/providers/google-admin.d.mts +9 -0
- package/dist/auth/providers/google-admin.mjs +43 -0
- package/dist/auth/providers/google.d.mts +12 -0
- package/dist/auth/providers/google.mjs +17 -0
- package/dist/auth-control-guard-DKUe_1oa.mjs +13 -0
- package/dist/authorize-BBj8C6Y8.mjs +36 -0
- package/dist/briefing-BrXCuMEE.mjs +1294 -0
- package/dist/briefing-ClWw4mc9.mjs +29 -0
- package/dist/{byline-OhH2dlRu.mjs → byline-naZxOPSa.mjs} +3 -3
- package/dist/{bylines-BGpD9_hy.mjs → bylines-BcOPh6Ej.mjs} +20 -53
- package/dist/bylines-HfUKum_j.d.mts +2023 -0
- package/dist/{cache-BdSY-gQN.mjs → cache-DEbQ13c9.mjs} +21 -11
- package/dist/challenge-store-DHMgBGOq.mjs +48 -0
- package/dist/cli/index.mjs +142 -22
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +3 -3
- package/dist/comment-DFO-gWDH.mjs +246 -0
- package/dist/comments-Gy3zLBaP.mjs +186 -0
- package/dist/components-DND2rd3D.mjs +107 -0
- package/dist/{content-DWi4d0rT.mjs → content-CyLkb-qH.mjs} +33 -44
- package/dist/context-bE5Kyvcj.mjs +184 -0
- package/dist/context-nxMyOe3p.mjs +849 -0
- package/dist/context-route-helpers-D-6uCQ0S.mjs +45 -0
- package/dist/context-types-C-LwdAxx.mjs +23 -0
- package/dist/cron-DGzVTtJp.mjs +263 -0
- package/dist/dashboard-DqnYU8EU.mjs +120 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.mjs +3 -3
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +1 -2
- package/dist/device-flow-7AhWNwCK.mjs +487 -0
- package/dist/email-console-CgLVZbcn.mjs +36 -0
- package/dist/entity-aliases-C0v-yNET.mjs +51 -0
- package/dist/error-DEGjx2Xw.mjs +435 -0
- package/dist/escape-mNZr4t2A.mjs +8 -0
- package/dist/experimental-workflows-DldxJlqV.mjs +38 -0
- package/dist/fts-manager-B1pTNEG_.mjs +297 -0
- package/dist/hash-CDX7M0ze.mjs +32 -0
- package/dist/hitl-requests-Bx3Bkk9l.mjs +118 -0
- package/dist/hitl-route-helpers-DMmJRS7B.mjs +96 -0
- package/dist/import-DD3f2jkc.mjs +243 -0
- package/dist/import-DVZcYlDp.mjs +1323 -0
- package/dist/index-CkljPf5F.d.mts +227 -0
- package/dist/index.d.mts +15 -11
- package/dist/index.mjs +60 -22
- package/dist/{loader-sMG4TZ-u.mjs → loader-PZnPxFLc.mjs} +42 -5
- package/dist/{manifest-schema-D1MSVnoI.mjs → manifest-schema-DYoCQ5np.mjs} +22 -10
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -1
- package/dist/media/local-runtime.d.mts +11 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-DMTr80Gv.mjs → media-_7Fxdu45.mjs} +1 -1
- package/dist/menus-BacxVCCo.mjs +312 -0
- package/dist/menus-CrzHokKj.mjs +3502 -0
- package/dist/normalize-C49G_o1k.mjs +126 -0
- package/dist/oauth-authorization-C1qiw4hd.mjs +283 -0
- package/dist/oauth-clients-CvWatf5p.mjs +298 -0
- package/dist/oauth-state-store-hSdzxsEe.mjs +48 -0
- package/dist/oauth-user-lookup-B4OcmsLV.mjs +25 -0
- package/dist/options-z8VVg1Ll.mjs +114 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/parse-BeQXIt1U.mjs +88 -0
- package/dist/passkey-config-Daqs5fjq.mjs +42 -0
- package/dist/{patterns-CrCYkMBb.mjs → patterns-K0DLqWir.mjs} +53 -1
- package/dist/{placeholder-Cp8g5Emj.mjs → placeholder-C2P5fKa4.mjs} +1 -126
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +4 -4
- package/dist/preview-C_4DyVox.mjs +788 -0
- package/dist/public-url-BB_umF5G.mjs +71 -0
- package/dist/{query-kDmwCsHh.mjs → query-RiobVwB5.mjs} +93 -19
- package/dist/rate-limit-CbJoj_fT.mjs +112 -0
- package/dist/{redirect-DnEWAkVg.mjs → redirect-CGl64yOX.mjs} +9 -5
- package/dist/redirect-ClSmMOtC.mjs +16 -0
- package/dist/redirects-B69T59hK.mjs +499 -0
- package/dist/redirects-CqaxraTO.mjs +1070 -0
- package/dist/{registry-C0zjeB9P.mjs → registry-C-_hxLqa.mjs} +26 -294
- package/dist/request-meta-Bd0mQfiS.mjs +130 -0
- package/dist/review-requests-C2DIHwlJ.mjs +148 -0
- package/dist/review-requests-DIyjw-K_.mjs +79 -0
- package/dist/{runner-CFI6B6J2.d.mts → runner-9eIQXuc2.d.mts} +1 -1
- package/dist/{index-yvc6E_17.d.mts → runtime-C4-7y7xK.d.mts} +1539 -2007
- package/dist/runtime.d.mts +10 -6
- package/dist/runtime.mjs +3 -3
- package/dist/schema-BNpI53of.mjs +40 -0
- package/dist/search-DM6CVti3.mjs +337 -0
- package/dist/secrets-dI8zzTV7.mjs +160 -0
- package/dist/sections-DZFyAQXd.mjs +338 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +18 -13
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo-BBgTCOYU.mjs +85 -0
- package/dist/seo-CUQctrog.mjs +129 -0
- package/dist/service-CSfcQguB.mjs +194 -0
- package/dist/settings-4XnpVMOS.mjs +223 -0
- package/dist/settings-Bw93cLfe.mjs +50 -0
- package/dist/setup-complete-DidsDQ1e.mjs +21 -0
- package/dist/setup-nonce-pml1PMKo.mjs +17 -0
- package/dist/sidecar-client-vzwV98K4.mjs +66 -0
- package/dist/site-activity-B8FjLIVh.mjs +104 -0
- package/dist/site-context-Bpu_Paur.mjs +4122 -0
- package/dist/site-url-CYIcO0Tj.mjs +12 -0
- package/dist/slugify-PDTDtMXp.mjs +30 -0
- package/dist/ssrf-CmM76lLV.mjs +248 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +2 -2
- package/dist/{taxonomies-1s5PaS_8.mjs → taxonomies-BvBgfzn3.mjs} +11 -7
- package/dist/taxonomies-CpqGcIJD.mjs +355 -0
- package/dist/taxonomy-D5cbhc8u.mjs +165 -0
- package/dist/{tokens-CJz9ubV6.mjs → tokens-DLTo4dO2.mjs} +1 -1
- package/dist/{transport-DB5eDN4x.mjs → transport-C9e_h-BF.mjs} +5 -4
- package/dist/trusted-proxy-Bi0Cuk5n.mjs +30 -0
- package/dist/{types-BawVha09.mjs → types-Bs6lTBBW.mjs} +1 -1
- package/dist/types-C982qI5I.d.mts +344 -0
- package/dist/types-D4XVOt01.d.mts +165 -0
- package/dist/{types-Cj0KMIZV.d.mts → types-DgfUZqcd.d.mts} +54 -16
- package/dist/{types-BuMDPy5C.d.mts → types-IPACEM14.d.mts} +6 -0
- package/dist/user-CcXq-zoL.mjs +154 -0
- package/dist/utils-D2in-zwy.mjs +285 -0
- package/dist/{validate-BZ5wnLLp.mjs → validate-BJgA6TW_.mjs} +1 -1
- package/dist/{validate-IPf8n4Fj.d.mts → validate-JCZihRIa.d.mts} +3 -3
- package/dist/version-DH53KCQd.mjs +6 -0
- package/dist/widgets-B7Q_7bxN.mjs +104 -0
- package/dist/wordpress-slugs-BevajWrC.mjs +14 -0
- package/dist/zod-generator-DBVP8D0P.mjs +132 -0
- package/locals.d.ts +1 -6
- package/package.json +96 -41
- package/src/components/DinewayHead.astro +8 -4
- package/src/components/DinewayImage.astro +7 -5
- package/src/components/DinewayMedia.astro +9 -3
- package/src/components/Gallery.astro +5 -3
- package/src/components/Image.astro +5 -1
- package/src/components/InlinePortableTextEditor.tsx +68 -19
- package/dist/error-BmL6QipT.mjs +0 -30
- package/dist/search-Hlm6g8Td.mjs +0 -11200
- package/dist/version-DxxaFHZ_.mjs +0 -6
- package/src/astro/routes/PluginRegistry.tsx +0 -21
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +0 -112
- package/src/astro/routes/api/admin/allowed-domains/index.ts +0 -108
- package/src/astro/routes/api/admin/api-tokens/[id].ts +0 -44
- package/src/astro/routes/api/admin/api-tokens/index.ts +0 -90
- package/src/astro/routes/api/admin/briefing.ts +0 -76
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +0 -90
- package/src/astro/routes/api/admin/bylines/index.ts +0 -74
- package/src/astro/routes/api/admin/comments/[id]/status.ts +0 -120
- package/src/astro/routes/api/admin/comments/[id].ts +0 -64
- package/src/astro/routes/api/admin/comments/bulk.ts +0 -42
- package/src/astro/routes/api/admin/comments/counts.ts +0 -30
- package/src/astro/routes/api/admin/comments/index.ts +0 -46
- package/src/astro/routes/api/admin/context/[id]/history.ts +0 -35
- package/src/astro/routes/api/admin/context/[id]/index.ts +0 -35
- package/src/astro/routes/api/admin/context/[id]/review.ts +0 -57
- package/src/astro/routes/api/admin/context/[id]/supersede.ts +0 -58
- package/src/astro/routes/api/admin/context/diff.ts +0 -35
- package/src/astro/routes/api/admin/context/index.ts +0 -69
- package/src/astro/routes/api/admin/context/stale.ts +0 -35
- package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +0 -38
- package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +0 -54
- package/src/astro/routes/api/admin/hitl-requests/index.ts +0 -38
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +0 -132
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +0 -51
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +0 -137
- package/src/astro/routes/api/admin/oauth-clients/index.ts +0 -95
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +0 -91
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +0 -91
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +0 -38
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +0 -98
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +0 -154
- package/src/astro/routes/api/admin/plugins/index.ts +0 -32
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +0 -62
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +0 -33
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +0 -135
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +0 -38
- package/src/astro/routes/api/admin/plugins/updates.ts +0 -28
- package/src/astro/routes/api/admin/review-requests/[id]/index.ts +0 -35
- package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +0 -52
- package/src/astro/routes/api/admin/review-requests/index.ts +0 -35
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +0 -33
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +0 -62
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +0 -45
- package/src/astro/routes/api/admin/users/[id]/disable.ts +0 -72
- package/src/astro/routes/api/admin/users/[id]/enable.ts +0 -48
- package/src/astro/routes/api/admin/users/[id]/index.ts +0 -166
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +0 -72
- package/src/astro/routes/api/admin/users/index.ts +0 -66
- package/src/astro/routes/api/auth/dev-bypass.ts +0 -139
- package/src/astro/routes/api/auth/invite/accept.ts +0 -52
- package/src/astro/routes/api/auth/invite/complete.ts +0 -86
- package/src/astro/routes/api/auth/invite/index.ts +0 -99
- package/src/astro/routes/api/auth/invite/register-options.ts +0 -73
- package/src/astro/routes/api/auth/logout.ts +0 -40
- package/src/astro/routes/api/auth/magic-link/send.ts +0 -90
- package/src/astro/routes/api/auth/magic-link/verify.ts +0 -71
- package/src/astro/routes/api/auth/me.ts +0 -60
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +0 -221
- package/src/astro/routes/api/auth/oauth/[provider].ts +0 -120
- package/src/astro/routes/api/auth/passkey/[id].ts +0 -124
- package/src/astro/routes/api/auth/passkey/index.ts +0 -54
- package/src/astro/routes/api/auth/passkey/options.ts +0 -85
- package/src/astro/routes/api/auth/passkey/register/options.ts +0 -88
- package/src/astro/routes/api/auth/passkey/register/verify.ts +0 -119
- package/src/astro/routes/api/auth/passkey/verify.ts +0 -72
- package/src/astro/routes/api/auth/signup/complete.ts +0 -87
- package/src/astro/routes/api/auth/signup/request.ts +0 -89
- package/src/astro/routes/api/auth/signup/verify.ts +0 -53
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +0 -310
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +0 -28
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +0 -68
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +0 -77
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +0 -42
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +0 -107
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +0 -100
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +0 -64
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +0 -31
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +0 -129
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +0 -143
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +0 -50
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +0 -69
- package/src/astro/routes/api/content/[collection]/[id].ts +0 -173
- package/src/astro/routes/api/content/[collection]/index.ts +0 -103
- package/src/astro/routes/api/content/[collection]/trash.ts +0 -33
- package/src/astro/routes/api/dashboard.ts +0 -32
- package/src/astro/routes/api/dev/emails.ts +0 -36
- package/src/astro/routes/api/health.ts +0 -54
- package/src/astro/routes/api/import/probe.ts +0 -47
- package/src/astro/routes/api/import/wordpress/analyze.ts +0 -523
- package/src/astro/routes/api/import/wordpress/execute.ts +0 -330
- package/src/astro/routes/api/import/wordpress/media.ts +0 -338
- package/src/astro/routes/api/import/wordpress/prepare.ts +0 -212
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +0 -425
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +0 -111
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +0 -58
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +0 -399
- package/src/astro/routes/api/manifest.ts +0 -75
- package/src/astro/routes/api/mcp.ts +0 -125
- package/src/astro/routes/api/media/[id]/confirm.ts +0 -93
- package/src/astro/routes/api/media/[id].ts +0 -145
- package/src/astro/routes/api/media/file/[...key].ts +0 -79
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +0 -91
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +0 -111
- package/src/astro/routes/api/media/providers/index.ts +0 -30
- package/src/astro/routes/api/media/upload-url.ts +0 -146
- package/src/astro/routes/api/media.ts +0 -204
- package/src/astro/routes/api/menus/[name]/reorder.ts +0 -79
- package/src/astro/routes/api/menus/[name].ts +0 -145
- package/src/astro/routes/api/menus/index.ts +0 -91
- package/src/astro/routes/api/oauth/authorize.ts +0 -430
- package/src/astro/routes/api/oauth/device/authorize.ts +0 -45
- package/src/astro/routes/api/oauth/device/code.ts +0 -56
- package/src/astro/routes/api/oauth/device/token.ts +0 -70
- package/src/astro/routes/api/oauth/register.ts +0 -182
- package/src/astro/routes/api/oauth/token/refresh.ts +0 -38
- package/src/astro/routes/api/oauth/token/revoke.ts +0 -38
- package/src/astro/routes/api/oauth/token.ts +0 -195
- package/src/astro/routes/api/openapi.json.ts +0 -33
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +0 -109
- package/src/astro/routes/api/redirects/404s/index.ts +0 -72
- package/src/astro/routes/api/redirects/404s/summary.ts +0 -33
- package/src/astro/routes/api/redirects/[id].ts +0 -183
- package/src/astro/routes/api/redirects/index.ts +0 -100
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +0 -29
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +0 -62
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -104
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -67
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -45
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +0 -107
- package/src/astro/routes/api/schema/collections/index.ts +0 -61
- package/src/astro/routes/api/schema/index.ts +0 -109
- package/src/astro/routes/api/schema/orphans/[slug].ts +0 -36
- package/src/astro/routes/api/schema/orphans/index.ts +0 -26
- package/src/astro/routes/api/search/enable.ts +0 -64
- package/src/astro/routes/api/search/index.ts +0 -52
- package/src/astro/routes/api/search/rebuild.ts +0 -72
- package/src/astro/routes/api/search/stats.ts +0 -35
- package/src/astro/routes/api/search/suggest.ts +0 -50
- package/src/astro/routes/api/sections/[slug].ts +0 -203
- package/src/astro/routes/api/sections/index.ts +0 -107
- package/src/astro/routes/api/settings/email.ts +0 -150
- package/src/astro/routes/api/settings.ts +0 -116
- package/src/astro/routes/api/setup/admin-verify.ts +0 -122
- package/src/astro/routes/api/setup/admin.ts +0 -104
- package/src/astro/routes/api/setup/dev-bypass.ts +0 -200
- package/src/astro/routes/api/setup/dev-reset.ts +0 -40
- package/src/astro/routes/api/setup/index.ts +0 -128
- package/src/astro/routes/api/setup/status.ts +0 -122
- package/src/astro/routes/api/snapshot.ts +0 -76
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +0 -232
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +0 -131
- package/src/astro/routes/api/taxonomies/index.ts +0 -114
- package/src/astro/routes/api/themes/preview.ts +0 -78
- package/src/astro/routes/api/typegen.ts +0 -114
- package/src/astro/routes/api/well-known/auth.ts +0 -71
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +0 -48
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +0 -39
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +0 -114
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +0 -213
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +0 -126
- package/src/astro/routes/api/widget-areas/[name].ts +0 -135
- package/src/astro/routes/api/widget-areas/index.ts +0 -149
- package/src/astro/routes/api/widget-components.ts +0 -22
- package/src/astro/routes/robots.txt.ts +0 -81
- package/src/astro/routes/sitemap-[collection].xml.ts +0 -104
- package/src/astro/routes/sitemap.xml.ts +0 -92
- /package/dist/{adapters-C2ypTrZZ.d.mts → adapters-BLDldpJg.d.mts} +0 -0
- /package/{src → dist}/astro/routes/admin.astro +0 -0
- /package/dist/{base64-F8-DUraK.mjs → base64-Cz-aU0X1.mjs} +0 -0
- /package/dist/{chunks--4F8ddV4.mjs → chunks-D_jVet6z.mjs} +0 -0
- /package/dist/{config-BXwuX8Bx.mjs → config-CAMFxGaV.mjs} +0 -0
- /package/dist/{db-errors-CEqD7qH9.mjs → db-errors-DKUg_NgF.mjs} +0 -0
- /package/dist/{default-VjJyuuG9.mjs → default-C3PZN-bz.mjs} +0 -0
- /package/dist/{load-Coc9HpHH.mjs → load-D-9NhLmF.mjs} +0 -0
- /package/dist/{mode-47goXBBK.mjs → mode-C80mAZQv.mjs} +0 -0
- /package/dist/{placeholder--wOi4TbO.d.mts → placeholder-CHkLckzK.d.mts} +0 -0
- /package/dist/{request-cache-Dk5qPSOx.mjs → request-cache-DHMRr2Lf.mjs} +0 -0
- /package/dist/{transaction-Cn2rjY78.mjs → transaction-x2tJQ-A1.mjs} +0 -0
- /package/dist/{transport-Wge_IzKl.d.mts → transport-6RefuBdV.d.mts} +0 -0
- /package/dist/{types-griIBQOQ.mjs → types-B9gKVOHk.mjs} +0 -0
- /package/dist/{types-CWbdtiux.d.mts → types-B9qVtiHb.d.mts} +0 -0
- /package/dist/{types-COeOq9nK.mjs → types-DL7Y8D_t.mjs} +0 -0
- /package/dist/{types-BzcUjoqg.d.mts → types-Djdp0cZO.d.mts} +0 -0
- /package/dist/{types-DOrVigru.d.mts → types-Du8jreyC.d.mts} +0 -0
|
@@ -0,0 +1,2704 @@
|
|
|
1
|
+
import { r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
2
|
+
import { r as RevisionRepository, t as ContentRepository } from "./content-CyLkb-qH.mjs";
|
|
3
|
+
import { r as encodeBase64, t as decodeBase64 } from "./base64-Cz-aU0X1.mjs";
|
|
4
|
+
import { t as DinewayValidationError } from "./types-Bs6lTBBW.mjs";
|
|
5
|
+
import { t as MediaRepository } from "./media-_7Fxdu45.mjs";
|
|
6
|
+
import { t as CommentRepository } from "./comment-DFO-gWDH.mjs";
|
|
7
|
+
import { t as withTransaction } from "./transaction-x2tJQ-A1.mjs";
|
|
8
|
+
import { t as RedirectRepository } from "./redirect-CGl64yOX.mjs";
|
|
9
|
+
import { t as BylineRepository } from "./byline-naZxOPSa.mjs";
|
|
10
|
+
import { t as SeoRepository } from "./seo-CUQctrog.mjs";
|
|
11
|
+
import { r as isI18nEnabled } from "./config-CAMFxGaV.mjs";
|
|
12
|
+
import { i as invalidateRedirectCache } from "./cache-DEbQ13c9.mjs";
|
|
13
|
+
import { u as PluginStateRepository } from "./briefing-BrXCuMEE.mjs";
|
|
14
|
+
import { n as SchemaRegistry, t as SchemaError } from "./registry-C-_hxLqa.mjs";
|
|
15
|
+
import { n as hashString } from "./hash-CDX7M0ze.mjs";
|
|
16
|
+
import { n as VERSION, t as COMMIT } from "./version-DH53KCQd.mjs";
|
|
17
|
+
import { i as pluginManifestSchema, r as normalizeManifestRoute } from "./manifest-schema-DYoCQ5np.mjs";
|
|
18
|
+
import { t as DinewayStorageError } from "./types-B9gKVOHk.mjs";
|
|
19
|
+
import { sql } from "kysely";
|
|
20
|
+
import { createGzipDecoder, unpackTar } from "modern-tar";
|
|
21
|
+
|
|
22
|
+
//#region src/api/rev.ts
|
|
23
|
+
/**
|
|
24
|
+
* Generate a _rev token from a content item's version and updatedAt.
|
|
25
|
+
*/
|
|
26
|
+
function encodeRev(item) {
|
|
27
|
+
return encodeBase64(`${item.version}:${item.updatedAt}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decode a _rev token into its components.
|
|
31
|
+
* Returns null if the token is malformed.
|
|
32
|
+
*/
|
|
33
|
+
function decodeRev(rev) {
|
|
34
|
+
try {
|
|
35
|
+
const decoded = decodeBase64(rev);
|
|
36
|
+
const colonIdx = decoded.indexOf(":");
|
|
37
|
+
if (colonIdx === -1) return null;
|
|
38
|
+
const version = parseInt(decoded.slice(0, colonIdx), 10);
|
|
39
|
+
const updatedAt = decoded.slice(colonIdx + 1);
|
|
40
|
+
if (isNaN(version) || !updatedAt) return null;
|
|
41
|
+
return {
|
|
42
|
+
version,
|
|
43
|
+
updatedAt
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate a _rev token against a content item.
|
|
51
|
+
* Returns null if valid (or if no _rev provided), or an error message if invalid.
|
|
52
|
+
*/
|
|
53
|
+
function validateRev(rev, item) {
|
|
54
|
+
if (!rev) return { valid: true };
|
|
55
|
+
const decoded = decodeRev(rev);
|
|
56
|
+
if (!decoded) return {
|
|
57
|
+
valid: false,
|
|
58
|
+
message: "Malformed _rev token"
|
|
59
|
+
};
|
|
60
|
+
if (decoded.version !== item.version || decoded.updatedAt !== item.updatedAt) return {
|
|
61
|
+
valid: false,
|
|
62
|
+
message: "Content has been modified since last read (version conflict)"
|
|
63
|
+
};
|
|
64
|
+
return { valid: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/api/handlers/content.ts
|
|
69
|
+
/**
|
|
70
|
+
* Extract a slug source (title or name) from content data.
|
|
71
|
+
* Returns null if no suitable string field is found.
|
|
72
|
+
*/
|
|
73
|
+
function getSlugSource(data) {
|
|
74
|
+
if (typeof data.title === "string" && data.title.length > 0) return data.title;
|
|
75
|
+
if (typeof data.name === "string" && data.name.length > 0) return data.name;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/** Default SEO values for content without an explicit SEO row */
|
|
79
|
+
const SEO_DEFAULTS = {
|
|
80
|
+
title: null,
|
|
81
|
+
description: null,
|
|
82
|
+
image: null,
|
|
83
|
+
canonical: null,
|
|
84
|
+
noIndex: false
|
|
85
|
+
};
|
|
86
|
+
var ContentUpdateApiError = class extends Error {
|
|
87
|
+
constructor(code, message) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.code = code;
|
|
90
|
+
this.name = "ContentUpdateApiError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Check if a collection has SEO enabled.
|
|
95
|
+
*/
|
|
96
|
+
async function collectionHasSeo(db, collection) {
|
|
97
|
+
return (await db.selectFrom("_dineway_collections").select("has_seo").where("slug", "=", collection).executeTakeFirst())?.has_seo === 1;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Hydrate SEO data on a single content item if the collection has SEO enabled.
|
|
101
|
+
*/
|
|
102
|
+
async function hydrateSeo(db, collection, item, hasSeo) {
|
|
103
|
+
if (!hasSeo) return;
|
|
104
|
+
item.seo = await new SeoRepository(db).get(collection, item.id);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Hydrate SEO data on multiple content items using a single batch query.
|
|
108
|
+
*/
|
|
109
|
+
async function hydrateSeoMany(db, collection, items, hasSeo) {
|
|
110
|
+
if (!hasSeo || items.length === 0) return;
|
|
111
|
+
const seoMap = await new SeoRepository(db).getMany(collection, items.map((i) => i.id));
|
|
112
|
+
for (const item of items) item.seo = seoMap.get(item.id) ?? { ...SEO_DEFAULTS };
|
|
113
|
+
}
|
|
114
|
+
async function hydrateBylines(db, collection, item) {
|
|
115
|
+
const bylineRepo = new BylineRepository(db);
|
|
116
|
+
const bylines = await bylineRepo.getContentBylines(collection, item.id);
|
|
117
|
+
if (bylines.length > 0) {
|
|
118
|
+
item.bylines = bylines.map((c) => ({
|
|
119
|
+
...c,
|
|
120
|
+
source: "explicit"
|
|
121
|
+
}));
|
|
122
|
+
item.byline = bylines[0]?.byline ?? null;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (item.primaryBylineId) item.primaryBylineId = null;
|
|
126
|
+
if (item.authorId) {
|
|
127
|
+
const fallback = await bylineRepo.findByUserId(item.authorId);
|
|
128
|
+
if (fallback) {
|
|
129
|
+
item.bylines = [{
|
|
130
|
+
byline: fallback,
|
|
131
|
+
sortOrder: 0,
|
|
132
|
+
roleLabel: null,
|
|
133
|
+
source: "inferred"
|
|
134
|
+
}];
|
|
135
|
+
item.byline = fallback;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
item.bylines = [];
|
|
140
|
+
item.byline = null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Batch-hydrate bylines for multiple items using two bulk queries instead of N+1.
|
|
144
|
+
*/
|
|
145
|
+
async function hydrateBylinesMany(db, collection, items) {
|
|
146
|
+
if (items.length === 0) return;
|
|
147
|
+
const bylineRepo = new BylineRepository(db);
|
|
148
|
+
const contentIds = items.map((i) => i.id);
|
|
149
|
+
const bylinesMap = await bylineRepo.getContentBylinesMany(collection, contentIds);
|
|
150
|
+
const fallbackAuthorIds = [];
|
|
151
|
+
for (const item of items) if (!bylinesMap.has(item.id) && item.authorId) fallbackAuthorIds.push(item.authorId);
|
|
152
|
+
const uniqueAuthorIds = [...new Set(fallbackAuthorIds)];
|
|
153
|
+
const authorBylineMap = await bylineRepo.findByUserIds(uniqueAuthorIds);
|
|
154
|
+
for (const item of items) {
|
|
155
|
+
const explicit = bylinesMap.get(item.id);
|
|
156
|
+
if (explicit && explicit.length > 0) {
|
|
157
|
+
item.bylines = explicit.map((c) => ({
|
|
158
|
+
...c,
|
|
159
|
+
source: "explicit"
|
|
160
|
+
}));
|
|
161
|
+
item.byline = explicit[0]?.byline ?? null;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (item.primaryBylineId) item.primaryBylineId = null;
|
|
165
|
+
if (item.authorId) {
|
|
166
|
+
const fallback = authorBylineMap.get(item.authorId);
|
|
167
|
+
if (fallback) {
|
|
168
|
+
item.bylines = [{
|
|
169
|
+
byline: fallback,
|
|
170
|
+
sortOrder: 0,
|
|
171
|
+
roleLabel: null,
|
|
172
|
+
source: "inferred"
|
|
173
|
+
}];
|
|
174
|
+
item.byline = fallback;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
item.bylines = [];
|
|
179
|
+
item.byline = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Resolve an identifier (ID or slug) to a real content ID.
|
|
184
|
+
* Returns the ID if found, null if not found.
|
|
185
|
+
* When locale is provided, slug lookups are scoped to that locale.
|
|
186
|
+
*/
|
|
187
|
+
async function resolveId(repo, collection, identifier, locale) {
|
|
188
|
+
return (await repo.findByIdOrSlug(collection, identifier, locale))?.id ?? null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Resolve an identifier (ID or slug) to a real content ID,
|
|
192
|
+
* including trashed (soft-deleted) items.
|
|
193
|
+
*/
|
|
194
|
+
async function resolveIdIncludingTrashed(repo, collection, identifier, locale) {
|
|
195
|
+
return (await repo.findByIdOrSlugIncludingTrashed(collection, identifier, locale))?.id ?? null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create content list handler
|
|
199
|
+
*/
|
|
200
|
+
async function handleContentList(db, collection, params) {
|
|
201
|
+
try {
|
|
202
|
+
const repo = new ContentRepository(db);
|
|
203
|
+
const where = {};
|
|
204
|
+
if (params.status) where.status = params.status;
|
|
205
|
+
if (params.locale) where.locale = params.locale;
|
|
206
|
+
const result = await repo.findMany(collection, {
|
|
207
|
+
cursor: params.cursor,
|
|
208
|
+
limit: params.limit || 50,
|
|
209
|
+
where: Object.keys(where).length > 0 ? where : void 0,
|
|
210
|
+
orderBy: params.orderBy ? {
|
|
211
|
+
field: params.orderBy,
|
|
212
|
+
direction: params.order || "desc"
|
|
213
|
+
} : void 0
|
|
214
|
+
});
|
|
215
|
+
const hasSeo = await collectionHasSeo(db, collection);
|
|
216
|
+
await hydrateSeoMany(db, collection, result.items, hasSeo);
|
|
217
|
+
await hydrateBylinesMany(db, collection, result.items);
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
data: {
|
|
221
|
+
items: result.items,
|
|
222
|
+
nextCursor: result.nextCursor
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error("Content list error:", error);
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
error: {
|
|
230
|
+
code: "CONTENT_LIST_ERROR",
|
|
231
|
+
message: "Failed to list content"
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get single content item
|
|
238
|
+
*/
|
|
239
|
+
async function handleContentGet(db, collection, id, locale) {
|
|
240
|
+
try {
|
|
241
|
+
const item = await new ContentRepository(db).findByIdOrSlug(collection, id, locale);
|
|
242
|
+
if (!item) return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: {
|
|
245
|
+
code: "NOT_FOUND",
|
|
246
|
+
message: `Content item not found: ${id}`
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
250
|
+
await hydrateBylines(db, collection, item);
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
data: {
|
|
254
|
+
item,
|
|
255
|
+
_rev: encodeRev(item)
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error("Content get error:", error);
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: {
|
|
263
|
+
code: "CONTENT_GET_ERROR",
|
|
264
|
+
message: "Failed to get content"
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get a content item by id, including trashed items.
|
|
271
|
+
* Used by restore endpoint for ownership checks on soft-deleted items.
|
|
272
|
+
*/
|
|
273
|
+
async function handleContentGetIncludingTrashed(db, collection, id, locale) {
|
|
274
|
+
try {
|
|
275
|
+
const item = await new ContentRepository(db).findByIdOrSlugIncludingTrashed(collection, id, locale);
|
|
276
|
+
if (!item) return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: {
|
|
279
|
+
code: "NOT_FOUND",
|
|
280
|
+
message: `Content item not found: ${id}`
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
284
|
+
await hydrateBylines(db, collection, item);
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
data: {
|
|
288
|
+
item,
|
|
289
|
+
_rev: encodeRev(item)
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error("Content get error:", error);
|
|
294
|
+
return {
|
|
295
|
+
success: false,
|
|
296
|
+
error: {
|
|
297
|
+
code: "CONTENT_GET_ERROR",
|
|
298
|
+
message: "Failed to get content"
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Create content item.
|
|
305
|
+
*
|
|
306
|
+
* Content + SEO writes are wrapped in a transaction so either both succeed
|
|
307
|
+
* or neither does. If `body.seo` is provided for a non-SEO collection, the
|
|
308
|
+
* API returns a validation error rather than silently dropping it.
|
|
309
|
+
*/
|
|
310
|
+
async function handleContentCreate(db, collection, body) {
|
|
311
|
+
try {
|
|
312
|
+
const hasSeo = await collectionHasSeo(db, collection);
|
|
313
|
+
if (body.seo && !hasSeo) return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: {
|
|
316
|
+
code: "VALIDATION_ERROR",
|
|
317
|
+
message: `Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const item = await withTransaction(db, async (trx) => {
|
|
321
|
+
const repo = new ContentRepository(trx);
|
|
322
|
+
const bylineRepo = new BylineRepository(trx);
|
|
323
|
+
let slug = body.slug;
|
|
324
|
+
if (!slug) {
|
|
325
|
+
const slugSource = getSlugSource(body.data);
|
|
326
|
+
if (slugSource) slug = await repo.generateUniqueSlug(collection, slugSource, body.locale);
|
|
327
|
+
}
|
|
328
|
+
const created = await repo.create({
|
|
329
|
+
type: collection,
|
|
330
|
+
slug,
|
|
331
|
+
data: body.data,
|
|
332
|
+
status: body.status || "draft",
|
|
333
|
+
authorId: body.authorId,
|
|
334
|
+
locale: body.locale,
|
|
335
|
+
translationOf: body.translationOf,
|
|
336
|
+
createdAt: body.createdAt,
|
|
337
|
+
publishedAt: body.publishedAt
|
|
338
|
+
});
|
|
339
|
+
if (body.bylines !== void 0) {
|
|
340
|
+
await bylineRepo.setContentBylines(collection, created.id, body.bylines);
|
|
341
|
+
created.primaryBylineId = body.bylines[0]?.bylineId ?? null;
|
|
342
|
+
}
|
|
343
|
+
await hydrateBylines(trx, collection, created);
|
|
344
|
+
if (body.seo && hasSeo) created.seo = await new SeoRepository(trx).upsert(collection, created.id, body.seo);
|
|
345
|
+
else if (hasSeo) created.seo = { ...SEO_DEFAULTS };
|
|
346
|
+
return created;
|
|
347
|
+
});
|
|
348
|
+
return {
|
|
349
|
+
success: true,
|
|
350
|
+
data: {
|
|
351
|
+
item,
|
|
352
|
+
_rev: encodeRev(item)
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error("Content create error:", error);
|
|
357
|
+
return {
|
|
358
|
+
success: false,
|
|
359
|
+
error: {
|
|
360
|
+
code: "CONTENT_CREATE_ERROR",
|
|
361
|
+
message: "Failed to create content"
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Update content item.
|
|
368
|
+
* If `_rev` is provided, validates it against the current version before writing.
|
|
369
|
+
* No `_rev` = blind write (backwards-compatible for admin UI).
|
|
370
|
+
*
|
|
371
|
+
* Content + SEO writes are wrapped in a transaction for atomicity.
|
|
372
|
+
*/
|
|
373
|
+
async function handleContentUpdate(db, collection, id, body) {
|
|
374
|
+
try {
|
|
375
|
+
const hasSeo = await collectionHasSeo(db, collection);
|
|
376
|
+
if (body.seo && !hasSeo) return {
|
|
377
|
+
success: false,
|
|
378
|
+
error: {
|
|
379
|
+
code: "VALIDATION_ERROR",
|
|
380
|
+
message: `Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const resolvedId = await resolveId(new ContentRepository(db), collection, id) ?? id;
|
|
384
|
+
const item = await withTransaction(db, async (trx) => {
|
|
385
|
+
const trxRepo = new ContentRepository(trx);
|
|
386
|
+
const bylineRepo = new BylineRepository(trx);
|
|
387
|
+
const existing = body._rev || body.slug ? await trxRepo.findById(collection, resolvedId) : null;
|
|
388
|
+
if (body._rev) {
|
|
389
|
+
if (!existing) throw new ContentUpdateApiError("NOT_FOUND", `Content item not found: ${id}`);
|
|
390
|
+
const revCheck = validateRev(body._rev, existing);
|
|
391
|
+
if (!revCheck.valid) throw new ContentUpdateApiError("CONFLICT", revCheck.message);
|
|
392
|
+
}
|
|
393
|
+
let oldSlug;
|
|
394
|
+
if (body.slug && existing?.slug && existing.slug !== body.slug) oldSlug = existing.slug;
|
|
395
|
+
const updated = await trxRepo.update(collection, resolvedId, {
|
|
396
|
+
data: body.data,
|
|
397
|
+
slug: body.slug,
|
|
398
|
+
status: body.status,
|
|
399
|
+
authorId: body.authorId,
|
|
400
|
+
publishedAt: body.publishedAt
|
|
401
|
+
});
|
|
402
|
+
if (body.bylines !== void 0) {
|
|
403
|
+
await bylineRepo.setContentBylines(collection, resolvedId, body.bylines);
|
|
404
|
+
updated.primaryBylineId = body.bylines[0]?.bylineId ?? null;
|
|
405
|
+
}
|
|
406
|
+
if (oldSlug && body.slug) {
|
|
407
|
+
const collectionRow = await trx.selectFrom("_dineway_collections").select("url_pattern").where("slug", "=", collection).executeTakeFirst();
|
|
408
|
+
await new RedirectRepository(trx).createAutoRedirect(collection, oldSlug, body.slug, resolvedId, collectionRow?.url_pattern ?? null);
|
|
409
|
+
invalidateRedirectCache();
|
|
410
|
+
}
|
|
411
|
+
if (isI18nEnabled() && body.data && updated.translationGroup) await syncNonTranslatableFields(trx, collection, updated.id, updated.translationGroup, body.data);
|
|
412
|
+
if (body.seo && hasSeo) updated.seo = await new SeoRepository(trx).upsert(collection, resolvedId, body.seo);
|
|
413
|
+
else if (hasSeo) updated.seo = await new SeoRepository(trx).get(collection, resolvedId);
|
|
414
|
+
await hydrateBylines(trx, collection, updated);
|
|
415
|
+
return updated;
|
|
416
|
+
});
|
|
417
|
+
return {
|
|
418
|
+
success: true,
|
|
419
|
+
data: {
|
|
420
|
+
item,
|
|
421
|
+
_rev: encodeRev(item)
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error instanceof ContentUpdateApiError) return {
|
|
426
|
+
success: false,
|
|
427
|
+
error: {
|
|
428
|
+
code: error.code,
|
|
429
|
+
message: error.message
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
console.error("Content update error:", error);
|
|
433
|
+
return {
|
|
434
|
+
success: false,
|
|
435
|
+
error: {
|
|
436
|
+
code: "CONTENT_UPDATE_ERROR",
|
|
437
|
+
message: "Failed to update content"
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Duplicate content item.
|
|
444
|
+
*
|
|
445
|
+
* Only copies SEO data if the collection has SEO enabled.
|
|
446
|
+
* Always returns consistent `seo` shape for SEO-enabled collections.
|
|
447
|
+
*/
|
|
448
|
+
async function handleContentDuplicate(db, collection, id, authorId) {
|
|
449
|
+
try {
|
|
450
|
+
const hasSeo = await collectionHasSeo(db, collection);
|
|
451
|
+
return {
|
|
452
|
+
success: true,
|
|
453
|
+
data: { item: await withTransaction(db, async (trx) => {
|
|
454
|
+
const repo = new ContentRepository(trx);
|
|
455
|
+
const bylineRepo = new BylineRepository(trx);
|
|
456
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
457
|
+
const dup = await repo.duplicate(collection, resolvedId, authorId);
|
|
458
|
+
const existingBylines = await bylineRepo.getContentBylines(collection, resolvedId);
|
|
459
|
+
if (existingBylines.length > 0) await bylineRepo.setContentBylines(collection, dup.id, existingBylines.map((entry) => ({
|
|
460
|
+
bylineId: entry.byline.id,
|
|
461
|
+
roleLabel: entry.roleLabel
|
|
462
|
+
})));
|
|
463
|
+
if (hasSeo) {
|
|
464
|
+
const seoRepo = new SeoRepository(trx);
|
|
465
|
+
await seoRepo.copyForDuplicate(collection, resolvedId, dup.id);
|
|
466
|
+
dup.seo = await seoRepo.get(collection, dup.id);
|
|
467
|
+
}
|
|
468
|
+
await hydrateBylines(trx, collection, dup);
|
|
469
|
+
return dup;
|
|
470
|
+
}) }
|
|
471
|
+
};
|
|
472
|
+
} catch (err) {
|
|
473
|
+
if (err instanceof DinewayValidationError) return {
|
|
474
|
+
success: false,
|
|
475
|
+
error: {
|
|
476
|
+
code: "NOT_FOUND",
|
|
477
|
+
message: err.message
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
console.error("Content duplicate error:", err);
|
|
481
|
+
return {
|
|
482
|
+
success: false,
|
|
483
|
+
error: {
|
|
484
|
+
code: "CONTENT_DUPLICATE_ERROR",
|
|
485
|
+
message: "Failed to duplicate content"
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Delete content item (soft delete - moves to trash)
|
|
492
|
+
*/
|
|
493
|
+
async function handleContentDelete(db, collection, id) {
|
|
494
|
+
try {
|
|
495
|
+
if (!await withTransaction(db, async (trx) => {
|
|
496
|
+
const repo = new ContentRepository(trx);
|
|
497
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
498
|
+
return repo.delete(collection, resolvedId);
|
|
499
|
+
})) return {
|
|
500
|
+
success: false,
|
|
501
|
+
error: {
|
|
502
|
+
code: "NOT_FOUND",
|
|
503
|
+
message: `Content item not found: ${id}`
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
data: { deleted: true }
|
|
509
|
+
};
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.error("Content delete error:", error);
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: {
|
|
515
|
+
code: "CONTENT_DELETE_ERROR",
|
|
516
|
+
message: "Failed to delete content"
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Restore content item from trash
|
|
523
|
+
*/
|
|
524
|
+
async function handleContentRestore(db, collection, id) {
|
|
525
|
+
try {
|
|
526
|
+
if (!await withTransaction(db, async (trx) => {
|
|
527
|
+
const repo = new ContentRepository(trx);
|
|
528
|
+
const resolvedId = await resolveIdIncludingTrashed(repo, collection, id) ?? id;
|
|
529
|
+
return repo.restore(collection, resolvedId);
|
|
530
|
+
})) return {
|
|
531
|
+
success: false,
|
|
532
|
+
error: {
|
|
533
|
+
code: "NOT_FOUND",
|
|
534
|
+
message: `Trashed content item not found: ${id}`
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
return {
|
|
538
|
+
success: true,
|
|
539
|
+
data: { restored: true }
|
|
540
|
+
};
|
|
541
|
+
} catch (error) {
|
|
542
|
+
console.error("Content restore error:", error);
|
|
543
|
+
return {
|
|
544
|
+
success: false,
|
|
545
|
+
error: {
|
|
546
|
+
code: "CONTENT_RESTORE_ERROR",
|
|
547
|
+
message: "Failed to restore content"
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Permanently delete content item (cannot be undone).
|
|
554
|
+
* Also cleans up associated SEO data.
|
|
555
|
+
*/
|
|
556
|
+
async function handleContentPermanentDelete(db, collection, id) {
|
|
557
|
+
try {
|
|
558
|
+
const resolvedId = await resolveIdIncludingTrashed(new ContentRepository(db), collection, id) ?? id;
|
|
559
|
+
if (!await withTransaction(db, async (trx) => {
|
|
560
|
+
const wasDeleted = await new ContentRepository(trx).permanentDelete(collection, resolvedId);
|
|
561
|
+
if (wasDeleted) {
|
|
562
|
+
await new SeoRepository(trx).delete(collection, resolvedId);
|
|
563
|
+
await new CommentRepository(trx).deleteByContent(collection, resolvedId);
|
|
564
|
+
await new RevisionRepository(trx).deleteByEntry(collection, resolvedId);
|
|
565
|
+
}
|
|
566
|
+
return wasDeleted;
|
|
567
|
+
})) return {
|
|
568
|
+
success: false,
|
|
569
|
+
error: {
|
|
570
|
+
code: "NOT_FOUND",
|
|
571
|
+
message: `Content item not found: ${id}`
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
return {
|
|
575
|
+
success: true,
|
|
576
|
+
data: { deleted: true }
|
|
577
|
+
};
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.error("Content permanent delete error:", error);
|
|
580
|
+
return {
|
|
581
|
+
success: false,
|
|
582
|
+
error: {
|
|
583
|
+
code: "CONTENT_DELETE_ERROR",
|
|
584
|
+
message: "Failed to permanently delete content"
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* List trashed content items
|
|
591
|
+
*/
|
|
592
|
+
async function handleContentListTrashed(db, collection, options = {}) {
|
|
593
|
+
try {
|
|
594
|
+
const result = await new ContentRepository(db).findTrashed(collection, {
|
|
595
|
+
limit: options.limit,
|
|
596
|
+
cursor: options.cursor
|
|
597
|
+
});
|
|
598
|
+
return {
|
|
599
|
+
success: true,
|
|
600
|
+
data: {
|
|
601
|
+
items: result.items.map((item) => ({
|
|
602
|
+
id: item.id,
|
|
603
|
+
type: item.type,
|
|
604
|
+
slug: item.slug,
|
|
605
|
+
status: item.status,
|
|
606
|
+
data: item.data,
|
|
607
|
+
authorId: item.authorId,
|
|
608
|
+
createdAt: item.createdAt,
|
|
609
|
+
updatedAt: item.updatedAt,
|
|
610
|
+
publishedAt: item.publishedAt,
|
|
611
|
+
deletedAt: item.deletedAt
|
|
612
|
+
})),
|
|
613
|
+
nextCursor: result.nextCursor
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
} catch (error) {
|
|
617
|
+
console.error("Content list trashed error:", error);
|
|
618
|
+
return {
|
|
619
|
+
success: false,
|
|
620
|
+
error: {
|
|
621
|
+
code: "CONTENT_LIST_ERROR",
|
|
622
|
+
message: "Failed to list trashed content"
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Count trashed content items
|
|
629
|
+
*/
|
|
630
|
+
async function handleContentCountTrashed(db, collection) {
|
|
631
|
+
try {
|
|
632
|
+
return {
|
|
633
|
+
success: true,
|
|
634
|
+
data: { count: await new ContentRepository(db).countTrashed(collection) }
|
|
635
|
+
};
|
|
636
|
+
} catch (error) {
|
|
637
|
+
console.error("Content count trashed error:", error);
|
|
638
|
+
return {
|
|
639
|
+
success: false,
|
|
640
|
+
error: {
|
|
641
|
+
code: "CONTENT_COUNT_ERROR",
|
|
642
|
+
message: "Failed to count trashed content"
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Schedule content for future publishing
|
|
649
|
+
*/
|
|
650
|
+
async function handleContentSchedule(db, collection, id, scheduledAt) {
|
|
651
|
+
try {
|
|
652
|
+
const item = await withTransaction(db, async (trx) => {
|
|
653
|
+
const repo = new ContentRepository(trx);
|
|
654
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
655
|
+
return repo.schedule(collection, resolvedId, scheduledAt);
|
|
656
|
+
});
|
|
657
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
658
|
+
return {
|
|
659
|
+
success: true,
|
|
660
|
+
data: { item }
|
|
661
|
+
};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (error instanceof DinewayValidationError) return {
|
|
664
|
+
success: false,
|
|
665
|
+
error: {
|
|
666
|
+
code: "VALIDATION_ERROR",
|
|
667
|
+
message: error.message
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
console.error("Content schedule error:", error);
|
|
671
|
+
return {
|
|
672
|
+
success: false,
|
|
673
|
+
error: {
|
|
674
|
+
code: "CONTENT_SCHEDULE_ERROR",
|
|
675
|
+
message: "Failed to schedule content"
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Unschedule content (revert to draft)
|
|
682
|
+
*/
|
|
683
|
+
async function handleContentUnschedule(db, collection, id) {
|
|
684
|
+
try {
|
|
685
|
+
const item = await withTransaction(db, async (trx) => {
|
|
686
|
+
const repo = new ContentRepository(trx);
|
|
687
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
688
|
+
return repo.unschedule(collection, resolvedId);
|
|
689
|
+
});
|
|
690
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
691
|
+
return {
|
|
692
|
+
success: true,
|
|
693
|
+
data: { item }
|
|
694
|
+
};
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (error instanceof DinewayValidationError) return {
|
|
697
|
+
success: false,
|
|
698
|
+
error: {
|
|
699
|
+
code: "VALIDATION_ERROR",
|
|
700
|
+
message: error.message
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
console.error("Content unschedule error:", error);
|
|
704
|
+
return {
|
|
705
|
+
success: false,
|
|
706
|
+
error: {
|
|
707
|
+
code: "CONTENT_UNSCHEDULE_ERROR",
|
|
708
|
+
message: "Failed to unschedule content"
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Publish content immediately.
|
|
715
|
+
*
|
|
716
|
+
* Wrapped in a transaction because publish performs multiple writes
|
|
717
|
+
* (syncDataColumns, slug sync, status/revision update) that must
|
|
718
|
+
* be atomic to prevent FTS shadow table corruption on crash.
|
|
719
|
+
*/
|
|
720
|
+
async function handleContentPublish(db, collection, id, options = {}) {
|
|
721
|
+
try {
|
|
722
|
+
const item = await withTransaction(db, async (trx) => {
|
|
723
|
+
const repo = new ContentRepository(trx);
|
|
724
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
725
|
+
return repo.publish(collection, resolvedId, options.publishedAt);
|
|
726
|
+
});
|
|
727
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
728
|
+
return {
|
|
729
|
+
success: true,
|
|
730
|
+
data: { item }
|
|
731
|
+
};
|
|
732
|
+
} catch (error) {
|
|
733
|
+
if (error instanceof DinewayValidationError) return {
|
|
734
|
+
success: false,
|
|
735
|
+
error: {
|
|
736
|
+
code: "VALIDATION_ERROR",
|
|
737
|
+
message: error.message
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
console.error("Content publish error:", error);
|
|
741
|
+
return {
|
|
742
|
+
success: false,
|
|
743
|
+
error: {
|
|
744
|
+
code: "CONTENT_PUBLISH_ERROR",
|
|
745
|
+
message: "Failed to publish content"
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Unpublish content (revert to draft).
|
|
752
|
+
*
|
|
753
|
+
* Wrapped in a transaction — unpublish may create a draft revision
|
|
754
|
+
* from the live version then update the status, which is multi-step.
|
|
755
|
+
*/
|
|
756
|
+
async function handleContentUnpublish(db, collection, id) {
|
|
757
|
+
try {
|
|
758
|
+
const item = await withTransaction(db, async (trx) => {
|
|
759
|
+
const repo = new ContentRepository(trx);
|
|
760
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
761
|
+
return repo.unpublish(collection, resolvedId);
|
|
762
|
+
});
|
|
763
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
764
|
+
return {
|
|
765
|
+
success: true,
|
|
766
|
+
data: { item }
|
|
767
|
+
};
|
|
768
|
+
} catch (error) {
|
|
769
|
+
if (error instanceof DinewayValidationError) return {
|
|
770
|
+
success: false,
|
|
771
|
+
error: {
|
|
772
|
+
code: "VALIDATION_ERROR",
|
|
773
|
+
message: error.message
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
console.error("Content unpublish error:", error);
|
|
777
|
+
return {
|
|
778
|
+
success: false,
|
|
779
|
+
error: {
|
|
780
|
+
code: "CONTENT_UNPUBLISH_ERROR",
|
|
781
|
+
message: "Failed to unpublish content"
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Count scheduled content items
|
|
788
|
+
*/
|
|
789
|
+
async function handleContentCountScheduled(db, collection) {
|
|
790
|
+
try {
|
|
791
|
+
return {
|
|
792
|
+
success: true,
|
|
793
|
+
data: { count: await new ContentRepository(db).countScheduled(collection) }
|
|
794
|
+
};
|
|
795
|
+
} catch (error) {
|
|
796
|
+
console.error("Content count scheduled error:", error);
|
|
797
|
+
return {
|
|
798
|
+
success: false,
|
|
799
|
+
error: {
|
|
800
|
+
code: "CONTENT_COUNT_ERROR",
|
|
801
|
+
message: "Failed to count scheduled content"
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Discard draft changes (revert to live version)
|
|
808
|
+
*/
|
|
809
|
+
async function handleContentDiscardDraft(db, collection, id) {
|
|
810
|
+
try {
|
|
811
|
+
const item = await withTransaction(db, async (trx) => {
|
|
812
|
+
const repo = new ContentRepository(trx);
|
|
813
|
+
const resolvedId = await resolveId(repo, collection, id) ?? id;
|
|
814
|
+
return repo.discardDraft(collection, resolvedId);
|
|
815
|
+
});
|
|
816
|
+
await hydrateSeo(db, collection, item, await collectionHasSeo(db, collection));
|
|
817
|
+
return {
|
|
818
|
+
success: true,
|
|
819
|
+
data: { item }
|
|
820
|
+
};
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (error instanceof DinewayValidationError) return {
|
|
823
|
+
success: false,
|
|
824
|
+
error: {
|
|
825
|
+
code: "NOT_FOUND",
|
|
826
|
+
message: error.message
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
console.error("Content discard draft error:", error);
|
|
830
|
+
return {
|
|
831
|
+
success: false,
|
|
832
|
+
error: {
|
|
833
|
+
code: "CONTENT_DISCARD_DRAFT_ERROR",
|
|
834
|
+
message: "Failed to discard draft"
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Compare live and draft revisions
|
|
841
|
+
*/
|
|
842
|
+
async function handleContentCompare(db, collection, id) {
|
|
843
|
+
try {
|
|
844
|
+
const entry = await new ContentRepository(db).findByIdOrSlug(collection, id);
|
|
845
|
+
if (!entry) return {
|
|
846
|
+
success: false,
|
|
847
|
+
error: {
|
|
848
|
+
code: "NOT_FOUND",
|
|
849
|
+
message: `Content item not found: ${id}`
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
const revisionRepo = new RevisionRepository(db);
|
|
853
|
+
const live = entry.liveRevisionId ? await revisionRepo.findById(entry.liveRevisionId) : null;
|
|
854
|
+
const draft = entry.draftRevisionId ? await revisionRepo.findById(entry.draftRevisionId) : null;
|
|
855
|
+
return {
|
|
856
|
+
success: true,
|
|
857
|
+
data: {
|
|
858
|
+
hasChanges: entry.draftRevisionId !== null && entry.draftRevisionId !== entry.liveRevisionId,
|
|
859
|
+
live: live?.data ?? null,
|
|
860
|
+
draft: draft?.data ?? null
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
} catch (error) {
|
|
864
|
+
console.error("Content compare error:", error);
|
|
865
|
+
return {
|
|
866
|
+
success: false,
|
|
867
|
+
error: {
|
|
868
|
+
code: "CONTENT_COMPARE_ERROR",
|
|
869
|
+
message: "Failed to compare revisions"
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Get all translations for a content item.
|
|
876
|
+
* Returns the item's translation group members with locale and status info.
|
|
877
|
+
*/
|
|
878
|
+
async function handleContentTranslations(db, collection, id) {
|
|
879
|
+
try {
|
|
880
|
+
const repo = new ContentRepository(db);
|
|
881
|
+
const item = await repo.findByIdOrSlug(collection, id);
|
|
882
|
+
if (!item) return {
|
|
883
|
+
success: false,
|
|
884
|
+
error: {
|
|
885
|
+
code: "NOT_FOUND",
|
|
886
|
+
message: `Content item not found: ${id}`
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
if (!item.translationGroup) return {
|
|
890
|
+
success: true,
|
|
891
|
+
data: {
|
|
892
|
+
translationGroup: item.id,
|
|
893
|
+
translations: [{
|
|
894
|
+
id: item.id,
|
|
895
|
+
locale: item.locale,
|
|
896
|
+
slug: item.slug,
|
|
897
|
+
status: item.status,
|
|
898
|
+
updatedAt: item.updatedAt
|
|
899
|
+
}]
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
const translations = await repo.findTranslations(collection, item.translationGroup);
|
|
903
|
+
return {
|
|
904
|
+
success: true,
|
|
905
|
+
data: {
|
|
906
|
+
translationGroup: item.translationGroup,
|
|
907
|
+
translations: translations.map((t) => ({
|
|
908
|
+
id: t.id,
|
|
909
|
+
locale: t.locale,
|
|
910
|
+
slug: t.slug,
|
|
911
|
+
status: t.status,
|
|
912
|
+
updatedAt: t.updatedAt
|
|
913
|
+
}))
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
} catch (error) {
|
|
917
|
+
if (error instanceof Error) console.error("Content translations error:", error);
|
|
918
|
+
return {
|
|
919
|
+
success: false,
|
|
920
|
+
error: {
|
|
921
|
+
code: "CONTENT_TRANSLATIONS_ERROR",
|
|
922
|
+
message: "Failed to get translations"
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Sync non-translatable fields to sibling locales.
|
|
929
|
+
*
|
|
930
|
+
* When a content item is updated and it belongs to a translation group,
|
|
931
|
+
* any non-translatable fields in the update data are written to all other
|
|
932
|
+
* rows in the same translation group within the same transaction.
|
|
933
|
+
*
|
|
934
|
+
* Non-translatable fields are **copied, not linked** — each row owns its
|
|
935
|
+
* own data. This keeps queries simple and avoids cross-row joins.
|
|
936
|
+
*/
|
|
937
|
+
async function syncNonTranslatableFields(trx, collectionSlug, updatedItemId, translationGroup, data) {
|
|
938
|
+
const collection = await trx.selectFrom("_dineway_collections").select("id").where("slug", "=", collectionSlug).executeTakeFirst();
|
|
939
|
+
if (!collection) return;
|
|
940
|
+
const nonTranslatableSlugs = (await trx.selectFrom("_dineway_fields").select("slug").where("collection_id", "=", collection.id).where("translatable", "=", 0).execute()).map((f) => f.slug);
|
|
941
|
+
if (nonTranslatableSlugs.length === 0) return;
|
|
942
|
+
const syncData = {};
|
|
943
|
+
for (const slug of nonTranslatableSlugs) if (slug in data) syncData[slug] = data[slug];
|
|
944
|
+
if (Object.keys(syncData).length === 0) return;
|
|
945
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
946
|
+
const tableName = `ec_${collectionSlug}`;
|
|
947
|
+
const setClauses = Object.entries(syncData).map(([key, value]) => {
|
|
948
|
+
validateIdentifier(key, "field slug");
|
|
949
|
+
const serialized = typeof value === "object" && value !== null ? JSON.stringify(value) : value;
|
|
950
|
+
return sql`${sql.ref(key)} = ${serialized}`;
|
|
951
|
+
});
|
|
952
|
+
await sql`
|
|
953
|
+
UPDATE ${sql.ref(tableName)}
|
|
954
|
+
SET ${sql.join(setClauses, sql`, `)}
|
|
955
|
+
WHERE translation_group = ${translationGroup}
|
|
956
|
+
AND id != ${updatedItemId}
|
|
957
|
+
`.execute(trx);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/api/handlers/manifest.ts
|
|
962
|
+
/**
|
|
963
|
+
* Manifest generation handlers
|
|
964
|
+
*/
|
|
965
|
+
/** Pattern to add spaces before capital letters */
|
|
966
|
+
const CAMEL_CASE_PATTERN = /([A-Z])/g;
|
|
967
|
+
const FIRST_CHAR_PATTERN = /^./;
|
|
968
|
+
/**
|
|
969
|
+
* Generate admin manifest from collections
|
|
970
|
+
*/
|
|
971
|
+
async function generateManifest(collections, plugins = {}) {
|
|
972
|
+
const manifestCollections = {};
|
|
973
|
+
for (const [name, definition] of Object.entries(collections)) {
|
|
974
|
+
const fields = extractFieldDescriptors(definition.schema);
|
|
975
|
+
manifestCollections[name] = {
|
|
976
|
+
label: definition.admin.label,
|
|
977
|
+
labelSingular: definition.admin.labelSingular || definition.admin.label,
|
|
978
|
+
supports: definition.admin.supports || [],
|
|
979
|
+
fields
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
version: VERSION,
|
|
984
|
+
commit: COMMIT,
|
|
985
|
+
hash: await hashString(JSON.stringify(manifestCollections)),
|
|
986
|
+
collections: manifestCollections,
|
|
987
|
+
plugins
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Extract field descriptors from Zod schema
|
|
992
|
+
* Note: This is a simplified implementation that handles common types
|
|
993
|
+
*/
|
|
994
|
+
function extractFieldDescriptors(schema) {
|
|
995
|
+
const fields = {};
|
|
996
|
+
const shape = typeof schema._def?.shape === "function" ? schema._def.shape() : schema.shape || {};
|
|
997
|
+
for (const [name, fieldSchema] of Object.entries(shape)) fields[name] = extractFieldType(name, fieldSchema);
|
|
998
|
+
return fields;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Extract field type from Zod schema
|
|
1002
|
+
*/
|
|
1003
|
+
/** Type guard: check if a value is a non-null object */
|
|
1004
|
+
function isObject(value) {
|
|
1005
|
+
return typeof value === "object" && value !== null;
|
|
1006
|
+
}
|
|
1007
|
+
function extractFieldType(name, schema) {
|
|
1008
|
+
if (!isObject(schema)) return {
|
|
1009
|
+
kind: "string",
|
|
1010
|
+
label: formatLabel(name)
|
|
1011
|
+
};
|
|
1012
|
+
if (schema.isPortableText) return {
|
|
1013
|
+
kind: "portableText",
|
|
1014
|
+
label: formatLabel(name)
|
|
1015
|
+
};
|
|
1016
|
+
if (schema.isImage) return {
|
|
1017
|
+
kind: "image",
|
|
1018
|
+
label: formatLabel(name)
|
|
1019
|
+
};
|
|
1020
|
+
if (schema.isReference) return {
|
|
1021
|
+
kind: "reference",
|
|
1022
|
+
label: formatLabel(name)
|
|
1023
|
+
};
|
|
1024
|
+
const def = isObject(schema._def) ? schema._def : void 0;
|
|
1025
|
+
switch (typeof def?.typeName === "string" ? def.typeName : void 0) {
|
|
1026
|
+
case "ZodString": return {
|
|
1027
|
+
kind: "string",
|
|
1028
|
+
label: formatLabel(name)
|
|
1029
|
+
};
|
|
1030
|
+
case "ZodNumber": return {
|
|
1031
|
+
kind: "number",
|
|
1032
|
+
label: formatLabel(name)
|
|
1033
|
+
};
|
|
1034
|
+
case "ZodBoolean": return {
|
|
1035
|
+
kind: "boolean",
|
|
1036
|
+
label: formatLabel(name)
|
|
1037
|
+
};
|
|
1038
|
+
case "ZodDate": return {
|
|
1039
|
+
kind: "datetime",
|
|
1040
|
+
label: formatLabel(name)
|
|
1041
|
+
};
|
|
1042
|
+
case "ZodEnum": {
|
|
1043
|
+
const values = Array.isArray(def?.values) ? def.values : [];
|
|
1044
|
+
return {
|
|
1045
|
+
kind: "select",
|
|
1046
|
+
label: formatLabel(name),
|
|
1047
|
+
options: values.filter((v) => typeof v === "string").map((v) => ({
|
|
1048
|
+
value: v,
|
|
1049
|
+
label: v.charAt(0).toUpperCase() + v.slice(1)
|
|
1050
|
+
}))
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
case "ZodArray": return {
|
|
1054
|
+
kind: "array",
|
|
1055
|
+
label: formatLabel(name)
|
|
1056
|
+
};
|
|
1057
|
+
case "ZodObject": return {
|
|
1058
|
+
kind: "object",
|
|
1059
|
+
label: formatLabel(name)
|
|
1060
|
+
};
|
|
1061
|
+
case "ZodOptional":
|
|
1062
|
+
case "ZodDefault":
|
|
1063
|
+
if (def?.innerType) return extractFieldType(name, def.innerType);
|
|
1064
|
+
return {
|
|
1065
|
+
kind: "string",
|
|
1066
|
+
label: formatLabel(name)
|
|
1067
|
+
};
|
|
1068
|
+
default: return {
|
|
1069
|
+
kind: "string",
|
|
1070
|
+
label: formatLabel(name)
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Format field name as label
|
|
1076
|
+
*/
|
|
1077
|
+
function formatLabel(name) {
|
|
1078
|
+
return name.replace(CAMEL_CASE_PATTERN, " $1").replace(FIRST_CHAR_PATTERN, (str) => str.toUpperCase()).trim();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
//#endregion
|
|
1082
|
+
//#region src/api/handlers/revision.ts
|
|
1083
|
+
/**
|
|
1084
|
+
* List revisions for a content entry
|
|
1085
|
+
*/
|
|
1086
|
+
async function handleRevisionList(db, collection, entryId, params = {}) {
|
|
1087
|
+
try {
|
|
1088
|
+
const repo = new RevisionRepository(db);
|
|
1089
|
+
const [items, total] = await Promise.all([repo.findByEntry(collection, entryId, { limit: Math.min(params.limit || 50, 100) }), repo.countByEntry(collection, entryId)]);
|
|
1090
|
+
return {
|
|
1091
|
+
success: true,
|
|
1092
|
+
data: {
|
|
1093
|
+
items,
|
|
1094
|
+
total
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
} catch {
|
|
1098
|
+
return {
|
|
1099
|
+
success: false,
|
|
1100
|
+
error: {
|
|
1101
|
+
code: "REVISION_LIST_ERROR",
|
|
1102
|
+
message: "Failed to list revisions"
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Get a specific revision
|
|
1109
|
+
*/
|
|
1110
|
+
async function handleRevisionGet(db, revisionId) {
|
|
1111
|
+
try {
|
|
1112
|
+
const item = await new RevisionRepository(db).findById(revisionId);
|
|
1113
|
+
if (!item) return {
|
|
1114
|
+
success: false,
|
|
1115
|
+
error: {
|
|
1116
|
+
code: "NOT_FOUND",
|
|
1117
|
+
message: `Revision not found: ${revisionId}`
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
return {
|
|
1121
|
+
success: true,
|
|
1122
|
+
data: { item }
|
|
1123
|
+
};
|
|
1124
|
+
} catch {
|
|
1125
|
+
return {
|
|
1126
|
+
success: false,
|
|
1127
|
+
error: {
|
|
1128
|
+
code: "REVISION_GET_ERROR",
|
|
1129
|
+
message: "Failed to get revision"
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Restore a revision (updates content to this revision's data and creates new revision)
|
|
1136
|
+
*/
|
|
1137
|
+
async function handleRevisionRestore(db, revisionId, callerUserId) {
|
|
1138
|
+
try {
|
|
1139
|
+
const revisionRepo = new RevisionRepository(db);
|
|
1140
|
+
const revision = await revisionRepo.findById(revisionId);
|
|
1141
|
+
if (!revision) return {
|
|
1142
|
+
success: false,
|
|
1143
|
+
error: {
|
|
1144
|
+
code: "NOT_FOUND",
|
|
1145
|
+
message: `Revision not found: ${revisionId}`
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
const { _slug, ...fieldData } = revision.data;
|
|
1149
|
+
const item = await withTransaction(db, async (trx) => {
|
|
1150
|
+
const contentRepo = new ContentRepository(trx);
|
|
1151
|
+
const txRevisionRepo = new RevisionRepository(trx);
|
|
1152
|
+
const restored = await contentRepo.update(revision.collection, revision.entryId, {
|
|
1153
|
+
data: fieldData,
|
|
1154
|
+
slug: typeof _slug === "string" ? _slug : void 0
|
|
1155
|
+
});
|
|
1156
|
+
await txRevisionRepo.create({
|
|
1157
|
+
collection: revision.collection,
|
|
1158
|
+
entryId: revision.entryId,
|
|
1159
|
+
data: revision.data,
|
|
1160
|
+
authorId: callerUserId
|
|
1161
|
+
});
|
|
1162
|
+
return restored;
|
|
1163
|
+
});
|
|
1164
|
+
revisionRepo.pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});
|
|
1165
|
+
return {
|
|
1166
|
+
success: true,
|
|
1167
|
+
data: { item }
|
|
1168
|
+
};
|
|
1169
|
+
} catch {
|
|
1170
|
+
return {
|
|
1171
|
+
success: false,
|
|
1172
|
+
error: {
|
|
1173
|
+
code: "REVISION_RESTORE_ERROR",
|
|
1174
|
+
message: "Failed to restore revision"
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region src/api/handlers/media.ts
|
|
1182
|
+
/**
|
|
1183
|
+
* List media items
|
|
1184
|
+
*/
|
|
1185
|
+
async function handleMediaList(db, params) {
|
|
1186
|
+
try {
|
|
1187
|
+
const result = await new MediaRepository(db).findMany({
|
|
1188
|
+
cursor: params.cursor,
|
|
1189
|
+
limit: Math.min(params.limit || 50, 100),
|
|
1190
|
+
mimeType: params.mimeType
|
|
1191
|
+
});
|
|
1192
|
+
return {
|
|
1193
|
+
success: true,
|
|
1194
|
+
data: {
|
|
1195
|
+
items: result.items,
|
|
1196
|
+
nextCursor: result.nextCursor
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
} catch {
|
|
1200
|
+
return {
|
|
1201
|
+
success: false,
|
|
1202
|
+
error: {
|
|
1203
|
+
code: "MEDIA_LIST_ERROR",
|
|
1204
|
+
message: "Failed to list media"
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get single media item
|
|
1211
|
+
*/
|
|
1212
|
+
async function handleMediaGet(db, id) {
|
|
1213
|
+
try {
|
|
1214
|
+
const item = await new MediaRepository(db).findById(id);
|
|
1215
|
+
if (!item) return {
|
|
1216
|
+
success: false,
|
|
1217
|
+
error: {
|
|
1218
|
+
code: "NOT_FOUND",
|
|
1219
|
+
message: `Media item not found: ${id}`
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
return {
|
|
1223
|
+
success: true,
|
|
1224
|
+
data: { item }
|
|
1225
|
+
};
|
|
1226
|
+
} catch {
|
|
1227
|
+
return {
|
|
1228
|
+
success: false,
|
|
1229
|
+
error: {
|
|
1230
|
+
code: "MEDIA_GET_ERROR",
|
|
1231
|
+
message: "Failed to get media"
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Create media item (after file upload)
|
|
1238
|
+
*/
|
|
1239
|
+
async function handleMediaCreate(db, input) {
|
|
1240
|
+
try {
|
|
1241
|
+
return {
|
|
1242
|
+
success: true,
|
|
1243
|
+
data: { item: await new MediaRepository(db).create(input) }
|
|
1244
|
+
};
|
|
1245
|
+
} catch {
|
|
1246
|
+
return {
|
|
1247
|
+
success: false,
|
|
1248
|
+
error: {
|
|
1249
|
+
code: "MEDIA_CREATE_ERROR",
|
|
1250
|
+
message: "Failed to create media"
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Update media metadata
|
|
1257
|
+
*/
|
|
1258
|
+
async function handleMediaUpdate(db, id, input) {
|
|
1259
|
+
try {
|
|
1260
|
+
const item = await new MediaRepository(db).update(id, input);
|
|
1261
|
+
if (!item) return {
|
|
1262
|
+
success: false,
|
|
1263
|
+
error: {
|
|
1264
|
+
code: "NOT_FOUND",
|
|
1265
|
+
message: `Media item not found: ${id}`
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
return {
|
|
1269
|
+
success: true,
|
|
1270
|
+
data: { item }
|
|
1271
|
+
};
|
|
1272
|
+
} catch {
|
|
1273
|
+
return {
|
|
1274
|
+
success: false,
|
|
1275
|
+
error: {
|
|
1276
|
+
code: "MEDIA_UPDATE_ERROR",
|
|
1277
|
+
message: "Failed to update media"
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Delete media item
|
|
1284
|
+
*/
|
|
1285
|
+
async function handleMediaDelete(db, id) {
|
|
1286
|
+
try {
|
|
1287
|
+
if (!await new MediaRepository(db).delete(id)) return {
|
|
1288
|
+
success: false,
|
|
1289
|
+
error: {
|
|
1290
|
+
code: "NOT_FOUND",
|
|
1291
|
+
message: `Media item not found: ${id}`
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
return {
|
|
1295
|
+
success: true,
|
|
1296
|
+
data: { deleted: true }
|
|
1297
|
+
};
|
|
1298
|
+
} catch {
|
|
1299
|
+
return {
|
|
1300
|
+
success: false,
|
|
1301
|
+
error: {
|
|
1302
|
+
code: "MEDIA_DELETE_ERROR",
|
|
1303
|
+
message: "Failed to delete media"
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
//#endregion
|
|
1310
|
+
//#region src/api/handlers/schema.ts
|
|
1311
|
+
/**
|
|
1312
|
+
* List all collections
|
|
1313
|
+
*/
|
|
1314
|
+
async function handleSchemaCollectionList(db) {
|
|
1315
|
+
try {
|
|
1316
|
+
return {
|
|
1317
|
+
success: true,
|
|
1318
|
+
data: { items: await new SchemaRegistry(db).listCollections() }
|
|
1319
|
+
};
|
|
1320
|
+
} catch {
|
|
1321
|
+
return {
|
|
1322
|
+
success: false,
|
|
1323
|
+
error: {
|
|
1324
|
+
code: "SCHEMA_LIST_ERROR",
|
|
1325
|
+
message: "Failed to list collections"
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Get a collection by slug
|
|
1332
|
+
*/
|
|
1333
|
+
async function handleSchemaCollectionGet(db, slug, options) {
|
|
1334
|
+
try {
|
|
1335
|
+
const registry = new SchemaRegistry(db);
|
|
1336
|
+
if (options?.includeFields) {
|
|
1337
|
+
const item = await registry.getCollectionWithFields(slug);
|
|
1338
|
+
if (!item) return {
|
|
1339
|
+
success: false,
|
|
1340
|
+
error: {
|
|
1341
|
+
code: "NOT_FOUND",
|
|
1342
|
+
message: `Collection not found: ${slug}`
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
return {
|
|
1346
|
+
success: true,
|
|
1347
|
+
data: { item }
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const item = await registry.getCollection(slug);
|
|
1351
|
+
if (!item) return {
|
|
1352
|
+
success: false,
|
|
1353
|
+
error: {
|
|
1354
|
+
code: "NOT_FOUND",
|
|
1355
|
+
message: `Collection not found: ${slug}`
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
return {
|
|
1359
|
+
success: true,
|
|
1360
|
+
data: { item }
|
|
1361
|
+
};
|
|
1362
|
+
} catch {
|
|
1363
|
+
return {
|
|
1364
|
+
success: false,
|
|
1365
|
+
error: {
|
|
1366
|
+
code: "SCHEMA_GET_ERROR",
|
|
1367
|
+
message: "Failed to get collection"
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Create a collection
|
|
1374
|
+
*/
|
|
1375
|
+
async function handleSchemaCollectionCreate(db, input) {
|
|
1376
|
+
try {
|
|
1377
|
+
return {
|
|
1378
|
+
success: true,
|
|
1379
|
+
data: { item: await new SchemaRegistry(db).createCollection(input) }
|
|
1380
|
+
};
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
if (error instanceof SchemaError) return {
|
|
1383
|
+
success: false,
|
|
1384
|
+
error: {
|
|
1385
|
+
code: error.code,
|
|
1386
|
+
message: error.message,
|
|
1387
|
+
details: error.details
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
console.error("[dineway] Failed to create collection:", error);
|
|
1391
|
+
return {
|
|
1392
|
+
success: false,
|
|
1393
|
+
error: {
|
|
1394
|
+
code: "SCHEMA_CREATE_ERROR",
|
|
1395
|
+
message: "Failed to create collection"
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Update a collection
|
|
1402
|
+
*/
|
|
1403
|
+
async function handleSchemaCollectionUpdate(db, slug, input) {
|
|
1404
|
+
try {
|
|
1405
|
+
return {
|
|
1406
|
+
success: true,
|
|
1407
|
+
data: { item: await new SchemaRegistry(db).updateCollection(slug, input) }
|
|
1408
|
+
};
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
if (error instanceof SchemaError) return {
|
|
1411
|
+
success: false,
|
|
1412
|
+
error: {
|
|
1413
|
+
code: error.code,
|
|
1414
|
+
message: error.message,
|
|
1415
|
+
details: error.details
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
return {
|
|
1419
|
+
success: false,
|
|
1420
|
+
error: {
|
|
1421
|
+
code: "SCHEMA_UPDATE_ERROR",
|
|
1422
|
+
message: "Failed to update collection"
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Delete a collection
|
|
1429
|
+
*/
|
|
1430
|
+
async function handleSchemaCollectionDelete(db, slug, options) {
|
|
1431
|
+
try {
|
|
1432
|
+
await new SchemaRegistry(db).deleteCollection(slug, options);
|
|
1433
|
+
return {
|
|
1434
|
+
success: true,
|
|
1435
|
+
data: { success: true }
|
|
1436
|
+
};
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
if (error instanceof SchemaError) return {
|
|
1439
|
+
success: false,
|
|
1440
|
+
error: {
|
|
1441
|
+
code: error.code,
|
|
1442
|
+
message: error.message,
|
|
1443
|
+
details: error.details
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
return {
|
|
1447
|
+
success: false,
|
|
1448
|
+
error: {
|
|
1449
|
+
code: "SCHEMA_DELETE_ERROR",
|
|
1450
|
+
message: "Failed to delete collection"
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* List fields for a collection
|
|
1457
|
+
*/
|
|
1458
|
+
async function handleSchemaFieldList(db, collectionSlug) {
|
|
1459
|
+
try {
|
|
1460
|
+
const registry = new SchemaRegistry(db);
|
|
1461
|
+
const collection = await registry.getCollection(collectionSlug);
|
|
1462
|
+
if (!collection) return {
|
|
1463
|
+
success: false,
|
|
1464
|
+
error: {
|
|
1465
|
+
code: "NOT_FOUND",
|
|
1466
|
+
message: `Collection not found: ${collectionSlug}`
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
return {
|
|
1470
|
+
success: true,
|
|
1471
|
+
data: { items: await registry.listFields(collection.id) }
|
|
1472
|
+
};
|
|
1473
|
+
} catch {
|
|
1474
|
+
return {
|
|
1475
|
+
success: false,
|
|
1476
|
+
error: {
|
|
1477
|
+
code: "SCHEMA_FIELD_LIST_ERROR",
|
|
1478
|
+
message: "Failed to list fields"
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Get a field
|
|
1485
|
+
*/
|
|
1486
|
+
async function handleSchemaFieldGet(db, collectionSlug, fieldSlug) {
|
|
1487
|
+
try {
|
|
1488
|
+
const item = await new SchemaRegistry(db).getField(collectionSlug, fieldSlug);
|
|
1489
|
+
if (!item) return {
|
|
1490
|
+
success: false,
|
|
1491
|
+
error: {
|
|
1492
|
+
code: "NOT_FOUND",
|
|
1493
|
+
message: `Field not found: ${fieldSlug} in collection ${collectionSlug}`
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
return {
|
|
1497
|
+
success: true,
|
|
1498
|
+
data: { item }
|
|
1499
|
+
};
|
|
1500
|
+
} catch {
|
|
1501
|
+
return {
|
|
1502
|
+
success: false,
|
|
1503
|
+
error: {
|
|
1504
|
+
code: "SCHEMA_FIELD_GET_ERROR",
|
|
1505
|
+
message: "Failed to get field"
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Create a field
|
|
1512
|
+
*/
|
|
1513
|
+
async function handleSchemaFieldCreate(db, collectionSlug, input) {
|
|
1514
|
+
try {
|
|
1515
|
+
return {
|
|
1516
|
+
success: true,
|
|
1517
|
+
data: { item: await new SchemaRegistry(db).createField(collectionSlug, input) }
|
|
1518
|
+
};
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
if (error instanceof SchemaError) return {
|
|
1521
|
+
success: false,
|
|
1522
|
+
error: {
|
|
1523
|
+
code: error.code,
|
|
1524
|
+
message: error.message,
|
|
1525
|
+
details: error.details
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
return {
|
|
1529
|
+
success: false,
|
|
1530
|
+
error: {
|
|
1531
|
+
code: "SCHEMA_FIELD_CREATE_ERROR",
|
|
1532
|
+
message: "Failed to create field"
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Update a field
|
|
1539
|
+
*/
|
|
1540
|
+
async function handleSchemaFieldUpdate(db, collectionSlug, fieldSlug, input) {
|
|
1541
|
+
try {
|
|
1542
|
+
return {
|
|
1543
|
+
success: true,
|
|
1544
|
+
data: { item: await new SchemaRegistry(db).updateField(collectionSlug, fieldSlug, input) }
|
|
1545
|
+
};
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
if (error instanceof SchemaError) return {
|
|
1548
|
+
success: false,
|
|
1549
|
+
error: {
|
|
1550
|
+
code: error.code,
|
|
1551
|
+
message: error.message,
|
|
1552
|
+
details: error.details
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
return {
|
|
1556
|
+
success: false,
|
|
1557
|
+
error: {
|
|
1558
|
+
code: "SCHEMA_FIELD_UPDATE_ERROR",
|
|
1559
|
+
message: "Failed to update field"
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Delete a field
|
|
1566
|
+
*/
|
|
1567
|
+
async function handleSchemaFieldDelete(db, collectionSlug, fieldSlug) {
|
|
1568
|
+
try {
|
|
1569
|
+
await new SchemaRegistry(db).deleteField(collectionSlug, fieldSlug);
|
|
1570
|
+
return {
|
|
1571
|
+
success: true,
|
|
1572
|
+
data: { success: true }
|
|
1573
|
+
};
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
if (error instanceof SchemaError) return {
|
|
1576
|
+
success: false,
|
|
1577
|
+
error: {
|
|
1578
|
+
code: error.code,
|
|
1579
|
+
message: error.message,
|
|
1580
|
+
details: error.details
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
return {
|
|
1584
|
+
success: false,
|
|
1585
|
+
error: {
|
|
1586
|
+
code: "SCHEMA_FIELD_DELETE_ERROR",
|
|
1587
|
+
message: "Failed to delete field"
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Reorder fields
|
|
1594
|
+
*/
|
|
1595
|
+
async function handleSchemaFieldReorder(db, collectionSlug, fieldSlugs) {
|
|
1596
|
+
try {
|
|
1597
|
+
await new SchemaRegistry(db).reorderFields(collectionSlug, fieldSlugs);
|
|
1598
|
+
return {
|
|
1599
|
+
success: true,
|
|
1600
|
+
data: { success: true }
|
|
1601
|
+
};
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
if (error instanceof SchemaError) return {
|
|
1604
|
+
success: false,
|
|
1605
|
+
error: {
|
|
1606
|
+
code: error.code,
|
|
1607
|
+
message: error.message,
|
|
1608
|
+
details: error.details
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
return {
|
|
1612
|
+
success: false,
|
|
1613
|
+
error: {
|
|
1614
|
+
code: "SCHEMA_FIELD_REORDER_ERROR",
|
|
1615
|
+
message: "Failed to reorder fields"
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* List orphaned content tables
|
|
1622
|
+
*/
|
|
1623
|
+
async function handleOrphanedTableList(db) {
|
|
1624
|
+
try {
|
|
1625
|
+
return {
|
|
1626
|
+
success: true,
|
|
1627
|
+
data: { items: await new SchemaRegistry(db).discoverOrphanedTables() }
|
|
1628
|
+
};
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
console.error("[dineway] Failed to list orphaned tables:", error);
|
|
1631
|
+
return {
|
|
1632
|
+
success: false,
|
|
1633
|
+
error: {
|
|
1634
|
+
code: "ORPHAN_LIST_ERROR",
|
|
1635
|
+
message: "Failed to list orphaned tables"
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Register an orphaned table as a collection
|
|
1642
|
+
*/
|
|
1643
|
+
async function handleOrphanedTableRegister(db, slug, options) {
|
|
1644
|
+
try {
|
|
1645
|
+
return {
|
|
1646
|
+
success: true,
|
|
1647
|
+
data: { item: await new SchemaRegistry(db).registerOrphanedTable(slug, options) }
|
|
1648
|
+
};
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
if (error instanceof SchemaError) return {
|
|
1651
|
+
success: false,
|
|
1652
|
+
error: {
|
|
1653
|
+
code: error.code,
|
|
1654
|
+
message: error.message,
|
|
1655
|
+
details: error.details
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
return {
|
|
1659
|
+
success: false,
|
|
1660
|
+
error: {
|
|
1661
|
+
code: "ORPHAN_REGISTER_ERROR",
|
|
1662
|
+
message: "Failed to register orphaned table"
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
//#endregion
|
|
1669
|
+
//#region src/plugins/bundle-store.ts
|
|
1670
|
+
const VERSION_PATTERN = /^[a-z0-9][a-z0-9._+-]*$/i;
|
|
1671
|
+
const MARKETPLACE_BUNDLE_PREFIX = "marketplace";
|
|
1672
|
+
const MANIFEST_FILENAME = "manifest.json";
|
|
1673
|
+
const BACKEND_FILENAME = "backend.js";
|
|
1674
|
+
const ADMIN_FILENAME = "admin.js";
|
|
1675
|
+
function validateVersion(version) {
|
|
1676
|
+
if (version.includes("..")) throw new Error("Invalid version format");
|
|
1677
|
+
if (!VERSION_PATTERN.test(version)) throw new Error("Invalid version format");
|
|
1678
|
+
}
|
|
1679
|
+
function getBundlePrefix(pluginId, version) {
|
|
1680
|
+
validatePluginIdentifier(pluginId, "plugin ID");
|
|
1681
|
+
validateVersion(version);
|
|
1682
|
+
return `${MARKETPLACE_BUNDLE_PREFIX}/${pluginId}/${version}`;
|
|
1683
|
+
}
|
|
1684
|
+
async function streamToText(stream) {
|
|
1685
|
+
return new Response(stream).text();
|
|
1686
|
+
}
|
|
1687
|
+
var StoragePluginBundleStore = class {
|
|
1688
|
+
constructor(storage) {
|
|
1689
|
+
this.storage = storage;
|
|
1690
|
+
}
|
|
1691
|
+
async write(pluginId, version, bundle) {
|
|
1692
|
+
const prefix = getBundlePrefix(pluginId, version);
|
|
1693
|
+
await this.storage.upload({
|
|
1694
|
+
key: `${prefix}/${MANIFEST_FILENAME}`,
|
|
1695
|
+
body: new TextEncoder().encode(JSON.stringify(bundle.manifest)),
|
|
1696
|
+
contentType: "application/json"
|
|
1697
|
+
});
|
|
1698
|
+
await this.storage.upload({
|
|
1699
|
+
key: `${prefix}/${BACKEND_FILENAME}`,
|
|
1700
|
+
body: new TextEncoder().encode(bundle.backendCode),
|
|
1701
|
+
contentType: "application/javascript"
|
|
1702
|
+
});
|
|
1703
|
+
if (bundle.adminCode) {
|
|
1704
|
+
await this.storage.upload({
|
|
1705
|
+
key: `${prefix}/${ADMIN_FILENAME}`,
|
|
1706
|
+
body: new TextEncoder().encode(bundle.adminCode),
|
|
1707
|
+
contentType: "application/javascript"
|
|
1708
|
+
});
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
await this.storage.delete(`${prefix}/${ADMIN_FILENAME}`);
|
|
1713
|
+
} catch {}
|
|
1714
|
+
}
|
|
1715
|
+
async read(pluginId, version) {
|
|
1716
|
+
const prefix = getBundlePrefix(pluginId, version);
|
|
1717
|
+
try {
|
|
1718
|
+
const manifestResult = await this.storage.download(`${prefix}/${MANIFEST_FILENAME}`);
|
|
1719
|
+
const backendResult = await this.storage.download(`${prefix}/${BACKEND_FILENAME}`);
|
|
1720
|
+
const manifestText = await streamToText(manifestResult.body);
|
|
1721
|
+
const backendCode = await streamToText(backendResult.body);
|
|
1722
|
+
const parsed = JSON.parse(manifestText);
|
|
1723
|
+
const result = pluginManifestSchema.safeParse(parsed);
|
|
1724
|
+
if (!result.success) return null;
|
|
1725
|
+
let adminCode;
|
|
1726
|
+
try {
|
|
1727
|
+
adminCode = await streamToText((await this.storage.download(`${prefix}/${ADMIN_FILENAME}`)).body);
|
|
1728
|
+
} catch {}
|
|
1729
|
+
return {
|
|
1730
|
+
manifest: result.data,
|
|
1731
|
+
backendCode,
|
|
1732
|
+
adminCode
|
|
1733
|
+
};
|
|
1734
|
+
} catch {
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
async delete(pluginId, version) {
|
|
1739
|
+
const prefix = getBundlePrefix(pluginId, version);
|
|
1740
|
+
for (const filename of [
|
|
1741
|
+
MANIFEST_FILENAME,
|
|
1742
|
+
BACKEND_FILENAME,
|
|
1743
|
+
ADMIN_FILENAME
|
|
1744
|
+
]) try {
|
|
1745
|
+
await this.storage.delete(`${prefix}/${filename}`);
|
|
1746
|
+
} catch {}
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
function createPluginBundleStore(storage) {
|
|
1750
|
+
return new StoragePluginBundleStore(storage);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
//#endregion
|
|
1754
|
+
//#region src/plugins/marketplace.ts
|
|
1755
|
+
/**
|
|
1756
|
+
* MarketplaceClient — HTTP client for the Dineway Plugin Marketplace
|
|
1757
|
+
*
|
|
1758
|
+
* Used by the install/update/proxy endpoints in Dineway core to communicate
|
|
1759
|
+
* with the marketplace service. The marketplace is a distribution channel,
|
|
1760
|
+
* not a runtime dependency — bundles are copied to site-local bundle storage
|
|
1761
|
+
* at install time.
|
|
1762
|
+
*/
|
|
1763
|
+
const TRAILING_SLASHES = /\/+$/;
|
|
1764
|
+
const LEADING_DOT_SLASH = /^\.\//;
|
|
1765
|
+
var MarketplaceError = class extends Error {
|
|
1766
|
+
constructor(message, status, code) {
|
|
1767
|
+
super(message);
|
|
1768
|
+
this.status = status;
|
|
1769
|
+
this.code = code;
|
|
1770
|
+
this.name = "MarketplaceError";
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
var MarketplaceUnavailableError = class extends MarketplaceError {
|
|
1774
|
+
constructor(cause) {
|
|
1775
|
+
super("Plugin marketplace is unavailable", void 0, "MARKETPLACE_UNAVAILABLE");
|
|
1776
|
+
if (cause) this.cause = cause;
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
var MarketplaceClientImpl = class {
|
|
1780
|
+
baseUrl;
|
|
1781
|
+
siteOrigin;
|
|
1782
|
+
constructor(baseUrl, siteOrigin) {
|
|
1783
|
+
this.baseUrl = baseUrl.replace(TRAILING_SLASHES, "");
|
|
1784
|
+
this.siteOrigin = siteOrigin;
|
|
1785
|
+
}
|
|
1786
|
+
async search(query, opts) {
|
|
1787
|
+
const params = new URLSearchParams();
|
|
1788
|
+
if (query) params.set("q", query);
|
|
1789
|
+
if (opts?.category) params.set("category", opts.category);
|
|
1790
|
+
if (opts?.capability) params.set("capability", opts.capability);
|
|
1791
|
+
if (opts?.sort) params.set("sort", opts.sort);
|
|
1792
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
1793
|
+
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
1794
|
+
const qs = params.toString();
|
|
1795
|
+
const url = `${this.baseUrl}/api/v1/plugins${qs ? `?${qs}` : ""}`;
|
|
1796
|
+
return await this.fetchJson(url);
|
|
1797
|
+
}
|
|
1798
|
+
async getPlugin(id) {
|
|
1799
|
+
const url = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}`;
|
|
1800
|
+
return this.fetchJson(url);
|
|
1801
|
+
}
|
|
1802
|
+
async getVersions(id) {
|
|
1803
|
+
const url = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}/versions`;
|
|
1804
|
+
return (await this.fetchJson(url)).items;
|
|
1805
|
+
}
|
|
1806
|
+
async downloadBundle(id, version) {
|
|
1807
|
+
const bundleUrl = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}/versions/${encodeURIComponent(version)}/bundle`;
|
|
1808
|
+
const marketplaceOrigin = new URL(this.baseUrl).origin;
|
|
1809
|
+
const maxRedirects = 5;
|
|
1810
|
+
let response;
|
|
1811
|
+
try {
|
|
1812
|
+
let currentUrl = bundleUrl;
|
|
1813
|
+
response = await fetch(currentUrl, { redirect: "manual" });
|
|
1814
|
+
for (let i = 0; i < maxRedirects; i++) {
|
|
1815
|
+
if (response.status < 300 || response.status >= 400) break;
|
|
1816
|
+
const location = response.headers.get("location");
|
|
1817
|
+
if (!location) break;
|
|
1818
|
+
const target = new URL(location, currentUrl);
|
|
1819
|
+
if (target.origin !== marketplaceOrigin) throw new MarketplaceError(`Bundle download redirected to untrusted host: ${target.origin}`, response.status, "BUNDLE_REDIRECT_UNTRUSTED");
|
|
1820
|
+
currentUrl = target.href;
|
|
1821
|
+
response = await fetch(currentUrl, { redirect: "manual" });
|
|
1822
|
+
}
|
|
1823
|
+
if (response.status >= 300 && response.status < 400) throw new MarketplaceError(`Bundle download exceeded maximum redirects (${maxRedirects})`, response.status, "BUNDLE_TOO_MANY_REDIRECTS");
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
if (err instanceof MarketplaceError) throw err;
|
|
1826
|
+
throw new MarketplaceUnavailableError(err);
|
|
1827
|
+
}
|
|
1828
|
+
if (!response.ok) throw new MarketplaceError(`Failed to download bundle: ${response.status} ${response.statusText}`, response.status, "BUNDLE_DOWNLOAD_FAILED");
|
|
1829
|
+
const tarballBytes = new Uint8Array(await response.arrayBuffer());
|
|
1830
|
+
try {
|
|
1831
|
+
return await extractBundle(tarballBytes);
|
|
1832
|
+
} catch (err) {
|
|
1833
|
+
if (err instanceof MarketplaceError) throw err;
|
|
1834
|
+
throw new MarketplaceError("Failed to extract plugin bundle", void 0, "BUNDLE_EXTRACT_FAILED");
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
async reportInstall(id, version) {
|
|
1838
|
+
const siteHash = await generateSiteHash(this.siteOrigin);
|
|
1839
|
+
const url = `${this.baseUrl}/api/v1/plugins/${encodeURIComponent(id)}/installs`;
|
|
1840
|
+
try {
|
|
1841
|
+
await fetch(url, {
|
|
1842
|
+
method: "POST",
|
|
1843
|
+
headers: { "Content-Type": "application/json" },
|
|
1844
|
+
body: JSON.stringify({
|
|
1845
|
+
siteHash,
|
|
1846
|
+
version
|
|
1847
|
+
})
|
|
1848
|
+
});
|
|
1849
|
+
} catch {}
|
|
1850
|
+
}
|
|
1851
|
+
async searchThemes(query, opts) {
|
|
1852
|
+
const params = new URLSearchParams();
|
|
1853
|
+
if (query) params.set("q", query);
|
|
1854
|
+
if (opts?.keyword) params.set("keyword", opts.keyword);
|
|
1855
|
+
if (opts?.sort) params.set("sort", opts.sort);
|
|
1856
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
1857
|
+
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
1858
|
+
const qs = params.toString();
|
|
1859
|
+
const url = `${this.baseUrl}/api/v1/themes${qs ? `?${qs}` : ""}`;
|
|
1860
|
+
return this.fetchJson(url);
|
|
1861
|
+
}
|
|
1862
|
+
async getTheme(id) {
|
|
1863
|
+
const url = `${this.baseUrl}/api/v1/themes/${encodeURIComponent(id)}`;
|
|
1864
|
+
return this.fetchJson(url);
|
|
1865
|
+
}
|
|
1866
|
+
async fetchJson(url) {
|
|
1867
|
+
let response;
|
|
1868
|
+
try {
|
|
1869
|
+
response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
throw new MarketplaceUnavailableError(err);
|
|
1872
|
+
}
|
|
1873
|
+
if (!response.ok) {
|
|
1874
|
+
let errorMessage = `Marketplace request failed: ${response.status}`;
|
|
1875
|
+
try {
|
|
1876
|
+
const body = await response.json();
|
|
1877
|
+
if (body.error) errorMessage = body.error;
|
|
1878
|
+
} catch {}
|
|
1879
|
+
throw new MarketplaceError(errorMessage, response.status);
|
|
1880
|
+
}
|
|
1881
|
+
return await response.json();
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
/**
|
|
1885
|
+
* Extract manifest + code files from a tarball.
|
|
1886
|
+
*
|
|
1887
|
+
* The tarball is a gzipped tar archive containing:
|
|
1888
|
+
* - manifest.json
|
|
1889
|
+
* - backend.js
|
|
1890
|
+
* - admin.js (optional)
|
|
1891
|
+
*
|
|
1892
|
+
* We use a minimal tar parser since we only need to read a few small files.
|
|
1893
|
+
*/
|
|
1894
|
+
async function extractBundle(tarballBytes) {
|
|
1895
|
+
const decompressedStream = new ReadableStream({ start(controller) {
|
|
1896
|
+
controller.enqueue(tarballBytes);
|
|
1897
|
+
controller.close();
|
|
1898
|
+
} }).pipeThrough(createGzipDecoder());
|
|
1899
|
+
const decompressedBuf = await new Response(decompressedStream).arrayBuffer();
|
|
1900
|
+
const decompressedBytes = new Uint8Array(decompressedBuf);
|
|
1901
|
+
const entries = await unpackTar(new ReadableStream({ start(controller) {
|
|
1902
|
+
controller.enqueue(decompressedBytes);
|
|
1903
|
+
controller.close();
|
|
1904
|
+
} }));
|
|
1905
|
+
const decoder = new TextDecoder();
|
|
1906
|
+
const files = /* @__PURE__ */ new Map();
|
|
1907
|
+
for (const entry of entries) if (entry.data && entry.header.type === "file") {
|
|
1908
|
+
const name = entry.header.name.replace(LEADING_DOT_SLASH, "");
|
|
1909
|
+
files.set(name, decoder.decode(entry.data));
|
|
1910
|
+
}
|
|
1911
|
+
const manifestJson = files.get("manifest.json");
|
|
1912
|
+
const backendCode = files.get("backend.js");
|
|
1913
|
+
if (!manifestJson) throw new MarketplaceError("Invalid bundle: missing manifest.json", void 0, "INVALID_BUNDLE");
|
|
1914
|
+
if (!backendCode) throw new MarketplaceError("Invalid bundle: missing backend.js", void 0, "INVALID_BUNDLE");
|
|
1915
|
+
let manifest;
|
|
1916
|
+
try {
|
|
1917
|
+
const parsed = JSON.parse(manifestJson);
|
|
1918
|
+
const result = pluginManifestSchema.safeParse(parsed);
|
|
1919
|
+
if (!result.success) throw new MarketplaceError("Invalid bundle: manifest.json failed validation", void 0, "INVALID_BUNDLE");
|
|
1920
|
+
manifest = result.data;
|
|
1921
|
+
} catch (err) {
|
|
1922
|
+
if (err instanceof MarketplaceError) throw err;
|
|
1923
|
+
throw new MarketplaceError("Invalid bundle: malformed manifest.json", void 0, "INVALID_BUNDLE");
|
|
1924
|
+
}
|
|
1925
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", tarballBytes);
|
|
1926
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
1927
|
+
const checksum = Array.from(hashArray, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1928
|
+
return {
|
|
1929
|
+
manifest,
|
|
1930
|
+
backendCode,
|
|
1931
|
+
adminCode: files.get("admin.js"),
|
|
1932
|
+
checksum
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Generate a stable non-identifying site hash from the site origin.
|
|
1937
|
+
* The same origin always produces the same hash, so the marketplace
|
|
1938
|
+
* installs table deduplicates correctly per (plugin_id, site_hash).
|
|
1939
|
+
*/
|
|
1940
|
+
async function generateSiteHash(siteOrigin) {
|
|
1941
|
+
const seed = siteOrigin ? `dineway-site:${siteOrigin}` : `dineway-anonymous`;
|
|
1942
|
+
try {
|
|
1943
|
+
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed));
|
|
1944
|
+
const arr = new Uint8Array(hash);
|
|
1945
|
+
return Array.from(arr.slice(0, 8), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1946
|
+
} catch {
|
|
1947
|
+
let h = 2166136261;
|
|
1948
|
+
for (let i = 0; i < seed.length; i++) {
|
|
1949
|
+
h ^= seed.charCodeAt(i);
|
|
1950
|
+
h = Math.imul(h, 16777619);
|
|
1951
|
+
}
|
|
1952
|
+
const h2 = h ^ h >>> 16;
|
|
1953
|
+
return (h >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Create a MarketplaceClient for the given marketplace URL.
|
|
1958
|
+
*
|
|
1959
|
+
* @param baseUrl - The marketplace API base URL (e.g. "https://marketplace.example.com")
|
|
1960
|
+
* @param siteOrigin - The origin of the Dineway site (e.g. "https://myblog.example.com").
|
|
1961
|
+
* Used to generate a stable, non-identifying site hash for install deduplication.
|
|
1962
|
+
*/
|
|
1963
|
+
function createMarketplaceClient(baseUrl, siteOrigin) {
|
|
1964
|
+
return new MarketplaceClientImpl(baseUrl, siteOrigin);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
//#endregion
|
|
1968
|
+
//#region src/api/handlers/marketplace.ts
|
|
1969
|
+
function getClient(marketplaceUrl, siteOrigin) {
|
|
1970
|
+
if (!marketplaceUrl) return null;
|
|
1971
|
+
return createMarketplaceClient(marketplaceUrl, siteOrigin);
|
|
1972
|
+
}
|
|
1973
|
+
function diffCapabilities(oldCaps, newCaps) {
|
|
1974
|
+
const oldSet = new Set(oldCaps);
|
|
1975
|
+
const newSet = new Set(newCaps);
|
|
1976
|
+
return {
|
|
1977
|
+
added: newCaps.filter((c) => !oldSet.has(c)),
|
|
1978
|
+
removed: oldCaps.filter((c) => !newSet.has(c))
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Diff route visibility between two manifests.
|
|
1983
|
+
* Returns routes that changed from private to public (newly exposed).
|
|
1984
|
+
*/
|
|
1985
|
+
function diffRouteVisibility(oldManifest, newManifest) {
|
|
1986
|
+
const oldPublicRoutes = /* @__PURE__ */ new Set();
|
|
1987
|
+
if (oldManifest) for (const entry of oldManifest.routes) {
|
|
1988
|
+
const normalized = normalizeManifestRoute(entry);
|
|
1989
|
+
if (normalized.public === true) oldPublicRoutes.add(normalized.name);
|
|
1990
|
+
}
|
|
1991
|
+
const newlyPublic = [];
|
|
1992
|
+
for (const entry of newManifest.routes) {
|
|
1993
|
+
const normalized = normalizeManifestRoute(entry);
|
|
1994
|
+
if (normalized.public === true && !oldPublicRoutes.has(normalized.name)) newlyPublic.push(normalized.name);
|
|
1995
|
+
}
|
|
1996
|
+
return { newlyPublic };
|
|
1997
|
+
}
|
|
1998
|
+
async function resolveVersionMetadata(client, pluginId, pluginDetail, version) {
|
|
1999
|
+
if (pluginDetail.latestVersion?.version === version) return {
|
|
2000
|
+
version: pluginDetail.latestVersion.version,
|
|
2001
|
+
minDinewayVersion: pluginDetail.latestVersion.minDinewayVersion,
|
|
2002
|
+
bundleSize: pluginDetail.latestVersion.bundleSize,
|
|
2003
|
+
checksum: pluginDetail.latestVersion.checksum,
|
|
2004
|
+
changelog: pluginDetail.latestVersion.changelog,
|
|
2005
|
+
capabilities: pluginDetail.latestVersion.capabilities,
|
|
2006
|
+
status: pluginDetail.latestVersion.status,
|
|
2007
|
+
auditVerdict: pluginDetail.latestVersion.audit?.verdict ?? null,
|
|
2008
|
+
imageAuditVerdict: pluginDetail.latestVersion.imageAudit?.verdict ?? null,
|
|
2009
|
+
publishedAt: pluginDetail.latestVersion.publishedAt
|
|
2010
|
+
};
|
|
2011
|
+
return (await client.getVersions(pluginId)).find((v) => v.version === version) ?? null;
|
|
2012
|
+
}
|
|
2013
|
+
function validateBundleIdentity(bundle, pluginId, version) {
|
|
2014
|
+
if (bundle.manifest.id !== pluginId) return {
|
|
2015
|
+
success: false,
|
|
2016
|
+
error: {
|
|
2017
|
+
code: "MANIFEST_MISMATCH",
|
|
2018
|
+
message: `Bundle manifest ID (${bundle.manifest.id}) does not match requested plugin (${pluginId})`
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
if (bundle.manifest.version !== version) return {
|
|
2022
|
+
success: false,
|
|
2023
|
+
error: {
|
|
2024
|
+
code: "MANIFEST_VERSION_MISMATCH",
|
|
2025
|
+
message: `Bundle manifest version (${bundle.manifest.version}) does not match requested version (${version})`
|
|
2026
|
+
}
|
|
2027
|
+
};
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
async function preflightSandboxBundle(bundle, sandboxRunner) {
|
|
2031
|
+
await (await sandboxRunner.load(bundle.manifest, bundle.backendCode)).terminate();
|
|
2032
|
+
}
|
|
2033
|
+
function buildInstallPreview(pluginId, pluginDetail, versionMetadata) {
|
|
2034
|
+
return {
|
|
2035
|
+
pluginId,
|
|
2036
|
+
name: pluginDetail.name,
|
|
2037
|
+
description: pluginDetail.description,
|
|
2038
|
+
authorName: pluginDetail.author.name,
|
|
2039
|
+
version: versionMetadata.version,
|
|
2040
|
+
minDinewayVersion: versionMetadata.minDinewayVersion,
|
|
2041
|
+
bundleSize: versionMetadata.bundleSize,
|
|
2042
|
+
checksum: versionMetadata.checksum,
|
|
2043
|
+
changelog: versionMetadata.changelog,
|
|
2044
|
+
capabilities: versionMetadata.capabilities,
|
|
2045
|
+
status: versionMetadata.status,
|
|
2046
|
+
auditVerdict: versionMetadata.auditVerdict,
|
|
2047
|
+
imageAuditVerdict: versionMetadata.imageAuditVerdict,
|
|
2048
|
+
publishedAt: versionMetadata.publishedAt
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
async function handleMarketplaceInstallPreview(marketplaceUrl, pluginId, opts) {
|
|
2052
|
+
const client = getClient(marketplaceUrl, opts.siteOrigin);
|
|
2053
|
+
if (!client) return {
|
|
2054
|
+
success: false,
|
|
2055
|
+
error: {
|
|
2056
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2057
|
+
message: "Marketplace is not configured"
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
try {
|
|
2061
|
+
const pluginDetail = await client.getPlugin(pluginId);
|
|
2062
|
+
const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, opts.version);
|
|
2063
|
+
if (!versionMetadata) return {
|
|
2064
|
+
success: false,
|
|
2065
|
+
error: {
|
|
2066
|
+
code: "NO_VERSION",
|
|
2067
|
+
message: `Version ${opts.version} was not found for plugin ${pluginId}`
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
if (versionMetadata.auditVerdict === "fail" || versionMetadata.auditVerdict === "warn") return {
|
|
2071
|
+
success: false,
|
|
2072
|
+
error: {
|
|
2073
|
+
code: "AUDIT_FAILED",
|
|
2074
|
+
message: versionMetadata.auditVerdict === "fail" ? "Plugin failed security audit and cannot be installed" : "Plugin audit was inconclusive and cannot be installed until reviewed"
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
return {
|
|
2078
|
+
success: true,
|
|
2079
|
+
data: buildInstallPreview(pluginId, pluginDetail, versionMetadata)
|
|
2080
|
+
};
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2083
|
+
success: false,
|
|
2084
|
+
error: {
|
|
2085
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2086
|
+
message: "Marketplace is unavailable"
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
if (err instanceof MarketplaceError) return {
|
|
2090
|
+
success: false,
|
|
2091
|
+
error: {
|
|
2092
|
+
code: err.code ?? "MARKETPLACE_ERROR",
|
|
2093
|
+
message: err.message
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
console.error("Failed to preview marketplace plugin install:", err);
|
|
2097
|
+
return {
|
|
2098
|
+
success: false,
|
|
2099
|
+
error: {
|
|
2100
|
+
code: "INSTALL_FAILED",
|
|
2101
|
+
message: "Failed to prepare plugin install preview"
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
async function handleMarketplaceUpdatePreview(db, storage, marketplaceUrl, pluginId, opts) {
|
|
2107
|
+
const client = getClient(marketplaceUrl);
|
|
2108
|
+
if (!client) return {
|
|
2109
|
+
success: false,
|
|
2110
|
+
error: {
|
|
2111
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2112
|
+
message: "Marketplace is not configured"
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
if (!storage) return {
|
|
2116
|
+
success: false,
|
|
2117
|
+
error: {
|
|
2118
|
+
code: "STORAGE_NOT_CONFIGURED",
|
|
2119
|
+
message: "Storage is required"
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
try {
|
|
2123
|
+
const existing = await new PluginStateRepository(db).get(pluginId);
|
|
2124
|
+
if (!existing || existing.source !== "marketplace") return {
|
|
2125
|
+
success: false,
|
|
2126
|
+
error: {
|
|
2127
|
+
code: "NOT_FOUND",
|
|
2128
|
+
message: `No marketplace plugin found: ${pluginId}`
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
const oldVersion = existing.marketplaceVersion ?? existing.version;
|
|
2132
|
+
if (opts.version === oldVersion) return {
|
|
2133
|
+
success: false,
|
|
2134
|
+
error: {
|
|
2135
|
+
code: "ALREADY_UP_TO_DATE",
|
|
2136
|
+
message: "Plugin is already up to date"
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
const pluginDetail = await client.getPlugin(pluginId);
|
|
2140
|
+
const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, opts.version);
|
|
2141
|
+
if (!versionMetadata) return {
|
|
2142
|
+
success: false,
|
|
2143
|
+
error: {
|
|
2144
|
+
code: "NO_VERSION",
|
|
2145
|
+
message: `Version ${opts.version} was not found for plugin ${pluginId}`
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
const bundle = await client.downloadBundle(pluginId, opts.version);
|
|
2149
|
+
if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) return {
|
|
2150
|
+
success: false,
|
|
2151
|
+
error: {
|
|
2152
|
+
code: "CHECKSUM_MISMATCH",
|
|
2153
|
+
message: "Bundle checksum does not match marketplace record. Download may be corrupted."
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
const bundleIdentityError = validateBundleIdentity(bundle, pluginId, opts.version);
|
|
2157
|
+
if (bundleIdentityError) return bundleIdentityError;
|
|
2158
|
+
const oldBundle = await createPluginBundleStore(storage).read(pluginId, oldVersion);
|
|
2159
|
+
const capabilityChanges = diffCapabilities(oldBundle?.manifest.capabilities ?? [], bundle.manifest.capabilities);
|
|
2160
|
+
const routeVisibilityChanges = diffRouteVisibility(oldBundle?.manifest, bundle.manifest);
|
|
2161
|
+
return {
|
|
2162
|
+
success: true,
|
|
2163
|
+
data: {
|
|
2164
|
+
...buildInstallPreview(pluginId, pluginDetail, versionMetadata),
|
|
2165
|
+
oldVersion,
|
|
2166
|
+
capabilityChanges,
|
|
2167
|
+
routeVisibilityChanges
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
} catch (err) {
|
|
2171
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2172
|
+
success: false,
|
|
2173
|
+
error: {
|
|
2174
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2175
|
+
message: "Marketplace is unavailable"
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
if (err instanceof MarketplaceError) return {
|
|
2179
|
+
success: false,
|
|
2180
|
+
error: {
|
|
2181
|
+
code: err.code ?? "MARKETPLACE_ERROR",
|
|
2182
|
+
message: err.message
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
console.error("Failed to preview marketplace plugin update:", err);
|
|
2186
|
+
return {
|
|
2187
|
+
success: false,
|
|
2188
|
+
error: {
|
|
2189
|
+
code: "UPDATE_FAILED",
|
|
2190
|
+
message: "Failed to prepare plugin update preview"
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
async function handleMarketplaceInstall(db, storage, sandboxRunner, marketplaceUrl, pluginId, opts) {
|
|
2196
|
+
const client = getClient(marketplaceUrl, opts?.siteOrigin);
|
|
2197
|
+
if (!client) return {
|
|
2198
|
+
success: false,
|
|
2199
|
+
error: {
|
|
2200
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2201
|
+
message: "Marketplace is not configured"
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
if (!storage) return {
|
|
2205
|
+
success: false,
|
|
2206
|
+
error: {
|
|
2207
|
+
code: "STORAGE_NOT_CONFIGURED",
|
|
2208
|
+
message: "Storage is required for marketplace plugin installation"
|
|
2209
|
+
}
|
|
2210
|
+
};
|
|
2211
|
+
const bundleStore = createPluginBundleStore(storage);
|
|
2212
|
+
if (!sandboxRunner || !sandboxRunner.isAvailable()) return {
|
|
2213
|
+
success: false,
|
|
2214
|
+
error: {
|
|
2215
|
+
code: "SANDBOX_NOT_AVAILABLE",
|
|
2216
|
+
message: "Sandbox runner is required for marketplace plugins"
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
try {
|
|
2220
|
+
const stateRepo = new PluginStateRepository(db);
|
|
2221
|
+
const existing = await stateRepo.get(pluginId);
|
|
2222
|
+
if (existing && existing.source === "marketplace") return {
|
|
2223
|
+
success: false,
|
|
2224
|
+
error: {
|
|
2225
|
+
code: "ALREADY_INSTALLED",
|
|
2226
|
+
message: `Plugin ${pluginId} is already installed`
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
if (opts?.configuredPluginIds?.has(pluginId)) return {
|
|
2230
|
+
success: false,
|
|
2231
|
+
error: {
|
|
2232
|
+
code: "PLUGIN_ID_CONFLICT",
|
|
2233
|
+
message: `Cannot install marketplace plugin "${pluginId}" — a configured plugin with the same ID already exists`
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
const pluginDetail = await client.getPlugin(pluginId);
|
|
2237
|
+
const version = opts?.version ?? pluginDetail.latestVersion?.version;
|
|
2238
|
+
if (!version) return {
|
|
2239
|
+
success: false,
|
|
2240
|
+
error: {
|
|
2241
|
+
code: "NO_VERSION",
|
|
2242
|
+
message: `No published versions found for plugin ${pluginId}`
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, version);
|
|
2246
|
+
if (!versionMetadata) return {
|
|
2247
|
+
success: false,
|
|
2248
|
+
error: {
|
|
2249
|
+
code: "NO_VERSION",
|
|
2250
|
+
message: `Version ${version} was not found for plugin ${pluginId}`
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
if (versionMetadata.auditVerdict === "fail" || versionMetadata.auditVerdict === "warn") return {
|
|
2254
|
+
success: false,
|
|
2255
|
+
error: {
|
|
2256
|
+
code: "AUDIT_FAILED",
|
|
2257
|
+
message: versionMetadata.auditVerdict === "fail" ? "Plugin failed security audit and cannot be installed" : "Plugin audit was inconclusive and cannot be installed until reviewed"
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
const bundle = await client.downloadBundle(pluginId, version);
|
|
2261
|
+
if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) return {
|
|
2262
|
+
success: false,
|
|
2263
|
+
error: {
|
|
2264
|
+
code: "CHECKSUM_MISMATCH",
|
|
2265
|
+
message: "Bundle checksum does not match marketplace record. Download may be corrupted."
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
const bundleIdentityError = validateBundleIdentity(bundle, pluginId, version);
|
|
2269
|
+
if (bundleIdentityError) return bundleIdentityError;
|
|
2270
|
+
await preflightSandboxBundle(bundle, sandboxRunner);
|
|
2271
|
+
await bundleStore.write(pluginId, version, bundle);
|
|
2272
|
+
await stateRepo.upsert(pluginId, version, "active", {
|
|
2273
|
+
source: "marketplace",
|
|
2274
|
+
marketplaceVersion: version,
|
|
2275
|
+
displayName: pluginDetail.name,
|
|
2276
|
+
description: pluginDetail.description ?? void 0
|
|
2277
|
+
});
|
|
2278
|
+
client.reportInstall(pluginId, version).catch(() => {});
|
|
2279
|
+
return {
|
|
2280
|
+
success: true,
|
|
2281
|
+
data: {
|
|
2282
|
+
pluginId,
|
|
2283
|
+
version,
|
|
2284
|
+
capabilities: bundle.manifest.capabilities
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
} catch (err) {
|
|
2288
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2289
|
+
success: false,
|
|
2290
|
+
error: {
|
|
2291
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2292
|
+
message: "Plugin marketplace is currently unavailable"
|
|
2293
|
+
}
|
|
2294
|
+
};
|
|
2295
|
+
if (err instanceof MarketplaceError) return {
|
|
2296
|
+
success: false,
|
|
2297
|
+
error: {
|
|
2298
|
+
code: err.code ?? "MARKETPLACE_ERROR",
|
|
2299
|
+
message: err.message
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
if (err instanceof DinewayStorageError) return {
|
|
2303
|
+
success: false,
|
|
2304
|
+
error: {
|
|
2305
|
+
code: err.code ?? "STORAGE_ERROR",
|
|
2306
|
+
message: "Storage error while installing plugin"
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
2310
|
+
const code = err.code;
|
|
2311
|
+
if (typeof code === "string" && code.trim()) return {
|
|
2312
|
+
success: false,
|
|
2313
|
+
error: {
|
|
2314
|
+
code,
|
|
2315
|
+
message: "Failed to install plugin from marketplace"
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
console.error("Failed to install marketplace plugin:", err);
|
|
2320
|
+
return {
|
|
2321
|
+
success: false,
|
|
2322
|
+
error: {
|
|
2323
|
+
code: "INSTALL_FAILED",
|
|
2324
|
+
message: "Failed to install plugin from marketplace"
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
async function handleMarketplaceUpdate(db, storage, sandboxRunner, marketplaceUrl, pluginId, opts) {
|
|
2330
|
+
const client = getClient(marketplaceUrl);
|
|
2331
|
+
if (!client) return {
|
|
2332
|
+
success: false,
|
|
2333
|
+
error: {
|
|
2334
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2335
|
+
message: "Marketplace is not configured"
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
if (!storage) return {
|
|
2339
|
+
success: false,
|
|
2340
|
+
error: {
|
|
2341
|
+
code: "STORAGE_NOT_CONFIGURED",
|
|
2342
|
+
message: "Storage is required"
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
const bundleStore = createPluginBundleStore(storage);
|
|
2346
|
+
if (!sandboxRunner || !sandboxRunner.isAvailable()) return {
|
|
2347
|
+
success: false,
|
|
2348
|
+
error: {
|
|
2349
|
+
code: "SANDBOX_NOT_AVAILABLE",
|
|
2350
|
+
message: "Sandbox runner is required"
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
try {
|
|
2354
|
+
const stateRepo = new PluginStateRepository(db);
|
|
2355
|
+
const existing = await stateRepo.get(pluginId);
|
|
2356
|
+
if (!existing || existing.source !== "marketplace") return {
|
|
2357
|
+
success: false,
|
|
2358
|
+
error: {
|
|
2359
|
+
code: "NOT_FOUND",
|
|
2360
|
+
message: `No marketplace plugin found: ${pluginId}`
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
const oldVersion = existing.marketplaceVersion ?? existing.version;
|
|
2364
|
+
const pluginDetail = await client.getPlugin(pluginId);
|
|
2365
|
+
const newVersion = opts?.version ?? pluginDetail.latestVersion?.version;
|
|
2366
|
+
if (!newVersion) return {
|
|
2367
|
+
success: false,
|
|
2368
|
+
error: {
|
|
2369
|
+
code: "NO_VERSION",
|
|
2370
|
+
message: "No newer version available"
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
if (newVersion === oldVersion) return {
|
|
2374
|
+
success: false,
|
|
2375
|
+
error: {
|
|
2376
|
+
code: "ALREADY_UP_TO_DATE",
|
|
2377
|
+
message: "Plugin is already up to date"
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
const versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, newVersion);
|
|
2381
|
+
if (!versionMetadata) return {
|
|
2382
|
+
success: false,
|
|
2383
|
+
error: {
|
|
2384
|
+
code: "NO_VERSION",
|
|
2385
|
+
message: `Version ${newVersion} was not found for plugin ${pluginId}`
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
const bundle = await client.downloadBundle(pluginId, newVersion);
|
|
2389
|
+
if (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) return {
|
|
2390
|
+
success: false,
|
|
2391
|
+
error: {
|
|
2392
|
+
code: "CHECKSUM_MISMATCH",
|
|
2393
|
+
message: "Bundle checksum does not match marketplace record. Download may be corrupted."
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
const bundleIdentityError = validateBundleIdentity(bundle, pluginId, newVersion);
|
|
2397
|
+
if (bundleIdentityError) return bundleIdentityError;
|
|
2398
|
+
const oldBundle = await bundleStore.read(pluginId, oldVersion);
|
|
2399
|
+
const capabilityChanges = diffCapabilities(oldBundle?.manifest.capabilities ?? [], bundle.manifest.capabilities);
|
|
2400
|
+
if (capabilityChanges.added.length > 0 && !opts?.confirmCapabilityChanges) return {
|
|
2401
|
+
success: false,
|
|
2402
|
+
error: {
|
|
2403
|
+
code: "CAPABILITY_ESCALATION",
|
|
2404
|
+
message: "Plugin update requires new capabilities",
|
|
2405
|
+
details: { capabilityChanges }
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
const routeVisibilityChanges = diffRouteVisibility(oldBundle?.manifest, bundle.manifest);
|
|
2409
|
+
const hasNewPublicRoutes = routeVisibilityChanges.newlyPublic.length > 0;
|
|
2410
|
+
if (hasNewPublicRoutes && !opts?.confirmRouteVisibilityChanges) return {
|
|
2411
|
+
success: false,
|
|
2412
|
+
error: {
|
|
2413
|
+
code: "ROUTE_VISIBILITY_ESCALATION",
|
|
2414
|
+
message: "Plugin update exposes new public (unauthenticated) routes",
|
|
2415
|
+
details: {
|
|
2416
|
+
routeVisibilityChanges,
|
|
2417
|
+
capabilityChanges
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
await preflightSandboxBundle(bundle, sandboxRunner);
|
|
2422
|
+
await bundleStore.write(pluginId, newVersion, bundle);
|
|
2423
|
+
await stateRepo.upsert(pluginId, newVersion, "active", {
|
|
2424
|
+
source: "marketplace",
|
|
2425
|
+
marketplaceVersion: newVersion,
|
|
2426
|
+
displayName: pluginDetail.name,
|
|
2427
|
+
description: pluginDetail.description ?? void 0
|
|
2428
|
+
});
|
|
2429
|
+
bundleStore.delete(pluginId, oldVersion).catch(() => {});
|
|
2430
|
+
return {
|
|
2431
|
+
success: true,
|
|
2432
|
+
data: {
|
|
2433
|
+
pluginId,
|
|
2434
|
+
oldVersion,
|
|
2435
|
+
newVersion,
|
|
2436
|
+
capabilityChanges,
|
|
2437
|
+
routeVisibilityChanges: hasNewPublicRoutes ? routeVisibilityChanges : void 0
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
} catch (err) {
|
|
2441
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2442
|
+
success: false,
|
|
2443
|
+
error: {
|
|
2444
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2445
|
+
message: "Marketplace is unavailable"
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
if (err instanceof MarketplaceError) return {
|
|
2449
|
+
success: false,
|
|
2450
|
+
error: {
|
|
2451
|
+
code: err.code ?? "MARKETPLACE_ERROR",
|
|
2452
|
+
message: err.message
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
console.error("Failed to update marketplace plugin:", err);
|
|
2456
|
+
return {
|
|
2457
|
+
success: false,
|
|
2458
|
+
error: {
|
|
2459
|
+
code: "UPDATE_FAILED",
|
|
2460
|
+
message: "Failed to update plugin"
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
async function handleMarketplaceUninstall(db, storage, pluginId, opts) {
|
|
2466
|
+
try {
|
|
2467
|
+
const stateRepo = new PluginStateRepository(db);
|
|
2468
|
+
const existing = await stateRepo.get(pluginId);
|
|
2469
|
+
if (!existing || existing.source !== "marketplace") return {
|
|
2470
|
+
success: false,
|
|
2471
|
+
error: {
|
|
2472
|
+
code: "NOT_FOUND",
|
|
2473
|
+
message: `No marketplace plugin found: ${pluginId}`
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
const version = existing.marketplaceVersion ?? existing.version;
|
|
2477
|
+
if (storage) await createPluginBundleStore(storage).delete(pluginId, version);
|
|
2478
|
+
let dataDeleted = false;
|
|
2479
|
+
if (opts?.deleteData) try {
|
|
2480
|
+
await db.deleteFrom("_plugin_storage").where("plugin_id", "=", pluginId).execute();
|
|
2481
|
+
dataDeleted = true;
|
|
2482
|
+
} catch {}
|
|
2483
|
+
await stateRepo.delete(pluginId);
|
|
2484
|
+
return {
|
|
2485
|
+
success: true,
|
|
2486
|
+
data: {
|
|
2487
|
+
pluginId,
|
|
2488
|
+
dataDeleted
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
} catch (err) {
|
|
2492
|
+
console.error("Failed to uninstall marketplace plugin:", err);
|
|
2493
|
+
return {
|
|
2494
|
+
success: false,
|
|
2495
|
+
error: {
|
|
2496
|
+
code: "UNINSTALL_FAILED",
|
|
2497
|
+
message: "Failed to uninstall plugin"
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
async function handleMarketplaceUpdateCheck(db, marketplaceUrl) {
|
|
2503
|
+
const client = getClient(marketplaceUrl);
|
|
2504
|
+
if (!client) return {
|
|
2505
|
+
success: false,
|
|
2506
|
+
error: {
|
|
2507
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2508
|
+
message: "Marketplace is not configured"
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
try {
|
|
2512
|
+
const marketplacePlugins = await new PluginStateRepository(db).getMarketplacePlugins();
|
|
2513
|
+
const items = [];
|
|
2514
|
+
for (const plugin of marketplacePlugins) try {
|
|
2515
|
+
const detail = await client.getPlugin(plugin.pluginId);
|
|
2516
|
+
const latest = detail.latestVersion?.version;
|
|
2517
|
+
const installed = plugin.marketplaceVersion ?? plugin.version;
|
|
2518
|
+
if (!latest) continue;
|
|
2519
|
+
const hasUpdate = latest !== installed;
|
|
2520
|
+
let capabilityChanges;
|
|
2521
|
+
let hasCapabilityChanges = false;
|
|
2522
|
+
if (hasUpdate && detail.latestVersion) {
|
|
2523
|
+
capabilityChanges = diffCapabilities(detail.capabilities ?? [], detail.latestVersion.capabilities ?? []);
|
|
2524
|
+
hasCapabilityChanges = capabilityChanges.added.length > 0 || capabilityChanges.removed.length > 0;
|
|
2525
|
+
}
|
|
2526
|
+
items.push({
|
|
2527
|
+
pluginId: plugin.pluginId,
|
|
2528
|
+
installed,
|
|
2529
|
+
latest: latest ?? installed,
|
|
2530
|
+
hasUpdate,
|
|
2531
|
+
hasCapabilityChanges,
|
|
2532
|
+
capabilityChanges: hasCapabilityChanges ? capabilityChanges : void 0,
|
|
2533
|
+
hasRouteVisibilityChanges: false
|
|
2534
|
+
});
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
console.warn(`Failed to check updates for ${plugin.pluginId}:`, err);
|
|
2537
|
+
}
|
|
2538
|
+
return {
|
|
2539
|
+
success: true,
|
|
2540
|
+
data: { items }
|
|
2541
|
+
};
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2544
|
+
success: false,
|
|
2545
|
+
error: {
|
|
2546
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2547
|
+
message: "Marketplace is unavailable"
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
console.error("Failed to check marketplace updates:", err);
|
|
2551
|
+
return {
|
|
2552
|
+
success: false,
|
|
2553
|
+
error: {
|
|
2554
|
+
code: "UPDATE_CHECK_FAILED",
|
|
2555
|
+
message: "Failed to check for updates"
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
async function handleMarketplaceSearch(marketplaceUrl, query, opts) {
|
|
2561
|
+
const client = getClient(marketplaceUrl);
|
|
2562
|
+
if (!client) return {
|
|
2563
|
+
success: false,
|
|
2564
|
+
error: {
|
|
2565
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2566
|
+
message: "Marketplace is not configured"
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
try {
|
|
2570
|
+
return {
|
|
2571
|
+
success: true,
|
|
2572
|
+
data: await client.search(query, opts)
|
|
2573
|
+
};
|
|
2574
|
+
} catch (err) {
|
|
2575
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2576
|
+
success: false,
|
|
2577
|
+
error: {
|
|
2578
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2579
|
+
message: "Marketplace is unavailable"
|
|
2580
|
+
}
|
|
2581
|
+
};
|
|
2582
|
+
console.error("Failed to search marketplace:", err);
|
|
2583
|
+
return {
|
|
2584
|
+
success: false,
|
|
2585
|
+
error: {
|
|
2586
|
+
code: "SEARCH_FAILED",
|
|
2587
|
+
message: "Failed to search marketplace"
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
async function handleMarketplaceGetPlugin(marketplaceUrl, pluginId) {
|
|
2593
|
+
const client = getClient(marketplaceUrl);
|
|
2594
|
+
if (!client) return {
|
|
2595
|
+
success: false,
|
|
2596
|
+
error: {
|
|
2597
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2598
|
+
message: "Marketplace is not configured"
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
try {
|
|
2602
|
+
return {
|
|
2603
|
+
success: true,
|
|
2604
|
+
data: await client.getPlugin(pluginId)
|
|
2605
|
+
};
|
|
2606
|
+
} catch (err) {
|
|
2607
|
+
if (err instanceof MarketplaceError && err.status === 404) return {
|
|
2608
|
+
success: false,
|
|
2609
|
+
error: {
|
|
2610
|
+
code: "NOT_FOUND",
|
|
2611
|
+
message: `Plugin not found: ${pluginId}`
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2615
|
+
success: false,
|
|
2616
|
+
error: {
|
|
2617
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2618
|
+
message: "Marketplace is unavailable"
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
console.error("Failed to get marketplace plugin:", err);
|
|
2622
|
+
return {
|
|
2623
|
+
success: false,
|
|
2624
|
+
error: {
|
|
2625
|
+
code: "GET_PLUGIN_FAILED",
|
|
2626
|
+
message: "Failed to get plugin details"
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
async function handleThemeSearch(marketplaceUrl, query, opts) {
|
|
2632
|
+
const client = getClient(marketplaceUrl);
|
|
2633
|
+
if (!client) return {
|
|
2634
|
+
success: false,
|
|
2635
|
+
error: {
|
|
2636
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2637
|
+
message: "Marketplace is not configured"
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
try {
|
|
2641
|
+
return {
|
|
2642
|
+
success: true,
|
|
2643
|
+
data: await client.searchThemes(query, opts)
|
|
2644
|
+
};
|
|
2645
|
+
} catch (err) {
|
|
2646
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2647
|
+
success: false,
|
|
2648
|
+
error: {
|
|
2649
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2650
|
+
message: "Marketplace is unavailable"
|
|
2651
|
+
}
|
|
2652
|
+
};
|
|
2653
|
+
console.error("Failed to search themes:", err);
|
|
2654
|
+
return {
|
|
2655
|
+
success: false,
|
|
2656
|
+
error: {
|
|
2657
|
+
code: "THEME_SEARCH_FAILED",
|
|
2658
|
+
message: "Failed to search themes"
|
|
2659
|
+
}
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
async function handleThemeGetDetail(marketplaceUrl, themeId) {
|
|
2664
|
+
const client = getClient(marketplaceUrl);
|
|
2665
|
+
if (!client) return {
|
|
2666
|
+
success: false,
|
|
2667
|
+
error: {
|
|
2668
|
+
code: "MARKETPLACE_NOT_CONFIGURED",
|
|
2669
|
+
message: "Marketplace is not configured"
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
try {
|
|
2673
|
+
return {
|
|
2674
|
+
success: true,
|
|
2675
|
+
data: await client.getTheme(themeId)
|
|
2676
|
+
};
|
|
2677
|
+
} catch (err) {
|
|
2678
|
+
if (err instanceof MarketplaceError && err.status === 404) return {
|
|
2679
|
+
success: false,
|
|
2680
|
+
error: {
|
|
2681
|
+
code: "NOT_FOUND",
|
|
2682
|
+
message: `Theme not found: ${themeId}`
|
|
2683
|
+
}
|
|
2684
|
+
};
|
|
2685
|
+
if (err instanceof MarketplaceUnavailableError) return {
|
|
2686
|
+
success: false,
|
|
2687
|
+
error: {
|
|
2688
|
+
code: "MARKETPLACE_UNAVAILABLE",
|
|
2689
|
+
message: "Marketplace is unavailable"
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
console.error("Failed to get marketplace theme:", err);
|
|
2693
|
+
return {
|
|
2694
|
+
success: false,
|
|
2695
|
+
error: {
|
|
2696
|
+
code: "GET_THEME_FAILED",
|
|
2697
|
+
message: "Failed to get theme details"
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
//#endregion
|
|
2704
|
+
export { validateRev as $, handleRevisionGet as A, handleContentDuplicate as B, handleSchemaFieldReorder as C, handleMediaGet as D, handleMediaDelete as E, handleContentCountScheduled as F, handleContentPermanentDelete as G, handleContentGetIncludingTrashed as H, handleContentCountTrashed as I, handleContentSchedule as J, handleContentPublish as K, handleContentCreate as L, handleRevisionRestore as M, generateManifest as N, handleMediaList as O, handleContentCompare as P, handleContentUpdate as Q, handleContentDelete as R, handleSchemaFieldList as S, handleMediaCreate as T, handleContentList as U, handleContentGet as V, handleContentListTrashed as W, handleContentUnpublish as X, handleContentTranslations as Y, handleContentUnschedule as Z, handleSchemaCollectionList as _, handleMarketplaceUninstall as a, handleSchemaFieldDelete as b, handleMarketplaceUpdatePreview as c, createPluginBundleStore as d, handleOrphanedTableList as f, handleSchemaCollectionGet as g, handleSchemaCollectionDelete as h, handleMarketplaceSearch as i, handleRevisionList as j, handleMediaUpdate as k, handleThemeGetDetail as l, handleSchemaCollectionCreate as m, handleMarketplaceInstall as n, handleMarketplaceUpdate as o, handleOrphanedTableRegister as p, handleContentRestore as q, handleMarketplaceInstallPreview as r, handleMarketplaceUpdateCheck as s, handleMarketplaceGetPlugin as t, handleThemeSearch as u, handleSchemaCollectionUpdate as v, handleSchemaFieldUpdate as w, handleSchemaFieldGet as x, handleSchemaFieldCreate as y, handleContentDiscardDraft as z };
|