dineway 0.1.3 → 0.1.5
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/README.md +6 -3
- package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
- package/dist/astro/index.d.mts +18 -9
- package/dist/astro/index.mjs +238 -16
- package/dist/astro/middleware/auth.d.mts +16 -5
- package/dist/astro/middleware/auth.mjs +74 -37
- package/dist/astro/middleware/redirect.mjs +24 -8
- package/dist/astro/middleware/request-context.mjs +18 -5
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +411 -169
- package/dist/astro/types.d.mts +25 -8
- package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
- package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
- package/dist/cache-BdSY-gQN.mjs +42 -0
- package/dist/chunks--4F8ddV4.mjs +18 -0
- package/dist/cli/index.mjs +935 -15
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +11 -3
- package/dist/client/index.mjs +4 -3
- package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
- package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
- package/dist/database/instrumentation.d.mts +34 -0
- package/dist/database/instrumentation.mjs +53 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.mjs +11 -5
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +7 -1
- package/dist/db-errors-CEqD7qH9.mjs +23 -0
- package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
- package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
- package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
- package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +24 -22
- package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/page/index.d.mts +10 -2
- package/dist/page/index.mjs +22 -1
- package/dist/patterns-CrCYkMBb.mjs +92 -0
- package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
- package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
- package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
- package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
- package/dist/request-cache-Dk5qPSOx.mjs +66 -0
- package/dist/request-context.d.mts +4 -16
- package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
- package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-BNruJHDL.mjs → search-ByRGV2pq.mjs} +570 -424
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +11 -10
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +11 -3
- package/dist/storage/s3.mjs +78 -15
- package/dist/taxonomies-1s5PaS_8.mjs +266 -0
- package/dist/transaction-Cn2rjY78.mjs +27 -0
- package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
- package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
- package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
- package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
- package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
- package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
- package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
- package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
- package/dist/version-BKXPsfmJ.mjs +6 -0
- package/package.json +53 -39
- package/src/astro/routes/PluginRegistry.tsx +21 -0
- package/src/astro/routes/admin.astro +99 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +44 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +90 -0
- package/src/astro/routes/api/admin/briefing.ts +76 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +90 -0
- package/src/astro/routes/api/admin/bylines/index.ts +74 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
- package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
- package/src/astro/routes/api/admin/context/diff.ts +35 -0
- package/src/astro/routes/api/admin/context/index.ts +69 -0
- package/src/astro/routes/api/admin/context/stale.ts +35 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
- package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +132 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +137 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +95 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +91 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +91 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +98 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +154 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +135 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
- package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +62 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +72 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +166 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +86 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +90 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +221 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +120 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +85 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +88 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +119 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +72 -0
- package/src/astro/routes/api/auth/signup/complete.ts +87 -0
- package/src/astro/routes/api/auth/signup/request.ts +89 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +310 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +68 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +77 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +42 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +100 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +64 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +129 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +143 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +50 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +69 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +173 -0
- package/src/astro/routes/api/content/[collection]/index.ts +103 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/health.ts +54 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +523 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +330 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +212 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +425 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +399 -0
- package/src/astro/routes/api/manifest.ts +75 -0
- package/src/astro/routes/api/mcp.ts +125 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[...key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +91 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +146 -0
- package/src/astro/routes/api/media.ts +204 -0
- package/src/astro/routes/api/menus/[name]/items.ts +206 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +79 -0
- package/src/astro/routes/api/menus/[name].ts +145 -0
- package/src/astro/routes/api/menus/index.ts +91 -0
- package/src/astro/routes/api/oauth/authorize.ts +430 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +56 -0
- package/src/astro/routes/api/oauth/device/token.ts +70 -0
- package/src/astro/routes/api/oauth/register.ts +182 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +195 -0
- package/src/astro/routes/api/openapi.json.ts +33 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +109 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +183 -0
- package/src/astro/routes/api/redirects/index.ts +100 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +104 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +67 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +45 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +107 -0
- package/src/astro/routes/api/schema/collections/index.ts +61 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +52 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +50 -0
- package/src/astro/routes/api/sections/[slug].ts +203 -0
- package/src/astro/routes/api/sections/index.ts +107 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +116 -0
- package/src/astro/routes/api/setup/admin-verify.ts +122 -0
- package/src/astro/routes/api/setup/admin.ts +104 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +200 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +128 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +76 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +232 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +131 -0
- package/src/astro/routes/api/taxonomies/index.ts +114 -0
- package/src/astro/routes/api/themes/preview.ts +78 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +71 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +48 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +39 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +114 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +213 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +126 -0
- package/src/astro/routes/api/widget-areas/[name].ts +135 -0
- package/src/astro/routes/api/widget-areas/index.ts +149 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +81 -0
- package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
- package/src/astro/routes/sitemap.xml.ts +92 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/DinewayBodyEnd.astro +32 -0
- package/src/components/DinewayBodyStart.astro +32 -0
- package/src/components/DinewayHead.astro +61 -0
- package/src/components/DinewayImage.astro +178 -0
- package/src/components/DinewayMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1937 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +135 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +106 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/marks.ts +19 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/ui.ts +75 -0
- package/LICENSE +0 -9
- /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
- /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
- /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
- /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
- /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
- /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
- /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
- /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
- /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
- /package/dist/{types-DShnjzb6.mjs → types-griIBQOQ.mjs} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme preview signing endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/themes/preview
|
|
5
|
+
*
|
|
6
|
+
* Generates a signed preview URL for the "Try with my data" feature.
|
|
7
|
+
* The PREVIEW_SECRET must be set in the environment (shared with the preview sidecar).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
|
|
12
|
+
import { requirePerm } from "#api/authorize.js";
|
|
13
|
+
import { apiError, apiSuccess } from "#api/error.js";
|
|
14
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
export const POST: APIRoute = async ({ request, url, locals }) => {
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
|
|
21
|
+
if (!dineway?.db) {
|
|
22
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const denied = requirePerm(user, "plugins:read");
|
|
26
|
+
if (denied) return denied;
|
|
27
|
+
|
|
28
|
+
const secret = import.meta.env.DINEWAY_PREVIEW_SECRET || import.meta.env.PREVIEW_SECRET || "";
|
|
29
|
+
if (!secret) {
|
|
30
|
+
return apiError("NOT_CONFIGURED", "PREVIEW_SECRET is not configured", 500);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let body: { previewUrl: string };
|
|
34
|
+
try {
|
|
35
|
+
body = await request.json();
|
|
36
|
+
} catch {
|
|
37
|
+
return apiError("INVALID_REQUEST", "Invalid JSON body", 400);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!body.previewUrl || typeof body.previewUrl !== "string") {
|
|
41
|
+
return apiError("INVALID_REQUEST", "previewUrl is required", 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Validate previewUrl is a valid HTTPS URL
|
|
45
|
+
let parsedPreviewUrl: URL;
|
|
46
|
+
try {
|
|
47
|
+
parsedPreviewUrl = new URL(body.previewUrl);
|
|
48
|
+
} catch {
|
|
49
|
+
return apiError("INVALID_REQUEST", "previewUrl must be a valid URL", 400);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (parsedPreviewUrl.protocol !== "https:") {
|
|
53
|
+
return apiError("INVALID_REQUEST", "previewUrl must use HTTPS", 400);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const source = getPublicOrigin(url, dineway?.config);
|
|
57
|
+
const ttl = 3600; // 1 hour
|
|
58
|
+
const exp = Math.floor(Date.now() / 1000) + ttl;
|
|
59
|
+
|
|
60
|
+
// HMAC-SHA256 sign: message = "source:exp"
|
|
61
|
+
const encoder = new TextEncoder();
|
|
62
|
+
const key = await crypto.subtle.importKey(
|
|
63
|
+
"raw",
|
|
64
|
+
encoder.encode(secret),
|
|
65
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
66
|
+
false,
|
|
67
|
+
["sign"],
|
|
68
|
+
);
|
|
69
|
+
const buffer = await crypto.subtle.sign("HMAC", key, encoder.encode(`${source}:${exp}`));
|
|
70
|
+
const sig = Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
71
|
+
|
|
72
|
+
const previewUrl = new URL(body.previewUrl);
|
|
73
|
+
previewUrl.searchParams.set("source", source);
|
|
74
|
+
previewUrl.searchParams.set("exp", String(exp));
|
|
75
|
+
previewUrl.searchParams.set("sig", sig);
|
|
76
|
+
|
|
77
|
+
return apiSuccess({ url: previewUrl.toString() });
|
|
78
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typegen endpoint - generates dineway-env.d.ts content
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/typegen - Generate types and return as JSON
|
|
5
|
+
* GET /_dineway/api/typegen - Return types as text (for preview/debugging)
|
|
6
|
+
*
|
|
7
|
+
* The caller (integration or CLI) is responsible for writing the file to disk.
|
|
8
|
+
* This endpoint only generates the content — it has no filesystem access, so it
|
|
9
|
+
* stays portable across runtimes where the project root or Node filesystem APIs
|
|
10
|
+
* are not available here.
|
|
11
|
+
*
|
|
12
|
+
* Dev-only endpoint - disabled in production.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { APIRoute } from "astro";
|
|
16
|
+
|
|
17
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
18
|
+
import type { SchemaRegistry } from "#schema/registry.js";
|
|
19
|
+
|
|
20
|
+
export const prerender = false;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Safely list collections, returning empty array if tables don't exist yet
|
|
24
|
+
*/
|
|
25
|
+
async function safeListCollections(registry: SchemaRegistry) {
|
|
26
|
+
try {
|
|
27
|
+
return await registry.listCollections();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// Handle missing tables for new sites that haven't run setup yet
|
|
30
|
+
if (error instanceof Error && error.message.includes("no such table")) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate types content and metadata from the current schema.
|
|
39
|
+
*/
|
|
40
|
+
async function generateTypes(registry: SchemaRegistry) {
|
|
41
|
+
const { generateTypesFile, generateSchemaHash } = await import("#schema/zod-generator.js");
|
|
42
|
+
|
|
43
|
+
const collections = await safeListCollections(registry);
|
|
44
|
+
const collectionsWithFields = await Promise.all(
|
|
45
|
+
collections.map(async (c) => {
|
|
46
|
+
const fields = await registry.listFields(c.id);
|
|
47
|
+
return { ...c, fields };
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const types = generateTypesFile(collectionsWithFields);
|
|
52
|
+
const hash: string = await generateSchemaHash(collectionsWithFields);
|
|
53
|
+
|
|
54
|
+
return { types, hash, collections: collections.length };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* GET - Return types as plain text (for preview/debugging)
|
|
59
|
+
*/
|
|
60
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
61
|
+
if (!import.meta.env.DEV) {
|
|
62
|
+
return apiError("FORBIDDEN", "Typegen is only available in development", 403);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { dineway } = locals;
|
|
66
|
+
|
|
67
|
+
if (!dineway?.db) {
|
|
68
|
+
return apiError("NOT_CONFIGURED", "Dineway is not configured", 500);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const { SchemaRegistry } = await import("#schema/registry.js");
|
|
73
|
+
const registry = new SchemaRegistry(dineway.db);
|
|
74
|
+
const { types } = await generateTypes(registry);
|
|
75
|
+
|
|
76
|
+
return new Response(types, {
|
|
77
|
+
status: 200,
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "text/typescript",
|
|
80
|
+
"Cache-Control": "private, no-store",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return handleError(error, "Typegen failed", "TYPEGEN_ERROR");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* POST - Generate types and return as JSON
|
|
90
|
+
*
|
|
91
|
+
* The caller writes the file to disk. Response shape:
|
|
92
|
+
* { types: string, hash: string, collections: number }
|
|
93
|
+
*/
|
|
94
|
+
export const POST: APIRoute = async ({ locals }) => {
|
|
95
|
+
if (!import.meta.env.DEV) {
|
|
96
|
+
return apiError("FORBIDDEN", "Typegen is only available in development", 403);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { dineway } = locals;
|
|
100
|
+
|
|
101
|
+
if (!dineway?.db) {
|
|
102
|
+
return apiError("NOT_CONFIGURED", "Dineway is not configured", 500);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const { SchemaRegistry } = await import("#schema/registry.js");
|
|
107
|
+
const registry = new SchemaRegistry(dineway.db);
|
|
108
|
+
const result = await generateTypes(registry);
|
|
109
|
+
|
|
110
|
+
return apiSuccess(result);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return handleError(error, "Typegen failed", "TYPEGEN_ERROR");
|
|
113
|
+
}
|
|
114
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /_dineway/.well-known/auth
|
|
3
|
+
*
|
|
4
|
+
* Auth discovery endpoint. Returns available auth mechanisms.
|
|
5
|
+
* Public, unauthenticated. Used by CLI to determine how to authenticate.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { getAuthMode } from "#auth/mode.js";
|
|
11
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
12
|
+
|
|
13
|
+
import { VERSION } from "../../../../version.js";
|
|
14
|
+
|
|
15
|
+
export const prerender = false;
|
|
16
|
+
|
|
17
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
18
|
+
const { dineway } = locals;
|
|
19
|
+
|
|
20
|
+
// Build discovery response
|
|
21
|
+
const config = dineway?.config;
|
|
22
|
+
const authMode = config ? getAuthMode(config) : null;
|
|
23
|
+
|
|
24
|
+
const isExternal = authMode?.type === "external";
|
|
25
|
+
|
|
26
|
+
// Try to read site name from DB options
|
|
27
|
+
let siteName = "Dineway";
|
|
28
|
+
if (dineway?.db) {
|
|
29
|
+
try {
|
|
30
|
+
const options = new OptionsRepository(dineway.db);
|
|
31
|
+
siteName = (await options.get<string>("dineway:site_title")) || "Dineway";
|
|
32
|
+
} catch {
|
|
33
|
+
// DB may not be initialized yet
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const response: Record<string, unknown> = {
|
|
38
|
+
instance: {
|
|
39
|
+
name: siteName,
|
|
40
|
+
version: VERSION,
|
|
41
|
+
},
|
|
42
|
+
auth: {
|
|
43
|
+
mode: isExternal ? "external" : "passkey",
|
|
44
|
+
...(isExternal && authMode.type === "external"
|
|
45
|
+
? { external_provider: authMode.entrypoint }
|
|
46
|
+
: {}),
|
|
47
|
+
methods: {
|
|
48
|
+
device_flow: !isExternal
|
|
49
|
+
? {
|
|
50
|
+
client_id: "dineway-cli",
|
|
51
|
+
device_authorization_endpoint: "/_dineway/api/oauth/device/code",
|
|
52
|
+
token_endpoint: "/_dineway/api/oauth/device/token",
|
|
53
|
+
}
|
|
54
|
+
: undefined,
|
|
55
|
+
authorization_code: !isExternal
|
|
56
|
+
? {
|
|
57
|
+
authorization_endpoint: "/_dineway/oauth/authorize",
|
|
58
|
+
token_endpoint: "/_dineway/api/oauth/token",
|
|
59
|
+
}
|
|
60
|
+
: undefined,
|
|
61
|
+
api_tokens: true,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return Response.json(response, {
|
|
67
|
+
headers: {
|
|
68
|
+
"Cache-Control": "no-store",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /.well-known/oauth-authorization-server/_dineway
|
|
3
|
+
*
|
|
4
|
+
* RFC 8414 Authorization Server Metadata. The issuer pathname (`/_dineway`)
|
|
5
|
+
* is appended after the well-known prefix so standards-compliant OAuth/MCP
|
|
6
|
+
* clients can discover the metadata from the issuer URL.
|
|
7
|
+
*
|
|
8
|
+
* Public, unauthenticated.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
|
|
13
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
14
|
+
import { ALL_VALID_SCOPES } from "#auth/api-tokens.js";
|
|
15
|
+
import { filterExperimentalSiteContextWorkflowScopes } from "#site-context/experimental-workflows.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
20
|
+
const origin = getPublicOrigin(url, locals.dineway?.config);
|
|
21
|
+
const issuer = `${origin}/_dineway`;
|
|
22
|
+
|
|
23
|
+
return Response.json(
|
|
24
|
+
{
|
|
25
|
+
issuer,
|
|
26
|
+
authorization_endpoint: `${origin}/_dineway/oauth/authorize`,
|
|
27
|
+
token_endpoint: `${origin}/_dineway/api/oauth/token`,
|
|
28
|
+
scopes_supported: filterExperimentalSiteContextWorkflowScopes(ALL_VALID_SCOPES),
|
|
29
|
+
response_types_supported: ["code"],
|
|
30
|
+
grant_types_supported: [
|
|
31
|
+
"authorization_code",
|
|
32
|
+
"refresh_token",
|
|
33
|
+
"urn:ietf:params:oauth:grant-type:device_code",
|
|
34
|
+
],
|
|
35
|
+
code_challenge_methods_supported: ["S256"],
|
|
36
|
+
registration_endpoint: `${origin}/_dineway/api/oauth/register`,
|
|
37
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
38
|
+
client_id_metadata_document_supported: true,
|
|
39
|
+
device_authorization_endpoint: `${origin}/_dineway/api/oauth/device/code`,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
headers: {
|
|
43
|
+
"Cache-Control": "public, max-age=3600",
|
|
44
|
+
"Access-Control-Allow-Origin": "*",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /.well-known/oauth-protected-resource
|
|
3
|
+
*
|
|
4
|
+
* RFC 9728 Protected Resource Metadata. Tells MCP clients where to find
|
|
5
|
+
* the authorization server. Injected at the site root (not under /_dineway/)
|
|
6
|
+
* because RFC 9728 requires it at the well-known URI of the resource's origin.
|
|
7
|
+
*
|
|
8
|
+
* Also serves as `/.well-known/oauth-protected-resource/_dineway/api/mcp`
|
|
9
|
+
* (path-scoped variant) when Astro's routing allows.
|
|
10
|
+
*
|
|
11
|
+
* Public, unauthenticated.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { APIRoute } from "astro";
|
|
15
|
+
|
|
16
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
17
|
+
import { ALL_VALID_SCOPES } from "#auth/api-tokens.js";
|
|
18
|
+
import { filterExperimentalSiteContextWorkflowScopes } from "#site-context/experimental-workflows.js";
|
|
19
|
+
|
|
20
|
+
export const prerender = false;
|
|
21
|
+
|
|
22
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
23
|
+
const origin = getPublicOrigin(url, locals.dineway?.config);
|
|
24
|
+
|
|
25
|
+
return Response.json(
|
|
26
|
+
{
|
|
27
|
+
resource: `${origin}/_dineway/api/mcp`,
|
|
28
|
+
authorization_servers: [`${origin}/_dineway`],
|
|
29
|
+
scopes_supported: filterExperimentalSiteContextWorkflowScopes(ALL_VALID_SCOPES),
|
|
30
|
+
bearer_methods_supported: ["header"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
headers: {
|
|
34
|
+
"Cache-Control": "public, max-age=3600",
|
|
35
|
+
"Access-Control-Allow-Origin": "*",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reorder widgets endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/widget-areas/:name/reorder
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import { requirePerm } from "#api/authorize.js";
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
import {
|
|
13
|
+
ensureWorkflowHitlRouteRequest,
|
|
14
|
+
hitlRequiredRouteError,
|
|
15
|
+
resolveHitlRouteActor,
|
|
16
|
+
} from "#api/hitl-route-helpers.js";
|
|
17
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
18
|
+
import { reorderWidgetsBody } from "#api/schemas.js";
|
|
19
|
+
import {
|
|
20
|
+
logWidgetActivity,
|
|
21
|
+
RiskPolicyEvaluator,
|
|
22
|
+
widgetApiRouteSource,
|
|
23
|
+
WidgetHitlPayloadBuilder,
|
|
24
|
+
} from "#site-context/index.js";
|
|
25
|
+
|
|
26
|
+
export const prerender = false;
|
|
27
|
+
|
|
28
|
+
const reorderWidgetsHitlBody = reorderWidgetsBody.extend({
|
|
29
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
33
|
+
const { dineway, user } = locals;
|
|
34
|
+
const db = dineway.db;
|
|
35
|
+
const { name } = params;
|
|
36
|
+
|
|
37
|
+
const denied = requirePerm(user, "widgets:manage");
|
|
38
|
+
if (denied) return denied;
|
|
39
|
+
|
|
40
|
+
if (!name) {
|
|
41
|
+
return apiError("VALIDATION_ERROR", "name is required", 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
46
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
47
|
+
|
|
48
|
+
if (!area) {
|
|
49
|
+
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const body = await parseBody(request, reorderWidgetsHitlBody);
|
|
53
|
+
if (isParseError(body)) return body;
|
|
54
|
+
const { hitlRequestId, widgetIds } = body;
|
|
55
|
+
|
|
56
|
+
// Verify all widget IDs belong to this area
|
|
57
|
+
const existingIds = new Set(area.widgets.map((widget) => widget.id));
|
|
58
|
+
for (const id of widgetIds) {
|
|
59
|
+
if (!existingIds.has(id)) {
|
|
60
|
+
return apiError("VALIDATION_ERROR", `Widget "${id}" not found in area "${name}"`, 400);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const actor = resolveHitlRouteActor(locals);
|
|
65
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
66
|
+
db,
|
|
67
|
+
handlers: dineway,
|
|
68
|
+
});
|
|
69
|
+
let approvedHitlRequestId: string | null = null;
|
|
70
|
+
|
|
71
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
72
|
+
const action = await payloadBuilder.buildReorderWidgetsRequest({
|
|
73
|
+
area,
|
|
74
|
+
widgetIds,
|
|
75
|
+
});
|
|
76
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
77
|
+
actor: actor.identity,
|
|
78
|
+
hitlRequestId,
|
|
79
|
+
action,
|
|
80
|
+
});
|
|
81
|
+
if (!decision.allowed) {
|
|
82
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
83
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
84
|
+
}
|
|
85
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Update sort_order for each widget
|
|
89
|
+
await Promise.all(
|
|
90
|
+
widgetIds.map((id, index) =>
|
|
91
|
+
db
|
|
92
|
+
.updateTable("_dineway_widgets")
|
|
93
|
+
.set({ sort_order: index })
|
|
94
|
+
.where("id", "=", id)
|
|
95
|
+
.execute(),
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await logWidgetActivity(db, locals, {
|
|
100
|
+
action: "reordered",
|
|
101
|
+
areaId: area.id,
|
|
102
|
+
areaName: area.name,
|
|
103
|
+
...widgetApiRouteSource("reordered"),
|
|
104
|
+
detail: {
|
|
105
|
+
widgetIds,
|
|
106
|
+
hitlRequestId: approvedHitlRequestId,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return apiSuccess({ success: true });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return handleError(error, "Failed to reorder widgets", "WIDGET_REORDER_ERROR");
|
|
113
|
+
}
|
|
114
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single widget endpoints
|
|
3
|
+
*
|
|
4
|
+
* PUT /_dineway/api/widget-areas/:name/widgets/:id - Update widget
|
|
5
|
+
* DELETE /_dineway/api/widget-areas/:name/widgets/:id - Delete widget
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { requirePerm } from "#api/authorize.js";
|
|
12
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
13
|
+
import {
|
|
14
|
+
ensureWorkflowHitlRouteRequest,
|
|
15
|
+
hitlRequiredRouteError,
|
|
16
|
+
resolveHitlRouteActor,
|
|
17
|
+
} from "#api/hitl-route-helpers.js";
|
|
18
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
19
|
+
import { updateWidgetBody } from "#api/schemas.js";
|
|
20
|
+
import {
|
|
21
|
+
activityChangedKeys,
|
|
22
|
+
logWidgetActivity,
|
|
23
|
+
RiskPolicyEvaluator,
|
|
24
|
+
widgetApiRouteSource,
|
|
25
|
+
WidgetHitlPayloadBuilder,
|
|
26
|
+
} from "#site-context/index.js";
|
|
27
|
+
|
|
28
|
+
export const prerender = false;
|
|
29
|
+
|
|
30
|
+
const updateWidgetHitlBody = updateWidgetBody.extend({
|
|
31
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const deleteWidgetQuery = z.object({
|
|
35
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
39
|
+
const { dineway, user } = locals;
|
|
40
|
+
const db = dineway.db;
|
|
41
|
+
const { name, id } = params;
|
|
42
|
+
|
|
43
|
+
const denied = requirePerm(user, "widgets:manage");
|
|
44
|
+
if (denied) return denied;
|
|
45
|
+
|
|
46
|
+
if (!name || !id) {
|
|
47
|
+
return apiError("VALIDATION_ERROR", "name and id are required", 400);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
52
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
53
|
+
|
|
54
|
+
if (!area) {
|
|
55
|
+
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const existingWidget = area.widgets.find((widget) => widget.id === id);
|
|
59
|
+
|
|
60
|
+
if (!existingWidget) {
|
|
61
|
+
return apiError("NOT_FOUND", `Widget "${id}" not found in area "${name}"`, 404);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const body = await parseBody(request, updateWidgetHitlBody);
|
|
65
|
+
if (isParseError(body)) return body;
|
|
66
|
+
const { hitlRequestId, ...widgetInput } = body;
|
|
67
|
+
|
|
68
|
+
const actor = resolveHitlRouteActor(locals);
|
|
69
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
70
|
+
db,
|
|
71
|
+
handlers: dineway,
|
|
72
|
+
});
|
|
73
|
+
let approvedHitlRequestId: string | null = null;
|
|
74
|
+
|
|
75
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
76
|
+
const action = await payloadBuilder.buildUpdateWidgetRequest({
|
|
77
|
+
area,
|
|
78
|
+
currentWidget: existingWidget,
|
|
79
|
+
...widgetInput,
|
|
80
|
+
});
|
|
81
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
82
|
+
actor: actor.identity,
|
|
83
|
+
hitlRequestId,
|
|
84
|
+
action,
|
|
85
|
+
});
|
|
86
|
+
if (!decision.allowed) {
|
|
87
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
88
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
89
|
+
}
|
|
90
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build update object (only update provided fields)
|
|
94
|
+
const updates = buildWidgetUpdates(widgetInput);
|
|
95
|
+
|
|
96
|
+
if (Object.keys(updates).length === 0) {
|
|
97
|
+
return apiError("VALIDATION_ERROR", "No fields to update", 400);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await db.updateTable("_dineway_widgets").set(updates).where("id", "=", id).execute();
|
|
101
|
+
|
|
102
|
+
const widget = await db
|
|
103
|
+
.selectFrom("_dineway_widgets")
|
|
104
|
+
.selectAll()
|
|
105
|
+
.where("id", "=", id)
|
|
106
|
+
.executeTakeFirstOrThrow();
|
|
107
|
+
|
|
108
|
+
await logWidgetActivity(db, locals, {
|
|
109
|
+
action: "updated",
|
|
110
|
+
areaId: area.id,
|
|
111
|
+
areaName: area.name,
|
|
112
|
+
widgetId: widget.id,
|
|
113
|
+
...widgetApiRouteSource("updated"),
|
|
114
|
+
detail: {
|
|
115
|
+
changedKeys: activityChangedKeys(widgetInput as Record<string, unknown>),
|
|
116
|
+
type: widget.type,
|
|
117
|
+
hitlRequestId: approvedHitlRequestId,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return apiSuccess(widget);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return handleError(error, "Failed to update widget", "WIDGET_UPDATE_ERROR");
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
128
|
+
const { dineway, user } = locals;
|
|
129
|
+
const db = dineway.db;
|
|
130
|
+
const { name, id } = params;
|
|
131
|
+
|
|
132
|
+
const denied = requirePerm(user, "widgets:manage");
|
|
133
|
+
if (denied) return denied;
|
|
134
|
+
|
|
135
|
+
if (!name || !id) {
|
|
136
|
+
return apiError("VALIDATION_ERROR", "name and id are required", 400);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const query = parseQuery(new URL(request.url), deleteWidgetQuery);
|
|
140
|
+
if (isParseError(query)) return query;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
144
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
145
|
+
|
|
146
|
+
if (!area) {
|
|
147
|
+
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const existingWidget = area.widgets.find((widget) => widget.id === id);
|
|
151
|
+
|
|
152
|
+
if (!existingWidget) {
|
|
153
|
+
return apiError("NOT_FOUND", `Widget "${id}" not found in area "${name}"`, 404);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const actor = resolveHitlRouteActor(locals);
|
|
157
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
158
|
+
db,
|
|
159
|
+
handlers: dineway,
|
|
160
|
+
});
|
|
161
|
+
let approvedHitlRequestId: string | null = null;
|
|
162
|
+
|
|
163
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
164
|
+
const action = await payloadBuilder.buildDeleteWidgetRequest({
|
|
165
|
+
area,
|
|
166
|
+
currentWidget: existingWidget,
|
|
167
|
+
});
|
|
168
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
169
|
+
actor: actor.identity,
|
|
170
|
+
hitlRequestId: query.hitlRequestId,
|
|
171
|
+
action,
|
|
172
|
+
});
|
|
173
|
+
if (!decision.allowed) {
|
|
174
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
175
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
176
|
+
}
|
|
177
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await db.deleteFrom("_dineway_widgets").where("id", "=", id).execute();
|
|
181
|
+
|
|
182
|
+
await logWidgetActivity(db, locals, {
|
|
183
|
+
action: "deleted",
|
|
184
|
+
areaId: area.id,
|
|
185
|
+
areaName: area.name,
|
|
186
|
+
widgetId: existingWidget.id,
|
|
187
|
+
...widgetApiRouteSource("deleted"),
|
|
188
|
+
detail: {
|
|
189
|
+
type: existingWidget.type,
|
|
190
|
+
sortOrder: existingWidget.sortOrder,
|
|
191
|
+
hitlRequestId: approvedHitlRequestId,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return apiSuccess({ deleted: true });
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return handleError(error, "Failed to delete widget", "WIDGET_DELETE_ERROR");
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
function buildWidgetUpdates(body: z.infer<typeof updateWidgetBody>): Record<string, unknown> {
|
|
202
|
+
const updates: Record<string, unknown> = {};
|
|
203
|
+
if (body.title !== undefined) updates.title = body.title || null;
|
|
204
|
+
if (body.type !== undefined) updates.type = body.type;
|
|
205
|
+
if (body.content !== undefined)
|
|
206
|
+
updates.content = body.content ? JSON.stringify(body.content) : null;
|
|
207
|
+
if (body.menuName !== undefined) updates.menu_name = body.menuName || null;
|
|
208
|
+
if (body.componentId !== undefined) updates.component_id = body.componentId || null;
|
|
209
|
+
if (body.componentProps !== undefined) {
|
|
210
|
+
updates.component_props = body.componentProps ? JSON.stringify(body.componentProps) : null;
|
|
211
|
+
}
|
|
212
|
+
return updates;
|
|
213
|
+
}
|