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,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin review request resolution
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/admin/review-requests/:id/resolve - Approve or reject a review request
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleReviewRequestResolve } from "#api/handlers/review-requests.js";
|
|
12
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
13
|
+
import { logReviewRouteActivity, resolveReviewRouteActor } from "#api/review-route-helpers.js";
|
|
14
|
+
import { reviewRequestResolveBody } from "#api/schemas.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
const { id } = params;
|
|
21
|
+
|
|
22
|
+
if (!id) {
|
|
23
|
+
return apiError("VALIDATION_ERROR", "Review request ID required", 400);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dbErr = requireDb(dineway?.db);
|
|
27
|
+
if (dbErr) return dbErr;
|
|
28
|
+
|
|
29
|
+
const denied = requirePerm(user, "content:edit_any");
|
|
30
|
+
if (denied) return denied;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const body = await parseBody(request, reviewRequestResolveBody);
|
|
34
|
+
if (isParseError(body)) return body;
|
|
35
|
+
|
|
36
|
+
const result = await handleReviewRequestResolve(dineway.db, id, {
|
|
37
|
+
decision: body.decision,
|
|
38
|
+
reviewNote: body.reviewNote,
|
|
39
|
+
actor: resolveReviewRouteActor(locals),
|
|
40
|
+
});
|
|
41
|
+
if (!result.success) return unwrapResult(result);
|
|
42
|
+
|
|
43
|
+
await logReviewRouteActivity(dineway.db, locals, {
|
|
44
|
+
actionType: body.decision === "approved" ? "review.approved" : "review.rejected",
|
|
45
|
+
item: result.data.item,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return unwrapResult(result);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return handleError(error, "Failed to resolve review request", "REVIEW_REQUEST_RESOLVE_ERROR");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin review requests
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/review-requests - List review requests
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleReviewRequestList } from "#api/handlers/review-requests.js";
|
|
12
|
+
import { isParseError, parseQuery } from "#api/parse.js";
|
|
13
|
+
import { reviewRequestListQuery } from "#api/schemas.js";
|
|
14
|
+
|
|
15
|
+
export const prerender = false;
|
|
16
|
+
|
|
17
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
18
|
+
const { dineway, user } = locals;
|
|
19
|
+
|
|
20
|
+
const dbErr = requireDb(dineway?.db);
|
|
21
|
+
if (dbErr) return dbErr;
|
|
22
|
+
|
|
23
|
+
const denied = requirePerm(user, "content:edit_any");
|
|
24
|
+
if (denied) return denied;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const query = parseQuery(url, reviewRequestListQuery);
|
|
28
|
+
if (isParseError(query)) return query;
|
|
29
|
+
|
|
30
|
+
const result = await handleReviewRequestList(dineway.db, query);
|
|
31
|
+
return unwrapResult(result);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return handleError(error, "Failed to list review requests", "REVIEW_REQUEST_LIST_ERROR");
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme marketplace detail proxy endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/themes/marketplace/:id - Get theme details
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleThemeGetDetail } from "#api/index.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
16
|
+
const { dineway, user } = locals;
|
|
17
|
+
const { id } = params;
|
|
18
|
+
|
|
19
|
+
if (!dineway?.db) {
|
|
20
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const denied = requirePerm(user, "plugins:read");
|
|
24
|
+
if (denied) return denied;
|
|
25
|
+
|
|
26
|
+
if (!id) {
|
|
27
|
+
return apiError("INVALID_REQUEST", "Theme ID required", 400);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await handleThemeGetDetail(dineway.config.marketplace, id);
|
|
31
|
+
|
|
32
|
+
return unwrapResult(result);
|
|
33
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme marketplace thumbnail proxy
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/themes/marketplace/:id/thumbnail - Proxy thumbnail from marketplace
|
|
5
|
+
*
|
|
6
|
+
* Avoids CORS/auth issues when the marketplace service is behind
|
|
7
|
+
* external auth or on a different origin.
|
|
8
|
+
* The admin UI uses this instead of linking directly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
|
|
13
|
+
import { requirePerm } from "#api/authorize.js";
|
|
14
|
+
import { apiError } from "#api/error.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
export const GET: APIRoute = async ({ params, url, locals }) => {
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
const { id } = params;
|
|
21
|
+
|
|
22
|
+
if (!dineway?.db) {
|
|
23
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const denied = requirePerm(user, "plugins:read");
|
|
27
|
+
if (denied) return denied;
|
|
28
|
+
|
|
29
|
+
const marketplaceUrl = dineway.config.marketplace;
|
|
30
|
+
if (!marketplaceUrl || !id) {
|
|
31
|
+
return apiError("NOT_CONFIGURED", "Marketplace not configured", 400);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const width = url.searchParams.get("w");
|
|
35
|
+
const target = new URL(`/api/v1/themes/${encodeURIComponent(id)}/thumbnail`, marketplaceUrl);
|
|
36
|
+
if (width) target.searchParams.set("w", width);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const resp = await fetch(target.href);
|
|
40
|
+
if (!resp.ok) {
|
|
41
|
+
// Allowlist: only forward Content-Type from upstream.
|
|
42
|
+
// Never copy all upstream headers (denylist approach leaks
|
|
43
|
+
// headers we haven't anticipated).
|
|
44
|
+
return new Response(resp.body, {
|
|
45
|
+
status: resp.status,
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": resp.headers.get("Content-Type") ?? "application/octet-stream",
|
|
48
|
+
"Cache-Control": "private, no-store",
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Response(resp.body, {
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": resp.headers.get("Content-Type") ?? "image/png",
|
|
56
|
+
"Cache-Control": "private, no-store",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
return apiError("PROXY_ERROR", "Failed to fetch thumbnail", 502);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme marketplace search proxy endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/themes/marketplace - Search marketplace themes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleThemeSearch } from "#api/index.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
16
|
+
const { dineway, user } = locals;
|
|
17
|
+
|
|
18
|
+
if (!dineway?.db) {
|
|
19
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const denied = requirePerm(user, "plugins:read");
|
|
23
|
+
if (denied) return denied;
|
|
24
|
+
|
|
25
|
+
const query = url.searchParams.get("q") ?? undefined;
|
|
26
|
+
const keyword = url.searchParams.get("keyword") ?? undefined;
|
|
27
|
+
const sortParam = url.searchParams.get("sort");
|
|
28
|
+
const validSorts = new Set(["name", "created", "updated"]);
|
|
29
|
+
let sort: "name" | "created" | "updated" | undefined;
|
|
30
|
+
if (sortParam && validSorts.has(sortParam)) {
|
|
31
|
+
sort = sortParam as "name" | "created" | "updated"; // eslint-disable-line typescript-eslint(no-unsafe-type-assertion) -- validated by Set.has()
|
|
32
|
+
}
|
|
33
|
+
const cursor = url.searchParams.get("cursor") ?? undefined;
|
|
34
|
+
const limitParam = url.searchParams.get("limit");
|
|
35
|
+
const limit = limitParam ? Math.min(Math.max(1, parseInt(limitParam, 10) || 50), 100) : undefined;
|
|
36
|
+
|
|
37
|
+
const result = await handleThemeSearch(dineway.config.marketplace, query, {
|
|
38
|
+
keyword,
|
|
39
|
+
sort,
|
|
40
|
+
cursor,
|
|
41
|
+
limit,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return unwrapResult(result);
|
|
45
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User disable endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/admin/users/:id/disable - Soft-disable a user
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Role } from "@dineway-ai/auth";
|
|
8
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
import { withTransaction } from "#db/transaction.js";
|
|
13
|
+
|
|
14
|
+
export const prerender = false;
|
|
15
|
+
|
|
16
|
+
export const POST: APIRoute = async ({ params, locals }) => {
|
|
17
|
+
const { dineway, user: currentUser } = locals;
|
|
18
|
+
|
|
19
|
+
if (!dineway?.db) {
|
|
20
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!currentUser || currentUser.role < Role.ADMIN) {
|
|
24
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { id } = params;
|
|
28
|
+
|
|
29
|
+
if (!id) {
|
|
30
|
+
return apiError("VALIDATION_ERROR", "User ID required", 400);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Prevent disabling self
|
|
34
|
+
if (id === currentUser.id) {
|
|
35
|
+
return apiError("VALIDATION_ERROR", "Cannot disable your own account", 400);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
return await withTransaction(dineway.db, async (trx) => {
|
|
40
|
+
const adapter = createKyselyAdapter(trx);
|
|
41
|
+
|
|
42
|
+
// Get target user
|
|
43
|
+
const targetUser = await adapter.getUserById(id);
|
|
44
|
+
if (!targetUser) {
|
|
45
|
+
return apiError("NOT_FOUND", "User not found", 404);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if this would leave no active admins
|
|
49
|
+
if (targetUser.role === Role.ADMIN) {
|
|
50
|
+
const adminCount = await adapter.countAdmins();
|
|
51
|
+
if (adminCount <= 1) {
|
|
52
|
+
return apiError(
|
|
53
|
+
"VALIDATION_ERROR",
|
|
54
|
+
"Cannot disable the last admin. Promote another user first.",
|
|
55
|
+
400,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Disable user
|
|
61
|
+
await adapter.updateUser(id, { disabled: true });
|
|
62
|
+
|
|
63
|
+
// SEC-43: Revoke all OAuth tokens for the disabled user.
|
|
64
|
+
// Without this, existing refresh tokens remain valid for up to 90 days.
|
|
65
|
+
await trx.deleteFrom("_dineway_oauth_tokens").where("user_id", "=", id).execute();
|
|
66
|
+
|
|
67
|
+
return apiSuccess({ success: true });
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return handleError(error, "Failed to disable user", "USER_DISABLE_ERROR");
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User enable endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/admin/users/:id/enable - Re-enable a disabled user
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Role } from "@dineway-ai/auth";
|
|
8
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
export const POST: APIRoute = async ({ params, locals }) => {
|
|
16
|
+
const { dineway, user } = locals;
|
|
17
|
+
|
|
18
|
+
if (!dineway?.db) {
|
|
19
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!user || user.role < Role.ADMIN) {
|
|
23
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
27
|
+
|
|
28
|
+
const { id } = params;
|
|
29
|
+
|
|
30
|
+
if (!id) {
|
|
31
|
+
return apiError("VALIDATION_ERROR", "User ID required", 400);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Get target user
|
|
36
|
+
const targetUser = await adapter.getUserById(id);
|
|
37
|
+
if (!targetUser) {
|
|
38
|
+
return apiError("NOT_FOUND", "User not found", 404);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Enable user
|
|
42
|
+
await adapter.updateUser(id, { disabled: false });
|
|
43
|
+
|
|
44
|
+
return apiSuccess({ success: true });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return handleError(error, "Failed to enable user", "USER_ENABLE_ERROR");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User management detail endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/users/:id - Get user details
|
|
5
|
+
* PUT /_dineway/api/admin/users/:id - Update user
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Role } from "@dineway-ai/auth";
|
|
9
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
|
|
12
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
13
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
|
+
import { userUpdateBody } from "#api/schemas.js";
|
|
15
|
+
import { withTransaction } from "#db/transaction.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
20
|
+
const { dineway, user: currentUser } = locals;
|
|
21
|
+
|
|
22
|
+
if (!dineway?.db) {
|
|
23
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!currentUser || currentUser.role < Role.ADMIN) {
|
|
27
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
31
|
+
|
|
32
|
+
const { id } = params;
|
|
33
|
+
|
|
34
|
+
if (!id) {
|
|
35
|
+
return apiError("MISSING_PARAM", "User ID required", 400);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = await adapter.getUserWithDetails(id);
|
|
40
|
+
|
|
41
|
+
if (!result) {
|
|
42
|
+
return apiError("NOT_FOUND", "User not found", 404);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Transform for JSON serialization
|
|
46
|
+
const item = {
|
|
47
|
+
id: result.user.id,
|
|
48
|
+
email: result.user.email,
|
|
49
|
+
name: result.user.name,
|
|
50
|
+
avatarUrl: result.user.avatarUrl,
|
|
51
|
+
role: result.user.role,
|
|
52
|
+
emailVerified: result.user.emailVerified,
|
|
53
|
+
disabled: result.user.disabled,
|
|
54
|
+
createdAt: result.user.createdAt.toISOString(),
|
|
55
|
+
updatedAt: result.user.updatedAt.toISOString(),
|
|
56
|
+
lastLogin: result.lastLogin?.toISOString() ?? null,
|
|
57
|
+
credentials: result.credentials.map((c) => ({
|
|
58
|
+
id: c.id,
|
|
59
|
+
name: c.name,
|
|
60
|
+
deviceType: c.deviceType,
|
|
61
|
+
createdAt: c.createdAt.toISOString(),
|
|
62
|
+
lastUsedAt: c.lastUsedAt.toISOString(),
|
|
63
|
+
})),
|
|
64
|
+
oauthAccounts: result.oauthAccounts.map((a) => ({
|
|
65
|
+
provider: a.provider,
|
|
66
|
+
createdAt: a.createdAt.toISOString(),
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return apiSuccess({ item });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return handleError(error, "Failed to get user details", "USER_DETAIL_ERROR");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
77
|
+
const { dineway, user: currentUser } = locals;
|
|
78
|
+
|
|
79
|
+
if (!dineway?.db) {
|
|
80
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!currentUser || currentUser.role < Role.ADMIN) {
|
|
84
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
88
|
+
|
|
89
|
+
const { id } = params;
|
|
90
|
+
|
|
91
|
+
if (!id) {
|
|
92
|
+
return apiError("MISSING_PARAM", "User ID required", 400);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Get target user
|
|
97
|
+
const targetUser = await adapter.getUserById(id);
|
|
98
|
+
if (!targetUser) {
|
|
99
|
+
return apiError("NOT_FOUND", "User not found", 404);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const body = await parseBody(request, userUpdateBody);
|
|
103
|
+
if (isParseError(body)) return body;
|
|
104
|
+
|
|
105
|
+
// Role is already validated as RoleLevel by Zod schema
|
|
106
|
+
const role = body.role;
|
|
107
|
+
|
|
108
|
+
// Prevent editing own role (security: prevents self-demotion/lockout)
|
|
109
|
+
if (role !== undefined && id === currentUser.id) {
|
|
110
|
+
return apiError("SELF_ROLE_CHANGE", "Cannot change your own role", 400);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check email uniqueness if changing email
|
|
114
|
+
if (body.email && body.email !== targetUser.email) {
|
|
115
|
+
const existing = await adapter.getUserByEmail(body.email);
|
|
116
|
+
if (existing) {
|
|
117
|
+
return apiError("EMAIL_IN_USE", "Email already in use", 409);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return await withTransaction(dineway.db, async (trx) => {
|
|
122
|
+
const txAdapter = createKyselyAdapter(trx);
|
|
123
|
+
const latestTargetUser = await txAdapter.getUserById(id);
|
|
124
|
+
if (!latestTargetUser) {
|
|
125
|
+
return apiError("NOT_FOUND", "User not found", 404);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (role !== undefined && latestTargetUser.role === Role.ADMIN && role < Role.ADMIN) {
|
|
129
|
+
const adminCount = await txAdapter.countAdmins();
|
|
130
|
+
if (adminCount <= 1) {
|
|
131
|
+
return apiError(
|
|
132
|
+
"VALIDATION_ERROR",
|
|
133
|
+
"Cannot demote the last admin. Promote another user first.",
|
|
134
|
+
400,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Update user
|
|
140
|
+
await txAdapter.updateUser(id, {
|
|
141
|
+
name: body.name,
|
|
142
|
+
email: body.email,
|
|
143
|
+
role,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Fetch updated user
|
|
147
|
+
const updated = await txAdapter.getUserById(id);
|
|
148
|
+
|
|
149
|
+
return apiSuccess({
|
|
150
|
+
item: {
|
|
151
|
+
id: updated!.id,
|
|
152
|
+
email: updated!.email,
|
|
153
|
+
name: updated!.name,
|
|
154
|
+
avatarUrl: updated!.avatarUrl,
|
|
155
|
+
role: updated!.role,
|
|
156
|
+
emailVerified: updated!.emailVerified,
|
|
157
|
+
disabled: updated!.disabled,
|
|
158
|
+
createdAt: updated!.createdAt.toISOString(),
|
|
159
|
+
updatedAt: updated!.updatedAt.toISOString(),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return handleError(error, "Failed to update user", "USER_UPDATE_ERROR");
|
|
165
|
+
}
|
|
166
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send recovery link endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/admin/users/:id/send-recovery
|
|
5
|
+
*
|
|
6
|
+
* Admin-initiated account recovery — sends a recovery magic link to the user's email.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Role, sendMagicLink, type MagicLinkConfig } from "@dineway-ai/auth";
|
|
10
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
|
|
13
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
14
|
+
import { getSiteBaseUrl } from "#api/site-url.js";
|
|
15
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
export const POST: APIRoute = async ({ request, params, locals }) => {
|
|
20
|
+
const { dineway, user } = locals;
|
|
21
|
+
|
|
22
|
+
if (!dineway?.db) {
|
|
23
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!user || user.role < Role.ADMIN) {
|
|
27
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { id } = params;
|
|
31
|
+
|
|
32
|
+
if (!id) {
|
|
33
|
+
return apiError("VALIDATION_ERROR", "User ID required", 400);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
38
|
+
|
|
39
|
+
// Verify target user exists
|
|
40
|
+
const targetUser = await adapter.getUserById(id);
|
|
41
|
+
if (!targetUser) {
|
|
42
|
+
return apiError("NOT_FOUND", "User not found", 404);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if email pipeline is available
|
|
46
|
+
if (!dineway.email?.isAvailable()) {
|
|
47
|
+
return apiError(
|
|
48
|
+
"EMAIL_NOT_CONFIGURED",
|
|
49
|
+
"Email is not configured. Recovery links require an email provider.",
|
|
50
|
+
503,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build config using stored site URL (not request Host header)
|
|
55
|
+
const options = new OptionsRepository(dineway.db);
|
|
56
|
+
const baseUrl = await getSiteBaseUrl(dineway.db, request);
|
|
57
|
+
const siteName = (await options.get<string>("dineway:site_title")) ?? "Dineway";
|
|
58
|
+
|
|
59
|
+
const config: MagicLinkConfig = {
|
|
60
|
+
baseUrl,
|
|
61
|
+
siteName,
|
|
62
|
+
email: (message) => dineway.email!.send(message, "system"),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Send recovery link
|
|
66
|
+
await sendMagicLink(config, adapter, targetUser.email, "recovery");
|
|
67
|
+
|
|
68
|
+
return apiSuccess({ success: true, message: "Recovery link sent" });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return handleError(error, "Failed to send recovery link", "RECOVERY_SEND_ERROR");
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User management list endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/users - List users with search, filter, pagination
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Role } from "@dineway-ai/auth";
|
|
8
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
import { isParseError, parseQuery } from "#api/parse.js";
|
|
13
|
+
import { usersListQuery } from "#api/schemas.js";
|
|
14
|
+
|
|
15
|
+
export const prerender = false;
|
|
16
|
+
|
|
17
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
18
|
+
const { dineway, user } = locals;
|
|
19
|
+
|
|
20
|
+
if (!dineway?.db) {
|
|
21
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!user || user.role < Role.ADMIN) {
|
|
25
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Parse query parameters
|
|
32
|
+
const query = parseQuery(url, usersListQuery);
|
|
33
|
+
if (isParseError(query)) return query;
|
|
34
|
+
|
|
35
|
+
// Fetch users
|
|
36
|
+
const result = await adapter.getUsers({
|
|
37
|
+
search: query.search,
|
|
38
|
+
role: query.role ? parseInt(query.role, 10) : undefined,
|
|
39
|
+
cursor: query.cursor,
|
|
40
|
+
limit: query.limit,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Transform dates to ISO strings for JSON serialization
|
|
44
|
+
const items = result.items.map((u) => ({
|
|
45
|
+
id: u.id,
|
|
46
|
+
email: u.email,
|
|
47
|
+
name: u.name,
|
|
48
|
+
avatarUrl: u.avatarUrl,
|
|
49
|
+
role: u.role,
|
|
50
|
+
emailVerified: u.emailVerified,
|
|
51
|
+
disabled: u.disabled,
|
|
52
|
+
createdAt: u.createdAt.toISOString(),
|
|
53
|
+
updatedAt: u.updatedAt.toISOString(),
|
|
54
|
+
lastLogin: u.lastLogin?.toISOString() ?? null,
|
|
55
|
+
credentialCount: u.credentialCount,
|
|
56
|
+
oauthProviders: u.oauthProviders,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
return apiSuccess({
|
|
60
|
+
items,
|
|
61
|
+
nextCursor: result.nextCursor,
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return handleError(error, "Failed to list users", "USER_LIST_ERROR");
|
|
65
|
+
}
|
|
66
|
+
};
|