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,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_dineway/api/setup/admin/verify
|
|
3
|
+
*
|
|
4
|
+
* Complete admin creation by verifying the passkey registration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { Role, secureCompare } from "@dineway-ai/auth";
|
|
12
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
13
|
+
import { verifyRegistrationResponse, registerPasskey } from "@dineway-ai/auth/passkey";
|
|
14
|
+
|
|
15
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
16
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
17
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
18
|
+
import { setupAdminVerifyBody } from "#api/schemas.js";
|
|
19
|
+
import { createChallengeStore } from "#auth/challenge-store.js";
|
|
20
|
+
import { getPasskeyConfig } from "#auth/passkey-config.js";
|
|
21
|
+
import { SETUP_NONCE_COOKIE } from "#auth/setup-nonce.js";
|
|
22
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
23
|
+
|
|
24
|
+
export const POST: APIRoute = async ({ cookies, request, locals }) => {
|
|
25
|
+
const { dineway } = locals;
|
|
26
|
+
|
|
27
|
+
if (!dineway?.db) {
|
|
28
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Check if setup is already complete
|
|
33
|
+
const options = new OptionsRepository(dineway.db);
|
|
34
|
+
const setupComplete = await options.get("dineway:setup_complete");
|
|
35
|
+
|
|
36
|
+
if (setupComplete === true || setupComplete === "true") {
|
|
37
|
+
return apiError("SETUP_COMPLETE", "Setup already complete", 400);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if any users exist
|
|
41
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
42
|
+
const userCount = await adapter.countUsers();
|
|
43
|
+
|
|
44
|
+
if (userCount > 0) {
|
|
45
|
+
return apiError("ADMIN_EXISTS", "Admin user already exists", 400);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Get setup state
|
|
49
|
+
const setupState = await options.get<{
|
|
50
|
+
step?: string;
|
|
51
|
+
email?: string;
|
|
52
|
+
name?: string | null;
|
|
53
|
+
nonce?: string;
|
|
54
|
+
}>("dineway:setup_state");
|
|
55
|
+
|
|
56
|
+
if (!setupState || setupState.step !== "admin") {
|
|
57
|
+
return apiError("INVALID_STATE", "Invalid setup state. Please restart setup.", 400);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cookieNonce = cookies.get(SETUP_NONCE_COOKIE)?.value;
|
|
61
|
+
if (!setupState.nonce || !cookieNonce || !secureCompare(cookieNonce, setupState.nonce)) {
|
|
62
|
+
return apiError(
|
|
63
|
+
"INVALID_STATE",
|
|
64
|
+
"Setup session expired or tampered with. Please restart the admin step.",
|
|
65
|
+
400,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!setupState.email) {
|
|
70
|
+
return apiError("INVALID_STATE", "Invalid setup state. Please restart setup.", 400);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Parse request body
|
|
74
|
+
const body = await parseBody(request, setupAdminVerifyBody);
|
|
75
|
+
if (isParseError(body)) return body;
|
|
76
|
+
|
|
77
|
+
// Get passkey config
|
|
78
|
+
const url = new URL(request.url);
|
|
79
|
+
const siteName = (await options.get<string>("dineway:site_title")) ?? undefined;
|
|
80
|
+
const siteUrl = getPublicOrigin(url, dineway?.config);
|
|
81
|
+
const passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);
|
|
82
|
+
|
|
83
|
+
// Verify the registration response
|
|
84
|
+
const challengeStore = createChallengeStore(dineway.db);
|
|
85
|
+
|
|
86
|
+
const verified = await verifyRegistrationResponse(
|
|
87
|
+
passkeyConfig,
|
|
88
|
+
body.credential,
|
|
89
|
+
challengeStore,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Create the admin user
|
|
93
|
+
const user = await adapter.createUser({
|
|
94
|
+
email: setupState.email,
|
|
95
|
+
name: setupState.name ?? null,
|
|
96
|
+
role: Role.ADMIN,
|
|
97
|
+
emailVerified: false, // No email verification for first user
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Register the passkey
|
|
101
|
+
await registerPasskey(adapter, user.id, verified, "Setup passkey");
|
|
102
|
+
|
|
103
|
+
// Mark setup as complete
|
|
104
|
+
await options.set("dineway:setup_complete", true);
|
|
105
|
+
|
|
106
|
+
// Clean up setup state and the session nonce cookie
|
|
107
|
+
await options.delete("dineway:setup_state");
|
|
108
|
+
cookies.delete(SETUP_NONCE_COOKIE, { path: "/_dineway/" });
|
|
109
|
+
|
|
110
|
+
return apiSuccess({
|
|
111
|
+
success: true,
|
|
112
|
+
user: {
|
|
113
|
+
id: user.id,
|
|
114
|
+
email: user.email,
|
|
115
|
+
name: user.name,
|
|
116
|
+
role: user.role,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return handleError(error, "Failed to verify admin setup", "SETUP_VERIFY_ERROR");
|
|
121
|
+
}
|
|
122
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_dineway/api/setup/admin
|
|
3
|
+
*
|
|
4
|
+
* Step 3 of setup: Start admin creation by returning passkey registration options
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { generateToken } from "@dineway-ai/auth";
|
|
12
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
13
|
+
import { generateRegistrationOptions } from "@dineway-ai/auth/passkey";
|
|
14
|
+
|
|
15
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
16
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
17
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
18
|
+
import { setupAdminBody } from "#api/schemas.js";
|
|
19
|
+
import { createChallengeStore } from "#auth/challenge-store.js";
|
|
20
|
+
import { getPasskeyConfig } from "#auth/passkey-config.js";
|
|
21
|
+
import { SETUP_NONCE_COOKIE, SETUP_NONCE_MAX_AGE_SECONDS } from "#auth/setup-nonce.js";
|
|
22
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
23
|
+
|
|
24
|
+
export const POST: APIRoute = async ({ cookies, request, locals }) => {
|
|
25
|
+
const { dineway } = locals;
|
|
26
|
+
|
|
27
|
+
if (!dineway?.db) {
|
|
28
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Check if setup is already complete
|
|
33
|
+
const options = new OptionsRepository(dineway.db);
|
|
34
|
+
const setupComplete = await options.get("dineway:setup_complete");
|
|
35
|
+
|
|
36
|
+
if (setupComplete === true || setupComplete === "true") {
|
|
37
|
+
return apiError("SETUP_COMPLETE", "Setup already complete", 400);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if any users exist
|
|
41
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
42
|
+
const userCount = await adapter.countUsers();
|
|
43
|
+
|
|
44
|
+
if (userCount > 0) {
|
|
45
|
+
return apiError("ADMIN_EXISTS", "Admin user already exists", 400);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse request body
|
|
49
|
+
const body = await parseBody(request, setupAdminBody);
|
|
50
|
+
if (isParseError(body)) return body;
|
|
51
|
+
|
|
52
|
+
const nonce = generateToken();
|
|
53
|
+
|
|
54
|
+
// Get passkey config
|
|
55
|
+
const url = new URL(request.url);
|
|
56
|
+
const siteName = (await options.get<string>("dineway:site_title")) ?? undefined;
|
|
57
|
+
const siteUrl = getPublicOrigin(url, dineway?.config);
|
|
58
|
+
const passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);
|
|
59
|
+
|
|
60
|
+
// Generate registration options
|
|
61
|
+
const challengeStore = createChallengeStore(dineway.db);
|
|
62
|
+
|
|
63
|
+
// Create a temporary user object for registration options
|
|
64
|
+
// (not persisted until passkey is verified)
|
|
65
|
+
const tempUser = {
|
|
66
|
+
id: `setup-${Date.now()}`, // Temporary ID
|
|
67
|
+
email: body.email.toLowerCase(),
|
|
68
|
+
name: body.name || null,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const registrationOptions = await generateRegistrationOptions(
|
|
72
|
+
passkeyConfig,
|
|
73
|
+
tempUser,
|
|
74
|
+
[], // No existing credentials
|
|
75
|
+
challengeStore,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Store the nonce with the setup state. The verify endpoint compares
|
|
79
|
+
// it to an HttpOnly cookie so the admin step stays bound to this browser.
|
|
80
|
+
await options.set("dineway:setup_state", {
|
|
81
|
+
step: "admin",
|
|
82
|
+
email: body.email.toLowerCase(),
|
|
83
|
+
name: body.name || null,
|
|
84
|
+
tempUserId: tempUser.id,
|
|
85
|
+
nonce,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const publicOrigin = new URL(siteUrl);
|
|
89
|
+
cookies.set(SETUP_NONCE_COOKIE, nonce, {
|
|
90
|
+
path: "/_dineway/",
|
|
91
|
+
httpOnly: true,
|
|
92
|
+
sameSite: "strict",
|
|
93
|
+
secure: publicOrigin.protocol === "https:",
|
|
94
|
+
maxAge: SETUP_NONCE_MAX_AGE_SECONDS,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return apiSuccess({
|
|
98
|
+
success: true,
|
|
99
|
+
options: registrationOptions,
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return handleError(error, "Failed to create admin", "SETUP_ADMIN_ERROR");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_dineway/api/setup/dev-bypass
|
|
3
|
+
* GET /_dineway/api/setup/dev-bypass
|
|
4
|
+
*
|
|
5
|
+
* Development-only endpoint to bypass the setup wizard.
|
|
6
|
+
* Runs migrations, creates a dev admin user, and marks setup complete.
|
|
7
|
+
*
|
|
8
|
+
* ONLY available when import.meta.env.DEV is true.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* - GET with redirect: /_dineway/api/setup/dev-bypass?redirect=/_dineway/admin
|
|
12
|
+
* - POST for API: Returns JSON with setup info
|
|
13
|
+
*
|
|
14
|
+
* For agent/browser testing, navigate to:
|
|
15
|
+
* /_dineway/api/setup/dev-bypass?redirect=/_dineway/admin
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { APIRoute } from "astro";
|
|
19
|
+
|
|
20
|
+
export const prerender = false;
|
|
21
|
+
|
|
22
|
+
import { ulid } from "ulidx";
|
|
23
|
+
|
|
24
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
25
|
+
import { escapeHtml } from "#api/escape.js";
|
|
26
|
+
import { handleApiTokenCreate } from "#api/handlers/api-tokens.js";
|
|
27
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
28
|
+
import { isSafeRedirect } from "#api/redirect.js";
|
|
29
|
+
import { runMigrations } from "#db/migrations/runner.js";
|
|
30
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
31
|
+
import { applySeed } from "#seed/apply.js";
|
|
32
|
+
import { loadSeed } from "#seed/load.js";
|
|
33
|
+
import { validateSeed } from "#seed/validate.js";
|
|
34
|
+
|
|
35
|
+
// RBAC role levels (matching @dineway-ai/auth)
|
|
36
|
+
const ROLE_ADMIN = 50;
|
|
37
|
+
|
|
38
|
+
const DEV_USER_EMAIL = "dev@dineway.local";
|
|
39
|
+
const DEV_USER_NAME = "Dev Admin";
|
|
40
|
+
const DEV_SITE_TITLE = "Dineway Dev Site";
|
|
41
|
+
|
|
42
|
+
async function handleDevBypass(context: Parameters<APIRoute>[0]): Promise<Response> {
|
|
43
|
+
// CRITICAL: Only allow in development mode
|
|
44
|
+
if (!import.meta.env.DEV) {
|
|
45
|
+
return apiError("FORBIDDEN", "Dev bypass is only available in development mode", 403);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { locals, url, session } = context;
|
|
49
|
+
const { dineway } = locals;
|
|
50
|
+
|
|
51
|
+
if (!dineway?.db) {
|
|
52
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// Run migrations
|
|
57
|
+
const migrations = await runMigrations(dineway.db);
|
|
58
|
+
console.log("[setup-dev-bypass] Migrations applied:", migrations.applied);
|
|
59
|
+
|
|
60
|
+
// Apply seed (user seed or built-in default)
|
|
61
|
+
const seed = await loadSeed();
|
|
62
|
+
const validation = validateSeed(seed);
|
|
63
|
+
if (validation.valid) {
|
|
64
|
+
const seedResult = await applySeed(dineway.db, seed, {
|
|
65
|
+
includeContent: true,
|
|
66
|
+
onConflict: "skip",
|
|
67
|
+
storage: dineway.storage ?? undefined,
|
|
68
|
+
});
|
|
69
|
+
console.log(
|
|
70
|
+
`[setup-dev-bypass] Seed applied: ${seedResult.collections.created} collections, ${seedResult.fields.created} fields`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const options = new OptionsRepository(dineway.db);
|
|
75
|
+
|
|
76
|
+
// Find or create dev user (direct DB access to avoid @dineway-ai/auth import issues in dev)
|
|
77
|
+
const existingUser = await dineway.db
|
|
78
|
+
.selectFrom("users")
|
|
79
|
+
.selectAll()
|
|
80
|
+
.where("email", "=", DEV_USER_EMAIL)
|
|
81
|
+
.executeTakeFirst();
|
|
82
|
+
|
|
83
|
+
let user: { id: string; email: string; name: string; role: number };
|
|
84
|
+
let userCreated = false;
|
|
85
|
+
|
|
86
|
+
if (!existingUser) {
|
|
87
|
+
const now = new Date().toISOString();
|
|
88
|
+
const newUser = {
|
|
89
|
+
id: ulid(),
|
|
90
|
+
email: DEV_USER_EMAIL,
|
|
91
|
+
name: DEV_USER_NAME,
|
|
92
|
+
role: ROLE_ADMIN,
|
|
93
|
+
email_verified: 1,
|
|
94
|
+
created_at: now,
|
|
95
|
+
updated_at: now,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await dineway.db.insertInto("users").values(newUser).execute();
|
|
99
|
+
|
|
100
|
+
user = {
|
|
101
|
+
id: newUser.id,
|
|
102
|
+
email: newUser.email,
|
|
103
|
+
name: newUser.name,
|
|
104
|
+
role: newUser.role,
|
|
105
|
+
};
|
|
106
|
+
userCreated = true;
|
|
107
|
+
console.log("[setup-dev-bypass] Created dev admin user:", user.email);
|
|
108
|
+
} else {
|
|
109
|
+
user = {
|
|
110
|
+
id: existingUser.id,
|
|
111
|
+
email: existingUser.email,
|
|
112
|
+
name: existingUser.name || DEV_USER_NAME,
|
|
113
|
+
role: existingUser.role,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Set site title if not already set
|
|
118
|
+
const existingTitle = await options.get("dineway:site_title");
|
|
119
|
+
if (!existingTitle) {
|
|
120
|
+
await options.set("dineway:site_title", DEV_SITE_TITLE);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Store canonical site URL (used by magic-link/recovery emails)
|
|
124
|
+
await options.set("dineway:site_url", getPublicOrigin(url, dineway?.config));
|
|
125
|
+
|
|
126
|
+
// Mark setup complete
|
|
127
|
+
await options.set("dineway:setup_complete", true);
|
|
128
|
+
|
|
129
|
+
// Create session
|
|
130
|
+
if (session) {
|
|
131
|
+
session.set("user", { id: user.id });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Optionally create a PAT token (?token=1) for headless/CLI testing.
|
|
135
|
+
let token: string | undefined;
|
|
136
|
+
if (url.searchParams.has("token")) {
|
|
137
|
+
const result = await handleApiTokenCreate(dineway.db, user.id, {
|
|
138
|
+
name: "dev-bypass-token",
|
|
139
|
+
scopes: [
|
|
140
|
+
"content:read",
|
|
141
|
+
"content:write",
|
|
142
|
+
"media:read",
|
|
143
|
+
"media:write",
|
|
144
|
+
"schema:read",
|
|
145
|
+
"schema:write",
|
|
146
|
+
"admin",
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
if (result.success) {
|
|
150
|
+
token = result.data.token;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check for redirect parameter
|
|
155
|
+
const redirect = url.searchParams.get("redirect");
|
|
156
|
+
|
|
157
|
+
if (redirect) {
|
|
158
|
+
// Validate redirect is a safe local path (prevent open redirect via //evil.com or /\evil.com)
|
|
159
|
+
if (!isSafeRedirect(redirect)) {
|
|
160
|
+
return apiError("INVALID_REDIRECT", "Redirect must be a local path", 400);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Return an HTML page with meta-refresh redirect
|
|
164
|
+
// This ensures the session is fully saved before redirect
|
|
165
|
+
const safeRedirect = escapeHtml(redirect);
|
|
166
|
+
const html = `<!DOCTYPE html>
|
|
167
|
+
<html>
|
|
168
|
+
<head>
|
|
169
|
+
<meta http-equiv="refresh" content="0;url=${safeRedirect}">
|
|
170
|
+
</head>
|
|
171
|
+
<body>Redirecting...</body>
|
|
172
|
+
</html>`;
|
|
173
|
+
return new Response(html, {
|
|
174
|
+
status: 200,
|
|
175
|
+
headers: { "Content-Type": "text/html" },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Return JSON response
|
|
180
|
+
return apiSuccess({
|
|
181
|
+
success: true,
|
|
182
|
+
message: "Dev setup complete",
|
|
183
|
+
migrations: migrations.applied,
|
|
184
|
+
userCreated,
|
|
185
|
+
user: {
|
|
186
|
+
id: user.id,
|
|
187
|
+
email: user.email,
|
|
188
|
+
name: user.name,
|
|
189
|
+
role: user.role,
|
|
190
|
+
},
|
|
191
|
+
...(token ? { token } : {}),
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return handleError(error, "Dev bypass failed", "DEV_BYPASS_ERROR");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Support both GET and POST
|
|
199
|
+
export const GET: APIRoute = handleDevBypass;
|
|
200
|
+
export const POST: APIRoute = handleDevBypass;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_dineway/api/setup/dev-reset
|
|
3
|
+
*
|
|
4
|
+
* Development-only endpoint to reset setup state for testing.
|
|
5
|
+
* Clears the setup_complete flag and deletes all users,
|
|
6
|
+
* returning the site to the pre-setup state.
|
|
7
|
+
*
|
|
8
|
+
* ONLY available when import.meta.env.DEV is true.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
16
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
17
|
+
|
|
18
|
+
export const POST: APIRoute = async ({ locals }) => {
|
|
19
|
+
if (!import.meta.env.DEV) {
|
|
20
|
+
return apiError("FORBIDDEN", "Dev reset is only available in development mode", 403);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { dineway } = locals;
|
|
24
|
+
|
|
25
|
+
if (!dineway?.db) {
|
|
26
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const options = new OptionsRepository(dineway.db);
|
|
31
|
+
|
|
32
|
+
await options.delete("dineway:setup_complete");
|
|
33
|
+
await options.delete("dineway:setup_state");
|
|
34
|
+
await dineway.db.deleteFrom("users").execute();
|
|
35
|
+
|
|
36
|
+
return apiSuccess({ success: true });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return handleError(error, "Dev reset failed", "DEV_RESET_ERROR");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /_dineway/api/setup
|
|
3
|
+
*
|
|
4
|
+
* Executes the setup wizard - applies seed file and marks setup complete
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
13
|
+
import { getPublicOrigin } from "#api/public-url.js";
|
|
14
|
+
import { setupBody } from "#api/schemas.js";
|
|
15
|
+
import { getAuthMode } from "#auth/mode.js";
|
|
16
|
+
import { runMigrations } from "#db/migrations/runner.js";
|
|
17
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
18
|
+
import { applySeed } from "#seed/apply.js";
|
|
19
|
+
import { loadSeed } from "#seed/load.js";
|
|
20
|
+
import { validateSeed } from "#seed/validate.js";
|
|
21
|
+
|
|
22
|
+
export const POST: APIRoute = async ({ request, url, locals }) => {
|
|
23
|
+
const { dineway } = locals;
|
|
24
|
+
|
|
25
|
+
if (!dineway?.db) {
|
|
26
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Guard: reject if setup has already been completed.
|
|
31
|
+
// The options table may not exist on first-ever setup (pre-migration),
|
|
32
|
+
// so a query failure means setup hasn't run yet — allow it to proceed.
|
|
33
|
+
try {
|
|
34
|
+
const options = new OptionsRepository(dineway.db);
|
|
35
|
+
const setupComplete = await options.get("dineway:setup_complete");
|
|
36
|
+
|
|
37
|
+
if (setupComplete === true || setupComplete === "true") {
|
|
38
|
+
return apiError("ALREADY_CONFIGURED", "Setup has already been completed", 409);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Options table doesn't exist yet — first-ever setup, allow it
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Parse request body
|
|
45
|
+
const body = await parseBody(request, setupBody);
|
|
46
|
+
if (isParseError(body)) return body;
|
|
47
|
+
|
|
48
|
+
// 1. Run core migrations
|
|
49
|
+
try {
|
|
50
|
+
await runMigrations(dineway.db);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return handleError(error, "Failed to run database migrations", "MIGRATION_ERROR");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Load seed file (user seed or built-in default)
|
|
56
|
+
const seed = await loadSeed();
|
|
57
|
+
|
|
58
|
+
// 3. Override seed settings with form values
|
|
59
|
+
seed.settings = {
|
|
60
|
+
...seed.settings,
|
|
61
|
+
title: body.title,
|
|
62
|
+
tagline: body.tagline,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 4. Apply seed
|
|
66
|
+
const validation = validateSeed(seed);
|
|
67
|
+
if (!validation.valid) {
|
|
68
|
+
return apiError("INVALID_SEED", `Invalid seed file: ${validation.errors.join(", ")}`, 400);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let result;
|
|
72
|
+
try {
|
|
73
|
+
result = await applySeed(dineway.db, seed, {
|
|
74
|
+
includeContent: body.includeContent,
|
|
75
|
+
onConflict: "skip",
|
|
76
|
+
storage: dineway.storage ?? undefined,
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return handleError(error, "Failed to apply seed", "SEED_ERROR");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 5. Store setup state
|
|
83
|
+
// In external auth mode, mark setup complete immediately (first user to login becomes admin)
|
|
84
|
+
// In passkey mode, setup_complete is set after admin user is created
|
|
85
|
+
const authMode = getAuthMode(dineway.config);
|
|
86
|
+
const useExternalAuth = authMode.type === "external";
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const options = new OptionsRepository(dineway.db);
|
|
90
|
+
|
|
91
|
+
// Store the canonical site URL from the setup request.
|
|
92
|
+
// Keep the first value so later unauthenticated setup calls cannot
|
|
93
|
+
// rewrite the public origin during the setup window.
|
|
94
|
+
const siteUrl = getPublicOrigin(url, dineway.config);
|
|
95
|
+
await options.setIfAbsent("dineway:site_url", siteUrl);
|
|
96
|
+
|
|
97
|
+
if (useExternalAuth) {
|
|
98
|
+
// External auth mode: mark setup complete now
|
|
99
|
+
// First user to log in via external provider will become admin
|
|
100
|
+
await options.set("dineway:setup_complete", true);
|
|
101
|
+
await options.set("dineway:site_title", body.title);
|
|
102
|
+
if (body.tagline) {
|
|
103
|
+
await options.set("dineway:site_tagline", body.tagline);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Passkey mode: store state for next step (admin creation)
|
|
107
|
+
await options.set("dineway:setup_state", {
|
|
108
|
+
step: "site_complete",
|
|
109
|
+
title: body.title,
|
|
110
|
+
tagline: body.tagline,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error("Failed to save setup state:", error);
|
|
115
|
+
// Non-fatal - continue anyway
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 6. Return success with result
|
|
119
|
+
return apiSuccess({
|
|
120
|
+
success: true,
|
|
121
|
+
// In external auth mode, setup is complete - redirect to admin
|
|
122
|
+
setupComplete: useExternalAuth,
|
|
123
|
+
result,
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return handleError(error, "Setup failed", "SETUP_ERROR");
|
|
127
|
+
}
|
|
128
|
+
};
|