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,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress import prepare endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/import/wordpress/prepare
|
|
5
|
+
*
|
|
6
|
+
* Creates collections and fields needed for import.
|
|
7
|
+
* This is called after analyze, before execute.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { requirePerm } from "#api/authorize.js";
|
|
14
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
|
+
import {
|
|
16
|
+
ensureWorkflowHitlRouteRequest,
|
|
17
|
+
hitlRequiredRouteError,
|
|
18
|
+
resolveHitlRouteActor,
|
|
19
|
+
} from "#api/hitl-route-helpers.js";
|
|
20
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
21
|
+
import { wpPrepareBody } from "#api/schemas.js";
|
|
22
|
+
import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
|
|
23
|
+
import { FIELD_TYPES, type FieldType } from "#schema/types.js";
|
|
24
|
+
import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
|
|
25
|
+
import type { DinewayHandlers } from "#types";
|
|
26
|
+
|
|
27
|
+
import { capitalize, singularize, type ImportFieldDef } from "./analyze.js";
|
|
28
|
+
|
|
29
|
+
/** Validate that a string is a known FieldType, returning undefined if not */
|
|
30
|
+
function asFieldType(value: string): FieldType | undefined {
|
|
31
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- validated by includes check
|
|
32
|
+
return (FIELD_TYPES as readonly string[]).includes(value) ? (value as FieldType) : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const prerender = false;
|
|
36
|
+
|
|
37
|
+
const prepareBodySchema = wpPrepareBody.extend({
|
|
38
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
interface PrepareRequest {
|
|
42
|
+
postTypes: Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
collection: string;
|
|
45
|
+
fields: ImportFieldDef[];
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PrepareResult {
|
|
50
|
+
success: boolean;
|
|
51
|
+
collectionsCreated: string[];
|
|
52
|
+
fieldsCreated: Array<{ collection: string; field: string }>;
|
|
53
|
+
errors: Array<{ collection: string; error: string }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
57
|
+
const { dineway, user } = locals;
|
|
58
|
+
|
|
59
|
+
if (!dineway?.db) {
|
|
60
|
+
return apiError("NOT_CONFIGURED", "Dineway is not configured", 500);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const denied = requirePerm(user, "import:execute");
|
|
64
|
+
if (denied) return denied;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const body = await parseBody(request, prepareBodySchema);
|
|
68
|
+
if (isParseError(body)) return body;
|
|
69
|
+
|
|
70
|
+
const actor = resolveHitlRouteActor(locals);
|
|
71
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
72
|
+
db: dineway.db,
|
|
73
|
+
handlers: dineway,
|
|
74
|
+
});
|
|
75
|
+
const action = await new WordPressImportHitlPayloadBuilder().buildPrepareRequest({
|
|
76
|
+
postTypes: body.postTypes,
|
|
77
|
+
});
|
|
78
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
79
|
+
actor: actor.identity,
|
|
80
|
+
hitlRequestId: body.hitlRequestId,
|
|
81
|
+
action,
|
|
82
|
+
});
|
|
83
|
+
if (!decision.allowed) {
|
|
84
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
85
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await prepareImport(dineway.db, {
|
|
89
|
+
postTypes: body.postTypes,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return apiSuccess(result, result.success ? 200 : 400);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return handleError(error, "Failed to prepare import", "WXR_PREPARE_ERROR");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
async function prepareImport(
|
|
99
|
+
db: NonNullable<DinewayHandlers["db"]>,
|
|
100
|
+
request: PrepareRequest,
|
|
101
|
+
): Promise<PrepareResult> {
|
|
102
|
+
const { SchemaRegistry } = await import("#schema/registry.js");
|
|
103
|
+
const registry = new SchemaRegistry(db);
|
|
104
|
+
|
|
105
|
+
const result: PrepareResult = {
|
|
106
|
+
success: true,
|
|
107
|
+
collectionsCreated: [],
|
|
108
|
+
fieldsCreated: [],
|
|
109
|
+
errors: [],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const postType of request.postTypes) {
|
|
113
|
+
const collectionSlug = sanitizeWordPressImportSlug(postType.collection);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Check if collection exists
|
|
117
|
+
let collection = await registry.getCollection(collectionSlug);
|
|
118
|
+
|
|
119
|
+
if (!collection) {
|
|
120
|
+
// Create the collection
|
|
121
|
+
const label = capitalize(collectionSlug);
|
|
122
|
+
const labelSingular = capitalize(singularize(collectionSlug));
|
|
123
|
+
|
|
124
|
+
// Enable search by default for posts and pages
|
|
125
|
+
const isSearchable = ["posts", "pages", "post", "page"].includes(collectionSlug);
|
|
126
|
+
const supports: ("revisions" | "drafts" | "search")[] = ["revisions", "drafts"];
|
|
127
|
+
if (isSearchable) {
|
|
128
|
+
supports.push("search");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Default URL patterns for known post types
|
|
132
|
+
const urlPattern =
|
|
133
|
+
collectionSlug === "pages"
|
|
134
|
+
? "/{slug}"
|
|
135
|
+
: collectionSlug === "posts"
|
|
136
|
+
? "/blog/{slug}"
|
|
137
|
+
: undefined;
|
|
138
|
+
|
|
139
|
+
collection = await registry.createCollection({
|
|
140
|
+
slug: collectionSlug,
|
|
141
|
+
label,
|
|
142
|
+
labelSingular,
|
|
143
|
+
description: `Imported from WordPress post type: ${postType.name}`,
|
|
144
|
+
supports,
|
|
145
|
+
urlPattern,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
result.collectionsCreated.push(collectionSlug);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Create missing fields
|
|
152
|
+
const existingFields = await registry.listFields(collection.id);
|
|
153
|
+
const existingFieldSlugs = new Set(existingFields.map((f) => f.slug));
|
|
154
|
+
|
|
155
|
+
for (const field of postType.fields) {
|
|
156
|
+
if (existingFieldSlugs.has(field.slug)) {
|
|
157
|
+
// Field already exists - skip
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const fieldType = asFieldType(field.type);
|
|
162
|
+
if (!fieldType) {
|
|
163
|
+
result.errors.push({
|
|
164
|
+
collection: collectionSlug,
|
|
165
|
+
error: `Unknown field type "${field.type}" for field "${field.slug}"`,
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await registry.createField(collectionSlug, {
|
|
171
|
+
slug: field.slug,
|
|
172
|
+
label: field.label,
|
|
173
|
+
type: fieldType,
|
|
174
|
+
required: field.required,
|
|
175
|
+
unique: false,
|
|
176
|
+
searchable: field.searchable ?? false,
|
|
177
|
+
sortOrder: existingFields.length + result.fieldsCreated.length,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
result.fieldsCreated.push({
|
|
181
|
+
collection: collectionSlug,
|
|
182
|
+
field: field.slug,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Enable search if collection supports it and has searchable fields
|
|
187
|
+
const isSearchable = ["posts", "pages", "post", "page"].includes(collectionSlug);
|
|
188
|
+
if (isSearchable) {
|
|
189
|
+
const { FTSManager } = await import("#search/fts-manager.js");
|
|
190
|
+
const ftsManager = new FTSManager(db);
|
|
191
|
+
|
|
192
|
+
const searchableFields = await ftsManager.getSearchableFields(collectionSlug);
|
|
193
|
+
if (searchableFields.length > 0) {
|
|
194
|
+
try {
|
|
195
|
+
await ftsManager.enableSearch(collectionSlug);
|
|
196
|
+
} catch {
|
|
197
|
+
// Ignore - search can be enabled manually later
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`Prepare error for collection "${collectionSlug}":`, error);
|
|
203
|
+
result.success = false;
|
|
204
|
+
result.errors.push({
|
|
205
|
+
collection: collectionSlug,
|
|
206
|
+
error: "Failed to prepare collection",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress URL rewrite endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/import/wordpress/rewrite-urls
|
|
5
|
+
*
|
|
6
|
+
* Rewrites old WordPress media URLs in Portable Text content
|
|
7
|
+
* to point to newly imported Dineway media URLs.
|
|
8
|
+
*
|
|
9
|
+
* Handles URL variants (e.g., image.jpg vs image.jpg?w=200) by matching
|
|
10
|
+
* on the base URL path without query parameters.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { APIRoute } from "astro";
|
|
14
|
+
import { sql } from "kysely";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import { requirePerm } from "#api/authorize.js";
|
|
18
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
19
|
+
import {
|
|
20
|
+
ensureWorkflowHitlRouteRequest,
|
|
21
|
+
hitlRequiredRouteError,
|
|
22
|
+
resolveHitlRouteActor,
|
|
23
|
+
} from "#api/hitl-route-helpers.js";
|
|
24
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
25
|
+
import { wpRewriteUrlsBody } from "#api/schemas.js";
|
|
26
|
+
import { validateIdentifier } from "#db/validate.js";
|
|
27
|
+
import { normalizeMediaValue } from "#media/normalize.js";
|
|
28
|
+
import type { MediaProvider } from "#media/types.js";
|
|
29
|
+
import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
|
|
30
|
+
import type { DinewayHandlers } from "#types";
|
|
31
|
+
|
|
32
|
+
export const prerender = false;
|
|
33
|
+
|
|
34
|
+
const rewriteUrlsBodySchema = wpRewriteUrlsBody.extend({
|
|
35
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
|
|
39
|
+
|
|
40
|
+
export interface RewriteUrlsResult {
|
|
41
|
+
/** Total items updated */
|
|
42
|
+
updated: number;
|
|
43
|
+
/** Updates by collection */
|
|
44
|
+
byCollection: Record<string, number>;
|
|
45
|
+
/** URLs that were rewritten */
|
|
46
|
+
urlsRewritten: number;
|
|
47
|
+
/** Any errors encountered */
|
|
48
|
+
errors: Array<{ collection: string; id: string; error: string }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
52
|
+
const { dineway, user } = locals;
|
|
53
|
+
|
|
54
|
+
if (!dineway?.db) {
|
|
55
|
+
return apiError("NO_DB", "Database not initialized", 500);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const denied = requirePerm(user, "import:execute");
|
|
59
|
+
if (denied) return denied;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const body = await parseBody(request, rewriteUrlsBodySchema);
|
|
63
|
+
if (isParseError(body)) return body;
|
|
64
|
+
|
|
65
|
+
const urlEntries = Object.entries(body.urlMap);
|
|
66
|
+
if (urlEntries.length === 0) {
|
|
67
|
+
return apiSuccess({
|
|
68
|
+
updated: 0,
|
|
69
|
+
byCollection: {},
|
|
70
|
+
urlsRewritten: 0,
|
|
71
|
+
errors: [],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const actor = resolveHitlRouteActor(locals);
|
|
76
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
77
|
+
db: dineway.db,
|
|
78
|
+
handlers: dineway,
|
|
79
|
+
});
|
|
80
|
+
const action = await new WordPressImportHitlPayloadBuilder().buildRewriteUrlsRequest({
|
|
81
|
+
urlMap: body.urlMap,
|
|
82
|
+
collections: body.collections,
|
|
83
|
+
});
|
|
84
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
85
|
+
actor: actor.identity,
|
|
86
|
+
hitlRequestId: body.hitlRequestId,
|
|
87
|
+
action,
|
|
88
|
+
});
|
|
89
|
+
if (!decision.allowed) {
|
|
90
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
91
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const getProvider = (id: string) => dineway.getMediaProvider(id);
|
|
95
|
+
const result = await rewriteUrls(dineway.db, body.urlMap, getProvider, body.collections);
|
|
96
|
+
|
|
97
|
+
return apiSuccess(result);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return handleError(error, "Failed to rewrite URLs", "REWRITE_ERROR");
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Strip query parameters from a URL for base matching
|
|
105
|
+
*/
|
|
106
|
+
function getBaseUrl(url: string): string {
|
|
107
|
+
try {
|
|
108
|
+
const parsed = new URL(url);
|
|
109
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
110
|
+
} catch {
|
|
111
|
+
// If URL parsing fails, try simple string split
|
|
112
|
+
return url.split("?")[0] || url;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build a map of base URLs to new URLs for flexible matching
|
|
118
|
+
*/
|
|
119
|
+
function buildBaseUrlMap(urlMap: Record<string, string>): Map<string, string> {
|
|
120
|
+
const baseMap = new Map<string, string>();
|
|
121
|
+
for (const [oldUrl, newUrl] of Object.entries(urlMap)) {
|
|
122
|
+
const baseUrl = getBaseUrl(oldUrl);
|
|
123
|
+
baseMap.set(baseUrl, newUrl);
|
|
124
|
+
}
|
|
125
|
+
return baseMap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Find matching new URL for a given URL, checking both exact and base matches
|
|
130
|
+
*/
|
|
131
|
+
function findMatchingUrl(
|
|
132
|
+
url: string,
|
|
133
|
+
exactMap: Record<string, string>,
|
|
134
|
+
baseMap: Map<string, string>,
|
|
135
|
+
): string | null {
|
|
136
|
+
// Try exact match first
|
|
137
|
+
if (exactMap[url]) {
|
|
138
|
+
return exactMap[url];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Try base URL match (ignoring query params)
|
|
142
|
+
const baseUrl = getBaseUrl(url);
|
|
143
|
+
const baseMatch = baseMap.get(baseUrl);
|
|
144
|
+
if (baseMatch) {
|
|
145
|
+
return baseMatch;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Portable Text block type (simplified for URL rewriting)
|
|
153
|
+
*/
|
|
154
|
+
interface PortableTextBlock {
|
|
155
|
+
_type: string;
|
|
156
|
+
_key?: string;
|
|
157
|
+
asset?: {
|
|
158
|
+
_type?: string;
|
|
159
|
+
_ref?: string;
|
|
160
|
+
url?: string;
|
|
161
|
+
};
|
|
162
|
+
link?: string;
|
|
163
|
+
// For nested content like galleries
|
|
164
|
+
images?: PortableTextBlock[];
|
|
165
|
+
columns?: Array<{ content?: PortableTextBlock[] }>;
|
|
166
|
+
[key: string]: unknown;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Rewrite URLs in a Portable Text array, returning whether any changes were made
|
|
171
|
+
*/
|
|
172
|
+
function rewritePortableTextUrls(
|
|
173
|
+
blocks: PortableTextBlock[],
|
|
174
|
+
exactMap: Record<string, string>,
|
|
175
|
+
baseMap: Map<string, string>,
|
|
176
|
+
): { changed: boolean; urlsRewritten: number } {
|
|
177
|
+
let changed = false;
|
|
178
|
+
let urlsRewritten = 0;
|
|
179
|
+
|
|
180
|
+
for (const block of blocks) {
|
|
181
|
+
// Handle image blocks
|
|
182
|
+
if (block._type === "image" && block.asset?.url) {
|
|
183
|
+
const newUrl = findMatchingUrl(block.asset.url, exactMap, baseMap);
|
|
184
|
+
if (newUrl) {
|
|
185
|
+
block.asset.url = newUrl;
|
|
186
|
+
block.asset._ref = newUrl; // Also update the reference
|
|
187
|
+
changed = true;
|
|
188
|
+
urlsRewritten++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Handle image link URLs (for linked images)
|
|
193
|
+
if (block._type === "image" && block.link) {
|
|
194
|
+
const newUrl = findMatchingUrl(block.link, exactMap, baseMap);
|
|
195
|
+
if (newUrl) {
|
|
196
|
+
block.link = newUrl;
|
|
197
|
+
changed = true;
|
|
198
|
+
urlsRewritten++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle gallery blocks with nested images
|
|
203
|
+
if (block._type === "gallery" && Array.isArray(block.images)) {
|
|
204
|
+
const result = rewritePortableTextUrls(block.images, exactMap, baseMap);
|
|
205
|
+
if (result.changed) {
|
|
206
|
+
changed = true;
|
|
207
|
+
urlsRewritten += result.urlsRewritten;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Handle columns blocks with nested content
|
|
212
|
+
if (block._type === "columns" && Array.isArray(block.columns)) {
|
|
213
|
+
for (const column of block.columns) {
|
|
214
|
+
if (Array.isArray(column.content)) {
|
|
215
|
+
const result = rewritePortableTextUrls(column.content, exactMap, baseMap);
|
|
216
|
+
if (result.changed) {
|
|
217
|
+
changed = true;
|
|
218
|
+
urlsRewritten += result.urlsRewritten;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { changed, urlsRewritten };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Rewrite URLs in a string field using simple string replacement
|
|
230
|
+
*/
|
|
231
|
+
function rewriteStringUrls(
|
|
232
|
+
value: string,
|
|
233
|
+
exactMap: Record<string, string>,
|
|
234
|
+
baseMap: Map<string, string>,
|
|
235
|
+
): { newValue: string; changed: boolean; urlsRewritten: number } {
|
|
236
|
+
let newValue = value;
|
|
237
|
+
let changed = false;
|
|
238
|
+
let urlsRewritten = 0;
|
|
239
|
+
|
|
240
|
+
// Try exact matches first
|
|
241
|
+
for (const [oldUrl, newUrl] of Object.entries(exactMap)) {
|
|
242
|
+
if (newValue.includes(oldUrl)) {
|
|
243
|
+
newValue = newValue.split(oldUrl).join(newUrl);
|
|
244
|
+
changed = true;
|
|
245
|
+
urlsRewritten++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// For base URL matching in strings, we need to be more careful
|
|
250
|
+
// Only match if we find a URL that starts with the base
|
|
251
|
+
for (const [baseUrl, newUrl] of baseMap.entries()) {
|
|
252
|
+
// Look for the base URL followed by optional query string or end
|
|
253
|
+
const regex = new RegExp(escapeRegExp(baseUrl) + "(\\?[^\"'\\s]*)?", "g");
|
|
254
|
+
const matches = newValue.match(regex);
|
|
255
|
+
if (matches) {
|
|
256
|
+
for (const match of matches) {
|
|
257
|
+
// Don't replace if we already have an exact match in the map
|
|
258
|
+
if (!exactMap[match]) {
|
|
259
|
+
newValue = newValue.split(match).join(newUrl);
|
|
260
|
+
changed = true;
|
|
261
|
+
urlsRewritten++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { newValue, changed, urlsRewritten };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Escape special regex characters in a string
|
|
272
|
+
*/
|
|
273
|
+
function escapeRegExp(string: string): string {
|
|
274
|
+
return string.replace(REGEX_SPECIAL_CHARS, "\\$&");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function rewriteUrls(
|
|
278
|
+
db: NonNullable<DinewayHandlers["db"]>,
|
|
279
|
+
urlMap: Record<string, string>,
|
|
280
|
+
getProvider: (id: string) => MediaProvider | undefined,
|
|
281
|
+
collections?: string[],
|
|
282
|
+
): Promise<RewriteUrlsResult> {
|
|
283
|
+
const { SchemaRegistry } = await import("#schema/registry.js");
|
|
284
|
+
const registry = new SchemaRegistry(db);
|
|
285
|
+
|
|
286
|
+
const result: RewriteUrlsResult = {
|
|
287
|
+
updated: 0,
|
|
288
|
+
byCollection: {},
|
|
289
|
+
urlsRewritten: 0,
|
|
290
|
+
errors: [],
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Build base URL map for flexible matching
|
|
294
|
+
const baseMap = buildBaseUrlMap(urlMap);
|
|
295
|
+
|
|
296
|
+
// Get all collections or filter to specified ones
|
|
297
|
+
const allCollections = await registry.listCollections();
|
|
298
|
+
const targetCollections = collections?.length
|
|
299
|
+
? allCollections.filter((c) => collections.includes(c.slug))
|
|
300
|
+
: allCollections;
|
|
301
|
+
|
|
302
|
+
for (const collection of targetCollections) {
|
|
303
|
+
// Get fields that might contain URLs
|
|
304
|
+
const fields = await registry.listFields(collection.id);
|
|
305
|
+
const portableTextFields = fields.filter((f) => f.type === "portableText");
|
|
306
|
+
const stringFields = fields.filter((f) => ["text", "string"].includes(f.type));
|
|
307
|
+
// Image and file fields store URLs directly as TEXT
|
|
308
|
+
const mediaFields = fields.filter((f) => ["image", "file"].includes(f.type));
|
|
309
|
+
|
|
310
|
+
if (portableTextFields.length === 0 && stringFields.length === 0 && mediaFields.length === 0)
|
|
311
|
+
continue;
|
|
312
|
+
|
|
313
|
+
// Get table name
|
|
314
|
+
validateIdentifier(collection.slug, "collection slug");
|
|
315
|
+
const tableName = `ec_${collection.slug}`;
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
// Query all rows
|
|
319
|
+
const rows = await sql<{ id: string; [key: string]: unknown }>`
|
|
320
|
+
SELECT * FROM ${sql.ref(tableName)}
|
|
321
|
+
WHERE deleted_at IS NULL
|
|
322
|
+
`.execute(db);
|
|
323
|
+
|
|
324
|
+
for (const row of rows.rows) {
|
|
325
|
+
let rowUpdated = false;
|
|
326
|
+
const updates: Record<string, unknown> = {};
|
|
327
|
+
let rowUrlsRewritten = 0;
|
|
328
|
+
|
|
329
|
+
// Handle Portable Text fields - parse JSON and rewrite URLs in blocks
|
|
330
|
+
for (const field of portableTextFields) {
|
|
331
|
+
const value = row[field.slug];
|
|
332
|
+
if (!value || typeof value !== "string") continue;
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns unknown; validated by Array.isArray below
|
|
336
|
+
const blocks = JSON.parse(value) as PortableTextBlock[];
|
|
337
|
+
if (!Array.isArray(blocks)) continue;
|
|
338
|
+
|
|
339
|
+
const rewriteResult = rewritePortableTextUrls(blocks, urlMap, baseMap);
|
|
340
|
+
|
|
341
|
+
if (rewriteResult.changed) {
|
|
342
|
+
updates[field.slug] = JSON.stringify(blocks);
|
|
343
|
+
rowUpdated = true;
|
|
344
|
+
rowUrlsRewritten += rewriteResult.urlsRewritten;
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
// Not valid JSON, try string replacement as fallback
|
|
348
|
+
const stringResult = rewriteStringUrls(value, urlMap, baseMap);
|
|
349
|
+
if (stringResult.changed) {
|
|
350
|
+
updates[field.slug] = stringResult.newValue;
|
|
351
|
+
rowUpdated = true;
|
|
352
|
+
rowUrlsRewritten += stringResult.urlsRewritten;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Handle string/text fields - simple string replacement
|
|
358
|
+
for (const field of stringFields) {
|
|
359
|
+
const value = row[field.slug];
|
|
360
|
+
if (!value || typeof value !== "string") continue;
|
|
361
|
+
|
|
362
|
+
const stringResult = rewriteStringUrls(value, urlMap, baseMap);
|
|
363
|
+
if (stringResult.changed) {
|
|
364
|
+
updates[field.slug] = stringResult.newValue;
|
|
365
|
+
rowUpdated = true;
|
|
366
|
+
rowUrlsRewritten += stringResult.urlsRewritten;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Handle image/file fields - normalize to MediaValue objects
|
|
371
|
+
for (const field of mediaFields) {
|
|
372
|
+
const value = row[field.slug];
|
|
373
|
+
if (!value || typeof value !== "string") continue;
|
|
374
|
+
|
|
375
|
+
// Try to find a matching rewritten URL
|
|
376
|
+
const newUrl = findMatchingUrl(value, urlMap, baseMap);
|
|
377
|
+
if (newUrl) {
|
|
378
|
+
// Normalize into a proper MediaValue instead of storing a bare URL
|
|
379
|
+
try {
|
|
380
|
+
const normalized = await normalizeMediaValue(newUrl, getProvider);
|
|
381
|
+
updates[field.slug] = normalized ? JSON.stringify(normalized) : newUrl;
|
|
382
|
+
} catch {
|
|
383
|
+
updates[field.slug] = newUrl;
|
|
384
|
+
}
|
|
385
|
+
rowUpdated = true;
|
|
386
|
+
rowUrlsRewritten++;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (rowUpdated) {
|
|
391
|
+
try {
|
|
392
|
+
// Build update query dynamically
|
|
393
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely dynamic table requires type assertion
|
|
394
|
+
let query = db.updateTable(tableName as any).where("id", "=", row.id);
|
|
395
|
+
|
|
396
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
397
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely dynamic column update requires type assertion
|
|
398
|
+
query = query.set({ [key]: value } as any);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
await query.execute();
|
|
402
|
+
|
|
403
|
+
result.updated++;
|
|
404
|
+
result.urlsRewritten += rowUrlsRewritten;
|
|
405
|
+
result.byCollection[collection.slug] = (result.byCollection[collection.slug] || 0) + 1;
|
|
406
|
+
} catch (updateError) {
|
|
407
|
+
result.errors.push({
|
|
408
|
+
collection: collection.slug,
|
|
409
|
+
id: row.id,
|
|
410
|
+
error: updateError instanceof Error ? updateError.message : "Update failed",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch (queryError) {
|
|
416
|
+
result.errors.push({
|
|
417
|
+
collection: collection.slug,
|
|
418
|
+
id: "*",
|
|
419
|
+
error: queryError instanceof Error ? queryError.message : "Query failed for collection",
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return result;
|
|
425
|
+
}
|