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.
Files changed (296) hide show
  1. package/README.md +6 -3
  2. package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
  3. package/dist/astro/index.d.mts +18 -9
  4. package/dist/astro/index.mjs +238 -16
  5. package/dist/astro/middleware/auth.d.mts +16 -5
  6. package/dist/astro/middleware/auth.mjs +74 -37
  7. package/dist/astro/middleware/redirect.mjs +24 -8
  8. package/dist/astro/middleware/request-context.mjs +18 -5
  9. package/dist/astro/middleware/setup.mjs +1 -1
  10. package/dist/astro/middleware.mjs +411 -169
  11. package/dist/astro/types.d.mts +25 -8
  12. package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
  13. package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
  14. package/dist/cache-BdSY-gQN.mjs +42 -0
  15. package/dist/chunks--4F8ddV4.mjs +18 -0
  16. package/dist/cli/index.mjs +935 -15
  17. package/dist/client/external-auth-headers.d.mts +1 -1
  18. package/dist/client/index.d.mts +11 -3
  19. package/dist/client/index.mjs +4 -3
  20. package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
  21. package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
  22. package/dist/database/instrumentation.d.mts +34 -0
  23. package/dist/database/instrumentation.mjs +53 -0
  24. package/dist/db/index.d.mts +3 -3
  25. package/dist/db/index.mjs +2 -2
  26. package/dist/db/libsql.d.mts +1 -1
  27. package/dist/db/libsql.mjs +11 -5
  28. package/dist/db/postgres.d.mts +1 -1
  29. package/dist/db/sqlite.d.mts +1 -1
  30. package/dist/db/sqlite.mjs +7 -1
  31. package/dist/db-errors-CEqD7qH9.mjs +23 -0
  32. package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
  33. package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
  34. package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
  35. package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
  36. package/dist/index.d.mts +11 -11
  37. package/dist/index.mjs +24 -22
  38. package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
  39. package/dist/media/index.d.mts +1 -1
  40. package/dist/media/index.mjs +1 -1
  41. package/dist/media/local-runtime.d.mts +7 -7
  42. package/dist/page/index.d.mts +10 -2
  43. package/dist/page/index.mjs +22 -1
  44. package/dist/patterns-CrCYkMBb.mjs +92 -0
  45. package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
  46. package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
  47. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  48. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  49. package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
  50. package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
  51. package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
  52. package/dist/request-cache-Dk5qPSOx.mjs +66 -0
  53. package/dist/request-context.d.mts +4 -16
  54. package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
  55. package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
  56. package/dist/runtime.d.mts +6 -6
  57. package/dist/runtime.mjs +2 -2
  58. package/dist/{search-BNruJHDL.mjs → search-ByRGV2pq.mjs} +570 -424
  59. package/dist/seed/index.d.mts +2 -2
  60. package/dist/seed/index.mjs +11 -10
  61. package/dist/seo/index.d.mts +1 -1
  62. package/dist/storage/local.d.mts +1 -1
  63. package/dist/storage/local.mjs +1 -1
  64. package/dist/storage/s3.d.mts +11 -3
  65. package/dist/storage/s3.mjs +78 -15
  66. package/dist/taxonomies-1s5PaS_8.mjs +266 -0
  67. package/dist/transaction-Cn2rjY78.mjs +27 -0
  68. package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
  69. package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
  70. package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
  71. package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
  72. package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
  73. package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
  74. package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
  75. package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
  76. package/dist/version-BKXPsfmJ.mjs +6 -0
  77. package/package.json +53 -39
  78. package/src/astro/routes/PluginRegistry.tsx +21 -0
  79. package/src/astro/routes/admin.astro +99 -0
  80. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  81. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  82. package/src/astro/routes/api/admin/api-tokens/[id].ts +44 -0
  83. package/src/astro/routes/api/admin/api-tokens/index.ts +90 -0
  84. package/src/astro/routes/api/admin/briefing.ts +76 -0
  85. package/src/astro/routes/api/admin/bylines/[id]/index.ts +90 -0
  86. package/src/astro/routes/api/admin/bylines/index.ts +74 -0
  87. package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
  88. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  89. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  90. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  91. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  92. package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
  93. package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
  94. package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
  95. package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
  96. package/src/astro/routes/api/admin/context/diff.ts +35 -0
  97. package/src/astro/routes/api/admin/context/index.ts +69 -0
  98. package/src/astro/routes/api/admin/context/stale.ts +35 -0
  99. package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
  100. package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
  101. package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
  102. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +132 -0
  103. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  104. package/src/astro/routes/api/admin/oauth-clients/[id].ts +137 -0
  105. package/src/astro/routes/api/admin/oauth-clients/index.ts +95 -0
  106. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +91 -0
  107. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +91 -0
  108. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  109. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +98 -0
  110. package/src/astro/routes/api/admin/plugins/[id]/update.ts +154 -0
  111. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  112. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +62 -0
  113. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  114. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +135 -0
  115. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  116. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  117. package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
  118. package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
  119. package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
  120. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  121. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +62 -0
  122. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  123. package/src/astro/routes/api/admin/users/[id]/disable.ts +72 -0
  124. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  125. package/src/astro/routes/api/admin/users/[id]/index.ts +166 -0
  126. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  127. package/src/astro/routes/api/admin/users/index.ts +66 -0
  128. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  129. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  130. package/src/astro/routes/api/auth/invite/complete.ts +86 -0
  131. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  132. package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
  133. package/src/astro/routes/api/auth/logout.ts +40 -0
  134. package/src/astro/routes/api/auth/magic-link/send.ts +90 -0
  135. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  136. package/src/astro/routes/api/auth/me.ts +60 -0
  137. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +221 -0
  138. package/src/astro/routes/api/auth/oauth/[provider].ts +120 -0
  139. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  140. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  141. package/src/astro/routes/api/auth/passkey/options.ts +85 -0
  142. package/src/astro/routes/api/auth/passkey/register/options.ts +88 -0
  143. package/src/astro/routes/api/auth/passkey/register/verify.ts +119 -0
  144. package/src/astro/routes/api/auth/passkey/verify.ts +72 -0
  145. package/src/astro/routes/api/auth/signup/complete.ts +87 -0
  146. package/src/astro/routes/api/auth/signup/request.ts +89 -0
  147. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  148. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +310 -0
  149. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  150. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +68 -0
  151. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +77 -0
  152. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +42 -0
  153. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  154. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +100 -0
  155. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +64 -0
  156. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  157. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +129 -0
  158. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +143 -0
  159. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +50 -0
  160. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +69 -0
  161. package/src/astro/routes/api/content/[collection]/[id].ts +173 -0
  162. package/src/astro/routes/api/content/[collection]/index.ts +103 -0
  163. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  164. package/src/astro/routes/api/dashboard.ts +32 -0
  165. package/src/astro/routes/api/dev/emails.ts +36 -0
  166. package/src/astro/routes/api/health.ts +54 -0
  167. package/src/astro/routes/api/import/probe.ts +47 -0
  168. package/src/astro/routes/api/import/wordpress/analyze.ts +523 -0
  169. package/src/astro/routes/api/import/wordpress/execute.ts +330 -0
  170. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  171. package/src/astro/routes/api/import/wordpress/prepare.ts +212 -0
  172. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +425 -0
  173. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  174. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  175. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +399 -0
  176. package/src/astro/routes/api/manifest.ts +75 -0
  177. package/src/astro/routes/api/mcp.ts +125 -0
  178. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  179. package/src/astro/routes/api/media/[id].ts +145 -0
  180. package/src/astro/routes/api/media/file/[...key].ts +79 -0
  181. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +91 -0
  182. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  183. package/src/astro/routes/api/media/providers/index.ts +30 -0
  184. package/src/astro/routes/api/media/upload-url.ts +146 -0
  185. package/src/astro/routes/api/media.ts +204 -0
  186. package/src/astro/routes/api/menus/[name]/items.ts +206 -0
  187. package/src/astro/routes/api/menus/[name]/reorder.ts +79 -0
  188. package/src/astro/routes/api/menus/[name].ts +145 -0
  189. package/src/astro/routes/api/menus/index.ts +91 -0
  190. package/src/astro/routes/api/oauth/authorize.ts +430 -0
  191. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  192. package/src/astro/routes/api/oauth/device/code.ts +56 -0
  193. package/src/astro/routes/api/oauth/device/token.ts +70 -0
  194. package/src/astro/routes/api/oauth/register.ts +182 -0
  195. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  196. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  197. package/src/astro/routes/api/oauth/token.ts +195 -0
  198. package/src/astro/routes/api/openapi.json.ts +33 -0
  199. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +109 -0
  200. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  201. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  202. package/src/astro/routes/api/redirects/[id].ts +183 -0
  203. package/src/astro/routes/api/redirects/index.ts +100 -0
  204. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  205. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
  206. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +104 -0
  207. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +67 -0
  208. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +45 -0
  209. package/src/astro/routes/api/schema/collections/[slug]/index.ts +107 -0
  210. package/src/astro/routes/api/schema/collections/index.ts +61 -0
  211. package/src/astro/routes/api/schema/index.ts +109 -0
  212. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  213. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  214. package/src/astro/routes/api/search/enable.ts +64 -0
  215. package/src/astro/routes/api/search/index.ts +52 -0
  216. package/src/astro/routes/api/search/rebuild.ts +72 -0
  217. package/src/astro/routes/api/search/stats.ts +35 -0
  218. package/src/astro/routes/api/search/suggest.ts +50 -0
  219. package/src/astro/routes/api/sections/[slug].ts +203 -0
  220. package/src/astro/routes/api/sections/index.ts +107 -0
  221. package/src/astro/routes/api/settings/email.ts +150 -0
  222. package/src/astro/routes/api/settings.ts +116 -0
  223. package/src/astro/routes/api/setup/admin-verify.ts +122 -0
  224. package/src/astro/routes/api/setup/admin.ts +104 -0
  225. package/src/astro/routes/api/setup/dev-bypass.ts +200 -0
  226. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  227. package/src/astro/routes/api/setup/index.ts +128 -0
  228. package/src/astro/routes/api/setup/status.ts +122 -0
  229. package/src/astro/routes/api/snapshot.ts +76 -0
  230. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +232 -0
  231. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +131 -0
  232. package/src/astro/routes/api/taxonomies/index.ts +114 -0
  233. package/src/astro/routes/api/themes/preview.ts +78 -0
  234. package/src/astro/routes/api/typegen.ts +114 -0
  235. package/src/astro/routes/api/well-known/auth.ts +71 -0
  236. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +48 -0
  237. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +39 -0
  238. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +114 -0
  239. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +213 -0
  240. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +126 -0
  241. package/src/astro/routes/api/widget-areas/[name].ts +135 -0
  242. package/src/astro/routes/api/widget-areas/index.ts +149 -0
  243. package/src/astro/routes/api/widget-components.ts +22 -0
  244. package/src/astro/routes/robots.txt.ts +81 -0
  245. package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
  246. package/src/astro/routes/sitemap.xml.ts +92 -0
  247. package/src/components/Break.astro +45 -0
  248. package/src/components/Button.astro +71 -0
  249. package/src/components/Buttons.astro +49 -0
  250. package/src/components/Code.astro +59 -0
  251. package/src/components/Columns.astro +59 -0
  252. package/src/components/CommentForm.astro +315 -0
  253. package/src/components/Comments.astro +232 -0
  254. package/src/components/Cover.astro +128 -0
  255. package/src/components/DinewayBodyEnd.astro +32 -0
  256. package/src/components/DinewayBodyStart.astro +32 -0
  257. package/src/components/DinewayHead.astro +61 -0
  258. package/src/components/DinewayImage.astro +178 -0
  259. package/src/components/DinewayMedia.astro +167 -0
  260. package/src/components/Embed.astro +128 -0
  261. package/src/components/File.astro +122 -0
  262. package/src/components/Gallery.astro +93 -0
  263. package/src/components/HtmlBlock.astro +33 -0
  264. package/src/components/Image.astro +178 -0
  265. package/src/components/InlineEditor.astro +27 -0
  266. package/src/components/InlinePortableTextEditor.tsx +1937 -0
  267. package/src/components/LiveSearch.astro +614 -0
  268. package/src/components/PortableText.astro +51 -0
  269. package/src/components/Pullquote.astro +51 -0
  270. package/src/components/Table.astro +135 -0
  271. package/src/components/WidgetArea.astro +22 -0
  272. package/src/components/WidgetRenderer.astro +72 -0
  273. package/src/components/index.ts +106 -0
  274. package/src/components/marks/Link.astro +31 -0
  275. package/src/components/marks/StrikeThrough.astro +7 -0
  276. package/src/components/marks/Subscript.astro +7 -0
  277. package/src/components/marks/Superscript.astro +7 -0
  278. package/src/components/marks/Underline.astro +7 -0
  279. package/src/components/marks.ts +19 -0
  280. package/src/components/widgets/Archives.astro +65 -0
  281. package/src/components/widgets/Categories.astro +35 -0
  282. package/src/components/widgets/RecentPosts.astro +51 -0
  283. package/src/components/widgets/Search.astro +18 -0
  284. package/src/components/widgets/Tags.astro +38 -0
  285. package/src/ui.ts +75 -0
  286. package/LICENSE +0 -9
  287. /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
  288. /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
  289. /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
  290. /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
  291. /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
  292. /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
  293. /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
  294. /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
  295. /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
  296. /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 };