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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widgets CRUD endpoints
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/widget-areas/:name/widgets - Add widget
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
import { ulid } from "ulidx";
|
|
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 } from "#api/parse.js";
|
|
19
|
+
import { createWidgetBody } from "#api/schemas.js";
|
|
20
|
+
import {
|
|
21
|
+
logWidgetActivity,
|
|
22
|
+
RiskPolicyEvaluator,
|
|
23
|
+
widgetApiRouteSource,
|
|
24
|
+
WidgetHitlPayloadBuilder,
|
|
25
|
+
} from "#site-context/index.js";
|
|
26
|
+
|
|
27
|
+
export const prerender = false;
|
|
28
|
+
|
|
29
|
+
const createWidgetHitlBody = createWidgetBody.extend({
|
|
30
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
34
|
+
const { dineway, user } = locals;
|
|
35
|
+
const db = dineway.db;
|
|
36
|
+
const { name } = params;
|
|
37
|
+
|
|
38
|
+
const denied = requirePerm(user, "widgets:manage");
|
|
39
|
+
if (denied) return denied;
|
|
40
|
+
|
|
41
|
+
if (!name) {
|
|
42
|
+
return apiError("VALIDATION_ERROR", "name is required", 400);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
47
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
48
|
+
|
|
49
|
+
if (!area) {
|
|
50
|
+
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const body = await parseBody(request, createWidgetHitlBody);
|
|
54
|
+
if (isParseError(body)) return body;
|
|
55
|
+
const { hitlRequestId, ...widgetInput } = body;
|
|
56
|
+
|
|
57
|
+
const actor = resolveHitlRouteActor(locals);
|
|
58
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
59
|
+
db,
|
|
60
|
+
handlers: dineway,
|
|
61
|
+
});
|
|
62
|
+
let approvedHitlRequestId: string | null = null;
|
|
63
|
+
|
|
64
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
65
|
+
const action = await payloadBuilder.buildCreateWidgetRequest({
|
|
66
|
+
area,
|
|
67
|
+
...widgetInput,
|
|
68
|
+
});
|
|
69
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
70
|
+
actor: actor.identity,
|
|
71
|
+
hitlRequestId,
|
|
72
|
+
action,
|
|
73
|
+
});
|
|
74
|
+
if (!decision.allowed) {
|
|
75
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
76
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
77
|
+
}
|
|
78
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sortOrder = (area.widgets.at(-1)?.sortOrder ?? -1) + 1;
|
|
82
|
+
|
|
83
|
+
// Prepare values
|
|
84
|
+
const id = ulid();
|
|
85
|
+
await db
|
|
86
|
+
.insertInto("_dineway_widgets")
|
|
87
|
+
.values({
|
|
88
|
+
id,
|
|
89
|
+
area_id: area.id,
|
|
90
|
+
sort_order: sortOrder,
|
|
91
|
+
type: widgetInput.type,
|
|
92
|
+
title: widgetInput.title ?? null,
|
|
93
|
+
content: widgetInput.content ? JSON.stringify(widgetInput.content) : null,
|
|
94
|
+
menu_name: widgetInput.menuName ?? null,
|
|
95
|
+
component_id: widgetInput.componentId ?? null,
|
|
96
|
+
component_props: widgetInput.componentProps
|
|
97
|
+
? JSON.stringify(widgetInput.componentProps)
|
|
98
|
+
: null,
|
|
99
|
+
})
|
|
100
|
+
.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: "created",
|
|
110
|
+
areaId: area.id,
|
|
111
|
+
areaName: area.name,
|
|
112
|
+
widgetId: widget.id,
|
|
113
|
+
...widgetApiRouteSource("created"),
|
|
114
|
+
detail: {
|
|
115
|
+
type: widget.type,
|
|
116
|
+
sortOrder: widget.sort_order,
|
|
117
|
+
title: widget.title,
|
|
118
|
+
hitlRequestId: approvedHitlRequestId,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return apiSuccess(widget, 201);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return handleError(error, "Failed to create widget", "WIDGET_CREATE_ERROR");
|
|
125
|
+
}
|
|
126
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget area by name endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/widget-areas/:name - Get area with widgets
|
|
5
|
+
* DELETE /_dineway/api/widget-areas/:name - Delete area
|
|
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, parseQuery } from "#api/parse.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 deleteWidgetAreaQuery = z.object({
|
|
29
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
33
|
+
const { dineway, user } = locals;
|
|
34
|
+
const db = dineway.db;
|
|
35
|
+
const { name } = params;
|
|
36
|
+
|
|
37
|
+
const denied = requirePerm(user, "widgets:read");
|
|
38
|
+
if (denied) return denied;
|
|
39
|
+
|
|
40
|
+
if (!name) {
|
|
41
|
+
return apiError("VALIDATION_ERROR", "name is required", 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Get the area
|
|
46
|
+
const area = await db
|
|
47
|
+
.selectFrom("_dineway_widget_areas")
|
|
48
|
+
.selectAll()
|
|
49
|
+
.where("name", "=", name)
|
|
50
|
+
.executeTakeFirst();
|
|
51
|
+
|
|
52
|
+
if (!area) {
|
|
53
|
+
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get widgets for this area
|
|
57
|
+
const widgets = await db
|
|
58
|
+
.selectFrom("_dineway_widgets")
|
|
59
|
+
.selectAll()
|
|
60
|
+
.where("area_id", "=", area.id)
|
|
61
|
+
.orderBy("sort_order", "asc")
|
|
62
|
+
.execute();
|
|
63
|
+
|
|
64
|
+
return apiSuccess({
|
|
65
|
+
...area,
|
|
66
|
+
widgets,
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return handleError(error, "Failed to fetch widget area", "WIDGET_AREA_GET_ERROR");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
74
|
+
const { dineway, user } = locals;
|
|
75
|
+
const db = dineway.db;
|
|
76
|
+
const { name } = params;
|
|
77
|
+
|
|
78
|
+
const denied = requirePerm(user, "widgets:manage");
|
|
79
|
+
if (denied) return denied;
|
|
80
|
+
|
|
81
|
+
if (!name) {
|
|
82
|
+
return apiError("VALIDATION_ERROR", "name is required", 400);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const query = parseQuery(new URL(request.url), deleteWidgetAreaQuery);
|
|
86
|
+
if (isParseError(query)) return query;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
90
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
91
|
+
|
|
92
|
+
if (!area) {
|
|
93
|
+
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const actor = resolveHitlRouteActor(locals);
|
|
97
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
98
|
+
db,
|
|
99
|
+
handlers: dineway,
|
|
100
|
+
});
|
|
101
|
+
let approvedHitlRequestId: string | null = null;
|
|
102
|
+
|
|
103
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
104
|
+
const action = await payloadBuilder.buildDeleteWidgetAreaRequest({ area });
|
|
105
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
106
|
+
actor: actor.identity,
|
|
107
|
+
hitlRequestId: query.hitlRequestId,
|
|
108
|
+
action,
|
|
109
|
+
});
|
|
110
|
+
if (!decision.allowed) {
|
|
111
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
112
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
113
|
+
}
|
|
114
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Delete area (widgets cascade)
|
|
118
|
+
await db.deleteFrom("_dineway_widget_areas").where("id", "=", area.id).execute();
|
|
119
|
+
|
|
120
|
+
await logWidgetActivity(db, locals, {
|
|
121
|
+
action: "area_deleted",
|
|
122
|
+
areaId: area.id,
|
|
123
|
+
areaName: area.name,
|
|
124
|
+
...widgetApiRouteSource("area_deleted"),
|
|
125
|
+
detail: {
|
|
126
|
+
widgetCount: area.widgets.length,
|
|
127
|
+
hitlRequestId: approvedHitlRequestId,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return apiSuccess({ deleted: true });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return handleError(error, "Failed to delete widget area", "WIDGET_AREA_DELETE_ERROR");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget areas list and create endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/widget-areas - List all widget areas
|
|
5
|
+
* POST /_dineway/api/widget-areas - Create widget area
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
import { ulid } from "ulidx";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import { requirePerm } from "#api/authorize.js";
|
|
13
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
14
|
+
import {
|
|
15
|
+
ensureWorkflowHitlRouteRequest,
|
|
16
|
+
hitlRequiredRouteError,
|
|
17
|
+
resolveHitlRouteActor,
|
|
18
|
+
} from "#api/hitl-route-helpers.js";
|
|
19
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
20
|
+
import { createWidgetAreaBody } from "#api/schemas.js";
|
|
21
|
+
import {
|
|
22
|
+
logWidgetActivity,
|
|
23
|
+
RiskPolicyEvaluator,
|
|
24
|
+
widgetApiRouteSource,
|
|
25
|
+
WidgetHitlPayloadBuilder,
|
|
26
|
+
} from "#site-context/index.js";
|
|
27
|
+
|
|
28
|
+
export const prerender = false;
|
|
29
|
+
|
|
30
|
+
const createWidgetAreaHitlBody = createWidgetAreaBody.extend({
|
|
31
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
35
|
+
const { dineway, user } = locals;
|
|
36
|
+
const db = dineway.db;
|
|
37
|
+
|
|
38
|
+
const denied = requirePerm(user, "widgets:read");
|
|
39
|
+
if (denied) return denied;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const areas = await db
|
|
43
|
+
.selectFrom("_dineway_widget_areas")
|
|
44
|
+
.selectAll()
|
|
45
|
+
.orderBy("name", "asc")
|
|
46
|
+
.execute();
|
|
47
|
+
|
|
48
|
+
// Get widgets for each area (needed for drag-and-drop reordering in admin UI)
|
|
49
|
+
const areasWithWidgets = await Promise.all(
|
|
50
|
+
areas.map(async (area) => {
|
|
51
|
+
const widgets = await db
|
|
52
|
+
.selectFrom("_dineway_widgets")
|
|
53
|
+
.selectAll()
|
|
54
|
+
.where("area_id", "=", area.id)
|
|
55
|
+
.orderBy("sort_order", "asc")
|
|
56
|
+
.execute();
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...area,
|
|
60
|
+
widgets,
|
|
61
|
+
widgetCount: widgets.length,
|
|
62
|
+
};
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return apiSuccess({ items: areasWithWidgets });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return handleError(error, "Failed to fetch widget areas", "WIDGET_AREA_LIST_ERROR");
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
73
|
+
const { dineway, user } = locals;
|
|
74
|
+
const db = dineway.db;
|
|
75
|
+
|
|
76
|
+
const denied = requirePerm(user, "widgets:manage");
|
|
77
|
+
if (denied) return denied;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const body = await parseBody(request, createWidgetAreaHitlBody);
|
|
81
|
+
if (isParseError(body)) return body;
|
|
82
|
+
const { hitlRequestId, ...areaInput } = body;
|
|
83
|
+
|
|
84
|
+
// Check if area name already exists
|
|
85
|
+
const existing = await db
|
|
86
|
+
.selectFrom("_dineway_widget_areas")
|
|
87
|
+
.select("id")
|
|
88
|
+
.where("name", "=", areaInput.name)
|
|
89
|
+
.executeTakeFirst();
|
|
90
|
+
|
|
91
|
+
if (existing) {
|
|
92
|
+
return apiError("CONFLICT", `Widget area with name "${areaInput.name}" already exists`, 409);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const actor = resolveHitlRouteActor(locals);
|
|
96
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
97
|
+
db,
|
|
98
|
+
handlers: dineway,
|
|
99
|
+
});
|
|
100
|
+
let approvedHitlRequestId: string | null = null;
|
|
101
|
+
|
|
102
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
103
|
+
const action = await new WidgetHitlPayloadBuilder(db).buildCreateWidgetAreaRequest(areaInput);
|
|
104
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
105
|
+
actor: actor.identity,
|
|
106
|
+
hitlRequestId,
|
|
107
|
+
action,
|
|
108
|
+
});
|
|
109
|
+
if (!decision.allowed) {
|
|
110
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
111
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
112
|
+
}
|
|
113
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const id = ulid();
|
|
117
|
+
await db
|
|
118
|
+
.insertInto("_dineway_widget_areas")
|
|
119
|
+
.values({
|
|
120
|
+
id,
|
|
121
|
+
name: areaInput.name,
|
|
122
|
+
label: areaInput.label,
|
|
123
|
+
description: areaInput.description ?? null,
|
|
124
|
+
})
|
|
125
|
+
.execute();
|
|
126
|
+
|
|
127
|
+
const area = await db
|
|
128
|
+
.selectFrom("_dineway_widget_areas")
|
|
129
|
+
.selectAll()
|
|
130
|
+
.where("id", "=", id)
|
|
131
|
+
.executeTakeFirstOrThrow();
|
|
132
|
+
|
|
133
|
+
await logWidgetActivity(db, locals, {
|
|
134
|
+
action: "area_created",
|
|
135
|
+
areaId: area.id,
|
|
136
|
+
areaName: area.name,
|
|
137
|
+
...widgetApiRouteSource("area_created"),
|
|
138
|
+
detail: {
|
|
139
|
+
label: area.label,
|
|
140
|
+
description: area.description,
|
|
141
|
+
hitlRequestId: approvedHitlRequestId,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return apiSuccess(area, 201);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return handleError(error, "Failed to create widget area", "WIDGET_AREA_CREATE_ERROR");
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget components registry endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/widget-components - List available widget components
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { apiSuccess, handleError } from "#api/error.js";
|
|
10
|
+
import { getWidgetComponents } from "#widgets/components.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
export const GET: APIRoute = async () => {
|
|
15
|
+
try {
|
|
16
|
+
const components = getWidgetComponents();
|
|
17
|
+
|
|
18
|
+
return apiSuccess({ items: components });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return handleError(error, "Failed to fetch widget components", "WIDGET_COMPONENTS_ERROR");
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robots.txt endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /robots.txt - Serves robots.txt with sitemap reference
|
|
5
|
+
*
|
|
6
|
+
* If a custom robots.txt is configured in SEO settings, that is returned.
|
|
7
|
+
* Otherwise generates a default that allows all crawlers and references
|
|
8
|
+
* the sitemap.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
|
|
13
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
14
|
+
import { getSiteSettingsWithDb } from "#settings/index.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
19
|
+
|
|
20
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
21
|
+
const { dineway } = locals;
|
|
22
|
+
|
|
23
|
+
if (!dineway?.db) {
|
|
24
|
+
// Return a permissive default if CMS isn't initialized
|
|
25
|
+
return new Response("User-agent: *\nAllow: /\n", {
|
|
26
|
+
status: 200,
|
|
27
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const settings = await getSiteSettingsWithDb(dineway.db);
|
|
33
|
+
const siteUrl = (settings.url || getPublicOrigin(url, dineway?.config)).replace(
|
|
34
|
+
TRAILING_SLASH_RE,
|
|
35
|
+
"",
|
|
36
|
+
);
|
|
37
|
+
const sitemapUrl = `${siteUrl}/sitemap.xml`;
|
|
38
|
+
|
|
39
|
+
// Use custom robots.txt if configured
|
|
40
|
+
if (settings.seo?.robotsTxt) {
|
|
41
|
+
// Append sitemap directive if not already present
|
|
42
|
+
let content = settings.seo.robotsTxt;
|
|
43
|
+
if (!content.toLowerCase().includes("sitemap:")) {
|
|
44
|
+
content = `${content.trimEnd()}\n\nSitemap: ${sitemapUrl}\n`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return new Response(content, {
|
|
48
|
+
status: 200,
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
51
|
+
"Cache-Control": "public, max-age=86400",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Generate default robots.txt
|
|
57
|
+
const defaultRobots = [
|
|
58
|
+
"User-agent: *",
|
|
59
|
+
"Allow: /",
|
|
60
|
+
"",
|
|
61
|
+
"# Disallow admin and API routes",
|
|
62
|
+
"Disallow: /_dineway/",
|
|
63
|
+
"",
|
|
64
|
+
`Sitemap: ${sitemapUrl}`,
|
|
65
|
+
"",
|
|
66
|
+
].join("\n");
|
|
67
|
+
|
|
68
|
+
return new Response(defaultRobots, {
|
|
69
|
+
status: 200,
|
|
70
|
+
headers: {
|
|
71
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
72
|
+
"Cache-Control": "public, max-age=86400",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
return new Response("User-agent: *\nAllow: /\n", {
|
|
77
|
+
status: 200,
|
|
78
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-collection sitemap endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /sitemap-{collection}.xml - Sitemap for a single content collection.
|
|
5
|
+
*
|
|
6
|
+
* Uses the collection's url_pattern to build URLs. Falls back to
|
|
7
|
+
* /{collection}/{slug} when no pattern is configured.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
|
|
12
|
+
import { handleSitemapData } from "#api/handlers/seo.js";
|
|
13
|
+
import { getSiteSettingsWithDb } from "#settings/index.js";
|
|
14
|
+
|
|
15
|
+
export const prerender = false;
|
|
16
|
+
|
|
17
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
18
|
+
const AMP_RE = /&/g;
|
|
19
|
+
const LT_RE = /</g;
|
|
20
|
+
const GT_RE = />/g;
|
|
21
|
+
const QUOT_RE = /"/g;
|
|
22
|
+
const APOS_RE = /'/g;
|
|
23
|
+
const SLUG_PLACEHOLDER = "{slug}";
|
|
24
|
+
const ID_PLACEHOLDER = "{id}";
|
|
25
|
+
|
|
26
|
+
export const GET: APIRoute = async ({ params, locals, url }) => {
|
|
27
|
+
const { dineway } = locals;
|
|
28
|
+
const collectionSlug = params.collection;
|
|
29
|
+
|
|
30
|
+
if (!dineway?.db || !collectionSlug) {
|
|
31
|
+
return new Response("<!-- Dineway is not configured -->", {
|
|
32
|
+
status: 500,
|
|
33
|
+
headers: { "Content-Type": "application/xml" },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const settings = await getSiteSettingsWithDb(dineway.db);
|
|
39
|
+
const siteUrl = (settings.url || url.origin).replace(TRAILING_SLASH_RE, "");
|
|
40
|
+
|
|
41
|
+
const result = await handleSitemapData(dineway.db, collectionSlug);
|
|
42
|
+
|
|
43
|
+
if (!result.success || !result.data) {
|
|
44
|
+
return new Response("<!-- Failed to generate sitemap -->", {
|
|
45
|
+
status: 500,
|
|
46
|
+
headers: { "Content-Type": "application/xml" },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const col = result.data.collections[0];
|
|
51
|
+
if (!col) {
|
|
52
|
+
return new Response("<!-- Collection not found or empty -->", {
|
|
53
|
+
status: 404,
|
|
54
|
+
headers: { "Content-Type": "application/xml" },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const lines: string[] = [
|
|
59
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
60
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (const entry of col.entries) {
|
|
64
|
+
const slug = entry.slug || entry.id;
|
|
65
|
+
const path = col.urlPattern
|
|
66
|
+
? col.urlPattern
|
|
67
|
+
.replace(SLUG_PLACEHOLDER, encodeURIComponent(slug))
|
|
68
|
+
.replace(ID_PLACEHOLDER, encodeURIComponent(entry.id))
|
|
69
|
+
: `/${encodeURIComponent(col.collection)}/${encodeURIComponent(slug)}`;
|
|
70
|
+
|
|
71
|
+
const loc = `${siteUrl}${path}`;
|
|
72
|
+
|
|
73
|
+
lines.push(" <url>");
|
|
74
|
+
lines.push(` <loc>${escapeXml(loc)}</loc>`);
|
|
75
|
+
lines.push(` <lastmod>${escapeXml(entry.updatedAt)}</lastmod>`);
|
|
76
|
+
lines.push(" </url>");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
lines.push("</urlset>");
|
|
80
|
+
|
|
81
|
+
return new Response(lines.join("\n"), {
|
|
82
|
+
status: 200,
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "application/xml; charset=utf-8",
|
|
85
|
+
"Cache-Control": "public, max-age=3600",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
return new Response("<!-- Internal error generating sitemap -->", {
|
|
90
|
+
status: 500,
|
|
91
|
+
headers: { "Content-Type": "application/xml" },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Escape special XML characters in a string */
|
|
97
|
+
function escapeXml(str: string): string {
|
|
98
|
+
return str
|
|
99
|
+
.replace(AMP_RE, "&")
|
|
100
|
+
.replace(LT_RE, "<")
|
|
101
|
+
.replace(GT_RE, ">")
|
|
102
|
+
.replace(QUOT_RE, """)
|
|
103
|
+
.replace(APOS_RE, "'");
|
|
104
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitemap index endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /sitemap.xml - Sitemap index listing one sitemap per collection.
|
|
5
|
+
*
|
|
6
|
+
* Each collection with published, indexable content gets its own
|
|
7
|
+
* child sitemap at /sitemap-{collection}.xml. The index includes
|
|
8
|
+
* a <lastmod> per child derived from the most recently updated entry.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
|
|
13
|
+
import { handleSitemapData } from "#api/handlers/seo.js";
|
|
14
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
15
|
+
import { getSiteSettingsWithDb } from "#settings/index.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
20
|
+
const AMP_RE = /&/g;
|
|
21
|
+
const LT_RE = /</g;
|
|
22
|
+
const GT_RE = />/g;
|
|
23
|
+
const QUOT_RE = /"/g;
|
|
24
|
+
const APOS_RE = /'/g;
|
|
25
|
+
|
|
26
|
+
export const GET: APIRoute = async ({ locals, url }) => {
|
|
27
|
+
const { dineway } = locals;
|
|
28
|
+
|
|
29
|
+
if (!dineway?.db) {
|
|
30
|
+
return new Response("<!-- Dineway is not configured -->", {
|
|
31
|
+
status: 500,
|
|
32
|
+
headers: { "Content-Type": "application/xml" },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const settings = await getSiteSettingsWithDb(dineway.db);
|
|
38
|
+
const siteUrl = (settings.url || getPublicOrigin(url, dineway?.config)).replace(
|
|
39
|
+
TRAILING_SLASH_RE,
|
|
40
|
+
"",
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const result = await handleSitemapData(dineway.db);
|
|
44
|
+
|
|
45
|
+
if (!result.success || !result.data) {
|
|
46
|
+
return new Response("<!-- Failed to generate sitemap -->", {
|
|
47
|
+
status: 500,
|
|
48
|
+
headers: { "Content-Type": "application/xml" },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { collections } = result.data;
|
|
53
|
+
|
|
54
|
+
const lines: string[] = [
|
|
55
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
56
|
+
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const col of collections) {
|
|
60
|
+
const loc = `${siteUrl}/sitemap-${encodeURIComponent(col.collection)}.xml`;
|
|
61
|
+
lines.push(" <sitemap>");
|
|
62
|
+
lines.push(` <loc>${escapeXml(loc)}</loc>`);
|
|
63
|
+
lines.push(` <lastmod>${escapeXml(col.lastmod)}</lastmod>`);
|
|
64
|
+
lines.push(" </sitemap>");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
lines.push("</sitemapindex>");
|
|
68
|
+
|
|
69
|
+
return new Response(lines.join("\n"), {
|
|
70
|
+
status: 200,
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/xml; charset=utf-8",
|
|
73
|
+
"Cache-Control": "public, max-age=3600",
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
return new Response("<!-- Internal error generating sitemap -->", {
|
|
78
|
+
status: 500,
|
|
79
|
+
headers: { "Content-Type": "application/xml" },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** Escape special XML characters in a string */
|
|
85
|
+
function escapeXml(str: string): string {
|
|
86
|
+
return str
|
|
87
|
+
.replace(AMP_RE, "&")
|
|
88
|
+
.replace(LT_RE, "<")
|
|
89
|
+
.replace(GT_RE, ">")
|
|
90
|
+
.replace(QUOT_RE, """)
|
|
91
|
+
.replace(APOS_RE, "'");
|
|
92
|
+
}
|