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,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress WXR analyze endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/import/wordpress/analyze
|
|
5
|
+
*
|
|
6
|
+
* Accepts a WXR file upload and returns analysis of its contents,
|
|
7
|
+
* including post types, counts, custom fields, and schema compatibility.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
import { parseWxrString, SchemaRegistry, type WxrData } from "dineway";
|
|
12
|
+
import mime from "mime/lite";
|
|
13
|
+
|
|
14
|
+
import { requirePerm } from "#api/authorize.js";
|
|
15
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
16
|
+
import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
|
|
17
|
+
import type { DinewayHandlers } from "#types";
|
|
18
|
+
|
|
19
|
+
export const prerender = false;
|
|
20
|
+
|
|
21
|
+
const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
|
|
22
|
+
|
|
23
|
+
/** Field compatibility status */
|
|
24
|
+
export type FieldCompatibility =
|
|
25
|
+
| "compatible" // Field exists with compatible type
|
|
26
|
+
| "type_mismatch" // Field exists but type differs
|
|
27
|
+
| "missing"; // Field doesn't exist
|
|
28
|
+
|
|
29
|
+
/** Single field definition for import */
|
|
30
|
+
export interface ImportFieldDef {
|
|
31
|
+
slug: string;
|
|
32
|
+
label: string;
|
|
33
|
+
type: string;
|
|
34
|
+
required: boolean;
|
|
35
|
+
searchable?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Schema status for a collection */
|
|
39
|
+
export interface CollectionSchemaStatus {
|
|
40
|
+
/** Whether collection exists */
|
|
41
|
+
exists: boolean;
|
|
42
|
+
/** If exists, per-field compatibility */
|
|
43
|
+
fieldStatus: Record<
|
|
44
|
+
string,
|
|
45
|
+
{
|
|
46
|
+
status: FieldCompatibility;
|
|
47
|
+
existingType?: string;
|
|
48
|
+
requiredType: string;
|
|
49
|
+
}
|
|
50
|
+
>;
|
|
51
|
+
/** Can we safely import to this collection? */
|
|
52
|
+
canImport: boolean;
|
|
53
|
+
/** Human-readable reason if canImport is false */
|
|
54
|
+
reason?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Post type with full schema info */
|
|
58
|
+
export interface PostTypeAnalysis {
|
|
59
|
+
/** WordPress post type name */
|
|
60
|
+
name: string;
|
|
61
|
+
/** Number of items to import */
|
|
62
|
+
count: number;
|
|
63
|
+
/** Suggested collection slug */
|
|
64
|
+
suggestedCollection: string;
|
|
65
|
+
/** Fields we need to create */
|
|
66
|
+
requiredFields: ImportFieldDef[];
|
|
67
|
+
/** Schema compatibility status */
|
|
68
|
+
schemaStatus: CollectionSchemaStatus;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Individual attachment info for media import */
|
|
72
|
+
export interface AttachmentInfo {
|
|
73
|
+
id?: number;
|
|
74
|
+
title?: string;
|
|
75
|
+
url?: string;
|
|
76
|
+
filename?: string;
|
|
77
|
+
mimeType?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Author info from WordPress */
|
|
81
|
+
export interface WpAuthorInfo {
|
|
82
|
+
id?: number;
|
|
83
|
+
login?: string;
|
|
84
|
+
email?: string;
|
|
85
|
+
displayName?: string;
|
|
86
|
+
postCount: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface WxrAnalysis {
|
|
90
|
+
site: {
|
|
91
|
+
title: string;
|
|
92
|
+
url: string;
|
|
93
|
+
};
|
|
94
|
+
postTypes: PostTypeAnalysis[];
|
|
95
|
+
attachments: {
|
|
96
|
+
count: number;
|
|
97
|
+
items: AttachmentInfo[];
|
|
98
|
+
};
|
|
99
|
+
categories: number;
|
|
100
|
+
tags: number;
|
|
101
|
+
authors: WpAuthorInfo[];
|
|
102
|
+
customFields: Array<{
|
|
103
|
+
key: string;
|
|
104
|
+
count: number;
|
|
105
|
+
samples: string[];
|
|
106
|
+
suggestedField: string;
|
|
107
|
+
suggestedType: "string" | "number" | "boolean" | "date" | "json";
|
|
108
|
+
isInternal: boolean;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
113
|
+
const { dineway, user } = locals;
|
|
114
|
+
|
|
115
|
+
const denied = requirePerm(user, "import:execute");
|
|
116
|
+
if (denied) return denied;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const formData = await request.formData();
|
|
120
|
+
const fileEntry = formData.get("file");
|
|
121
|
+
const file = fileEntry instanceof File ? fileEntry : null;
|
|
122
|
+
|
|
123
|
+
if (!file) {
|
|
124
|
+
return apiError("VALIDATION_ERROR", "No file provided", 400);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Parse WXR
|
|
128
|
+
const text = await file.text();
|
|
129
|
+
const wxr = await parseWxrString(text);
|
|
130
|
+
|
|
131
|
+
// Fetch existing collections from schema registry
|
|
132
|
+
const existingCollections = await fetchExistingCollections(dineway?.db);
|
|
133
|
+
|
|
134
|
+
// Analyze content with schema compatibility
|
|
135
|
+
const analysis = analyzeWxr(wxr, existingCollections);
|
|
136
|
+
|
|
137
|
+
return apiSuccess(analysis);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return handleError(error, "Failed to analyze file", "WXR_ANALYZE_ERROR");
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Existing collection info from schema registry */
|
|
144
|
+
interface ExistingCollection {
|
|
145
|
+
slug: string;
|
|
146
|
+
fields: Map<string, { type: string; columnType: string }>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Fetch collections and their fields from schema registry */
|
|
150
|
+
async function fetchExistingCollections(
|
|
151
|
+
db: DinewayHandlers["db"] | undefined,
|
|
152
|
+
): Promise<Map<string, ExistingCollection>> {
|
|
153
|
+
const result = new Map<string, ExistingCollection>();
|
|
154
|
+
|
|
155
|
+
if (!db) return result;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const registry = new SchemaRegistry(db);
|
|
159
|
+
|
|
160
|
+
const collections = await registry.listCollections();
|
|
161
|
+
|
|
162
|
+
for (const collection of collections) {
|
|
163
|
+
const fields = await registry.listFields(collection.id);
|
|
164
|
+
const fieldMap = new Map<string, { type: string; columnType: string }>();
|
|
165
|
+
|
|
166
|
+
for (const field of fields) {
|
|
167
|
+
fieldMap.set(field.slug, {
|
|
168
|
+
type: field.type,
|
|
169
|
+
columnType: field.columnType,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
result.set(collection.slug, {
|
|
174
|
+
slug: collection.slug,
|
|
175
|
+
fields: fieldMap,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.warn("Could not fetch schema registry:", error);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Base fields required for any WordPress import */
|
|
186
|
+
const BASE_REQUIRED_FIELDS: ImportFieldDef[] = [
|
|
187
|
+
{ slug: "title", label: "Title", type: "string", required: true, searchable: true },
|
|
188
|
+
{ slug: "content", label: "Content", type: "portableText", required: false, searchable: true },
|
|
189
|
+
{ slug: "excerpt", label: "Excerpt", type: "text", required: false },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
/** Featured image field - only added to post types that have _thumbnail_id */
|
|
193
|
+
const FEATURED_IMAGE_FIELD: ImportFieldDef = {
|
|
194
|
+
slug: "featured_image",
|
|
195
|
+
label: "Featured Image",
|
|
196
|
+
type: "image",
|
|
197
|
+
required: false,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
function analyzeWxr(
|
|
201
|
+
wxr: WxrData,
|
|
202
|
+
existingCollections: Map<string, ExistingCollection>,
|
|
203
|
+
): WxrAnalysis {
|
|
204
|
+
// Count post types and track which have featured images
|
|
205
|
+
const postTypeCounts = new Map<string, number>();
|
|
206
|
+
const postTypesWithThumbnails = new Set<string>();
|
|
207
|
+
const metaKeys = new Map<string, { count: number; samples: string[]; isInternal: boolean }>();
|
|
208
|
+
const authorPostCounts = new Map<string, number>();
|
|
209
|
+
|
|
210
|
+
for (const post of wxr.posts) {
|
|
211
|
+
const type = post.postType || "post";
|
|
212
|
+
postTypeCounts.set(type, (postTypeCounts.get(type) || 0) + 1);
|
|
213
|
+
|
|
214
|
+
// Count posts per author (by login)
|
|
215
|
+
if (post.creator) {
|
|
216
|
+
authorPostCounts.set(post.creator, (authorPostCounts.get(post.creator) || 0) + 1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Track if this post type has featured images
|
|
220
|
+
if (post.meta.has("_thumbnail_id")) {
|
|
221
|
+
postTypesWithThumbnails.add(type);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Analyze meta keys
|
|
225
|
+
for (const [key, value] of post.meta) {
|
|
226
|
+
const existing = metaKeys.get(key);
|
|
227
|
+
if (existing) {
|
|
228
|
+
existing.count++;
|
|
229
|
+
if (existing.samples.length < 3 && value) {
|
|
230
|
+
existing.samples.push(value.slice(0, 100));
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
metaKeys.set(key, {
|
|
234
|
+
count: 1,
|
|
235
|
+
samples: value ? [value.slice(0, 100)] : [],
|
|
236
|
+
isInternal: isInternalMetaKey(key),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Map meta keys to fields (for custom fields analysis)
|
|
243
|
+
const customFields = [...metaKeys.entries()]
|
|
244
|
+
.filter(([_, info]) => !info.isInternal)
|
|
245
|
+
.map(([key, info]) => ({
|
|
246
|
+
key,
|
|
247
|
+
count: info.count,
|
|
248
|
+
samples: info.samples,
|
|
249
|
+
suggestedField: mapMetaKeyToField(key),
|
|
250
|
+
suggestedType: inferMetaType(key, info.samples[0]),
|
|
251
|
+
isInternal: info.isInternal,
|
|
252
|
+
}))
|
|
253
|
+
.toSorted((a, b) => b.count - a.count);
|
|
254
|
+
|
|
255
|
+
// Build post type analysis with schema compatibility
|
|
256
|
+
const seenSlugs = new Map<string, number>();
|
|
257
|
+
const postTypes: PostTypeAnalysis[] = [...postTypeCounts.entries()]
|
|
258
|
+
.filter(([type]) => !isInternalPostType(type))
|
|
259
|
+
.map(([name, count]) => {
|
|
260
|
+
let suggestedCollection = mapPostTypeToCollection(name);
|
|
261
|
+
|
|
262
|
+
// Deduplicate: if multiple post types produce the same slug, append a suffix
|
|
263
|
+
const seen = seenSlugs.get(suggestedCollection) ?? 0;
|
|
264
|
+
seenSlugs.set(suggestedCollection, seen + 1);
|
|
265
|
+
if (seen > 0) {
|
|
266
|
+
suggestedCollection = `${suggestedCollection}_${seen}`;
|
|
267
|
+
}
|
|
268
|
+
const existingCollection = existingCollections.get(suggestedCollection);
|
|
269
|
+
|
|
270
|
+
// Build required fields - add featured_image only if posts have thumbnails
|
|
271
|
+
const requiredFields = [...BASE_REQUIRED_FIELDS];
|
|
272
|
+
if (postTypesWithThumbnails.has(name)) {
|
|
273
|
+
requiredFields.push(FEATURED_IMAGE_FIELD);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const schemaStatus = checkSchemaCompatibility(requiredFields, existingCollection);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
name,
|
|
280
|
+
count,
|
|
281
|
+
suggestedCollection,
|
|
282
|
+
requiredFields,
|
|
283
|
+
schemaStatus,
|
|
284
|
+
};
|
|
285
|
+
})
|
|
286
|
+
.toSorted((a, b) => b.count - a.count);
|
|
287
|
+
|
|
288
|
+
// Build attachment info list
|
|
289
|
+
const attachmentItems: AttachmentInfo[] = wxr.attachments.map((att) => {
|
|
290
|
+
const filename = att.url ? getFilenameFromUrl(att.url) : undefined;
|
|
291
|
+
const mimeType = filename ? guessMimeType(filename) : undefined;
|
|
292
|
+
return {
|
|
293
|
+
id: att.id,
|
|
294
|
+
title: att.title,
|
|
295
|
+
url: att.url,
|
|
296
|
+
filename,
|
|
297
|
+
mimeType,
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
site: {
|
|
303
|
+
title: wxr.site.title || "WordPress Site",
|
|
304
|
+
url: wxr.site.link || "",
|
|
305
|
+
},
|
|
306
|
+
postTypes,
|
|
307
|
+
attachments: {
|
|
308
|
+
count: wxr.attachments.length,
|
|
309
|
+
items: attachmentItems,
|
|
310
|
+
},
|
|
311
|
+
categories: wxr.categories.length,
|
|
312
|
+
tags: wxr.tags.length,
|
|
313
|
+
authors: wxr.authors.map((a) => ({
|
|
314
|
+
id: a.id,
|
|
315
|
+
login: a.login,
|
|
316
|
+
email: a.email,
|
|
317
|
+
displayName: a.displayName || a.login || "Unknown",
|
|
318
|
+
postCount: a.login ? authorPostCounts.get(a.login) || 0 : 0,
|
|
319
|
+
})),
|
|
320
|
+
customFields,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Extract filename from URL */
|
|
325
|
+
function getFilenameFromUrl(url: string): string | undefined {
|
|
326
|
+
try {
|
|
327
|
+
const parsed = new URL(url);
|
|
328
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
329
|
+
return segments.pop();
|
|
330
|
+
} catch {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Guess MIME type from filename extension */
|
|
336
|
+
function guessMimeType(filename: string): string | undefined {
|
|
337
|
+
return mime.getType(filename) ?? undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Check if a collection schema is compatible with import requirements */
|
|
341
|
+
function checkSchemaCompatibility(
|
|
342
|
+
requiredFields: ImportFieldDef[],
|
|
343
|
+
existingCollection: ExistingCollection | undefined,
|
|
344
|
+
): CollectionSchemaStatus {
|
|
345
|
+
if (!existingCollection) {
|
|
346
|
+
// Collection doesn't exist - will need to create it
|
|
347
|
+
const fieldStatus: CollectionSchemaStatus["fieldStatus"] = {};
|
|
348
|
+
for (const field of requiredFields) {
|
|
349
|
+
fieldStatus[field.slug] = {
|
|
350
|
+
status: "missing",
|
|
351
|
+
requiredType: field.type,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
exists: false,
|
|
356
|
+
fieldStatus,
|
|
357
|
+
canImport: true, // We can create it
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Collection exists - check field compatibility
|
|
362
|
+
const fieldStatus: CollectionSchemaStatus["fieldStatus"] = {};
|
|
363
|
+
const incompatibleFields: string[] = [];
|
|
364
|
+
|
|
365
|
+
for (const field of requiredFields) {
|
|
366
|
+
const existingField = existingCollection.fields.get(field.slug);
|
|
367
|
+
|
|
368
|
+
if (!existingField) {
|
|
369
|
+
// Field missing - we can add it
|
|
370
|
+
fieldStatus[field.slug] = {
|
|
371
|
+
status: "missing",
|
|
372
|
+
requiredType: field.type,
|
|
373
|
+
};
|
|
374
|
+
} else if (isTypeCompatible(field.type, existingField.type)) {
|
|
375
|
+
// Field exists and is compatible
|
|
376
|
+
fieldStatus[field.slug] = {
|
|
377
|
+
status: "compatible",
|
|
378
|
+
existingType: existingField.type,
|
|
379
|
+
requiredType: field.type,
|
|
380
|
+
};
|
|
381
|
+
} else {
|
|
382
|
+
// Field exists but type doesn't match
|
|
383
|
+
fieldStatus[field.slug] = {
|
|
384
|
+
status: "type_mismatch",
|
|
385
|
+
existingType: existingField.type,
|
|
386
|
+
requiredType: field.type,
|
|
387
|
+
};
|
|
388
|
+
incompatibleFields.push(field.slug);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const canImport = incompatibleFields.length === 0;
|
|
393
|
+
const reason = canImport
|
|
394
|
+
? undefined
|
|
395
|
+
: `Incompatible field types: ${incompatibleFields.join(", ")}. ` +
|
|
396
|
+
`Existing fields have different types than required for import.`;
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
exists: true,
|
|
400
|
+
fieldStatus,
|
|
401
|
+
canImport,
|
|
402
|
+
reason,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Check if two field types are compatible for import */
|
|
407
|
+
function isTypeCompatible(requiredType: string, existingType: string): boolean {
|
|
408
|
+
// Exact match
|
|
409
|
+
if (requiredType === existingType) return true;
|
|
410
|
+
|
|
411
|
+
// Compatible mappings
|
|
412
|
+
const compatibleTypes: Record<string, string[]> = {
|
|
413
|
+
string: ["string", "text", "slug"],
|
|
414
|
+
text: ["string", "text"],
|
|
415
|
+
portableText: ["portableText", "json"],
|
|
416
|
+
number: ["number", "integer"],
|
|
417
|
+
integer: ["number", "integer"],
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const compatible = compatibleTypes[requiredType];
|
|
421
|
+
return compatible?.includes(existingType) ?? false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function isInternalPostType(type: string): boolean {
|
|
425
|
+
return [
|
|
426
|
+
"revision",
|
|
427
|
+
"nav_menu_item",
|
|
428
|
+
"custom_css",
|
|
429
|
+
"customize_changeset",
|
|
430
|
+
"oembed_cache",
|
|
431
|
+
"wp_global_styles",
|
|
432
|
+
"wp_navigation",
|
|
433
|
+
"wp_template",
|
|
434
|
+
"wp_template_part",
|
|
435
|
+
"attachment", // Handled separately as media
|
|
436
|
+
"wp_block", // Handled separately as sections (reusable blocks)
|
|
437
|
+
].includes(type);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function isInternalMetaKey(key: string): boolean {
|
|
441
|
+
if (key.startsWith("_edit_")) return true;
|
|
442
|
+
if (key.startsWith("_wp_")) return true;
|
|
443
|
+
if (key === "_edit_last" || key === "_edit_lock") return true;
|
|
444
|
+
if (key === "_pingme" || key === "_encloseme") return true;
|
|
445
|
+
|
|
446
|
+
// Keep these useful ones
|
|
447
|
+
if (key === "_thumbnail_id") return false;
|
|
448
|
+
if (key.startsWith("_yoast_")) return false;
|
|
449
|
+
if (key.startsWith("_rank_math_")) return false;
|
|
450
|
+
|
|
451
|
+
// Other underscore prefixes are usually internal
|
|
452
|
+
if (key.startsWith("_")) return true;
|
|
453
|
+
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function sanitizeSlug(slug: string): string {
|
|
458
|
+
return sanitizeWordPressImportSlug(slug);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function mapPostTypeToCollection(postType: string): string {
|
|
462
|
+
const mapping: Record<string, string> = {
|
|
463
|
+
post: "posts",
|
|
464
|
+
page: "pages",
|
|
465
|
+
attachment: "media",
|
|
466
|
+
product: "products",
|
|
467
|
+
portfolio: "portfolio",
|
|
468
|
+
testimonial: "testimonials",
|
|
469
|
+
team: "team",
|
|
470
|
+
event: "events",
|
|
471
|
+
faq: "faqs",
|
|
472
|
+
};
|
|
473
|
+
return mapping[postType] || sanitizeSlug(postType);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function mapMetaKeyToField(key: string): string {
|
|
477
|
+
// SEO plugins
|
|
478
|
+
if (key === "_yoast_wpseo_title") return "seo_title";
|
|
479
|
+
if (key === "_yoast_wpseo_metadesc") return "seo_description";
|
|
480
|
+
if (key === "_rank_math_title") return "seo_title";
|
|
481
|
+
if (key === "_rank_math_description") return "seo_description";
|
|
482
|
+
if (key === "_thumbnail_id") return "featured_image";
|
|
483
|
+
|
|
484
|
+
// Remove leading underscore
|
|
485
|
+
if (key.startsWith("_")) return key.slice(1);
|
|
486
|
+
|
|
487
|
+
return key;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function inferMetaType(
|
|
491
|
+
key: string,
|
|
492
|
+
value: string | undefined,
|
|
493
|
+
): "string" | "number" | "boolean" | "date" | "json" {
|
|
494
|
+
if (key.endsWith("_id") || key === "_thumbnail_id") return "string"; // reference
|
|
495
|
+
if (key.endsWith("_date") || key.endsWith("_time")) return "date";
|
|
496
|
+
if (key.endsWith("_count") || key.endsWith("_number")) return "number";
|
|
497
|
+
|
|
498
|
+
if (!value) return "string";
|
|
499
|
+
|
|
500
|
+
// Serialized PHP or JSON
|
|
501
|
+
if (value.startsWith("a:") || value.startsWith("{") || value.startsWith("[")) return "json";
|
|
502
|
+
|
|
503
|
+
// Number
|
|
504
|
+
if (NUMERIC_PATTERN.test(value)) return "number";
|
|
505
|
+
|
|
506
|
+
// Boolean
|
|
507
|
+
if (["0", "1", "true", "false"].includes(value)) return "boolean";
|
|
508
|
+
|
|
509
|
+
return "string";
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function capitalize(str: string): string {
|
|
513
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function singularize(str: string): string {
|
|
517
|
+
if (str.endsWith("ies")) return str.slice(0, -3) + "y";
|
|
518
|
+
if (str.endsWith("s")) return str.slice(0, -1);
|
|
519
|
+
return str;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Export helpers for use in prepare endpoint
|
|
523
|
+
export { capitalize, sanitizeSlug, singularize, mapPostTypeToCollection };
|