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,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Streamable HTTP endpoint
|
|
3
|
+
*
|
|
4
|
+
* Exposes an MCP server at /_dineway/api/mcp using the Streamable HTTP
|
|
5
|
+
* transport (Web Standard variant). The server runs stateless — each
|
|
6
|
+
* request creates a fresh transport, so no session tracking is needed.
|
|
7
|
+
* Authentication is handled by the existing Dineway auth middleware.
|
|
8
|
+
*
|
|
9
|
+
* POST /_dineway/api/mcp — JSON-RPC tool calls
|
|
10
|
+
* GET /_dineway/api/mcp — SSE stream (not used in stateless mode)
|
|
11
|
+
* DELETE /_dineway/api/mcp — Session close (not used in stateless mode)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
15
|
+
import type { APIRoute } from "astro";
|
|
16
|
+
|
|
17
|
+
import { apiError } from "#api/error.js";
|
|
18
|
+
import { createMcpServer } from "#mcp/server.js";
|
|
19
|
+
|
|
20
|
+
export const prerender = false;
|
|
21
|
+
|
|
22
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
23
|
+
const { dineway, user } = locals;
|
|
24
|
+
|
|
25
|
+
if (!dineway) {
|
|
26
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!user) {
|
|
30
|
+
return apiError("UNAUTHORIZED", "Authentication required", 401);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const server = createMcpServer();
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
37
|
+
// Stateless: no session management
|
|
38
|
+
sessionIdGenerator: undefined,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await server.connect(transport);
|
|
42
|
+
|
|
43
|
+
return await transport.handleRequest(request, {
|
|
44
|
+
authInfo: {
|
|
45
|
+
token: "",
|
|
46
|
+
clientId: "dineway-admin",
|
|
47
|
+
scopes: [],
|
|
48
|
+
extra: {
|
|
49
|
+
dineway,
|
|
50
|
+
userId: user.id,
|
|
51
|
+
userRole: user.role,
|
|
52
|
+
tokenScopes: locals.tokenScopes,
|
|
53
|
+
authToken: locals.authToken,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("[MCP]", error);
|
|
59
|
+
await server.close().catch(() => {});
|
|
60
|
+
|
|
61
|
+
return new Response(
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
jsonrpc: "2.0",
|
|
64
|
+
error: {
|
|
65
|
+
code: -32603,
|
|
66
|
+
message: "Internal server error",
|
|
67
|
+
},
|
|
68
|
+
id: null,
|
|
69
|
+
}),
|
|
70
|
+
{
|
|
71
|
+
status: 500,
|
|
72
|
+
headers: {
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
"Cache-Control": "private, no-store",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* GET — SSE stream. Not used in stateless mode.
|
|
83
|
+
*/
|
|
84
|
+
export const GET: APIRoute = async () => {
|
|
85
|
+
return new Response(
|
|
86
|
+
JSON.stringify({
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
error: {
|
|
89
|
+
code: -32000,
|
|
90
|
+
message: "Method not allowed. This is a stateless MCP endpoint — use POST.",
|
|
91
|
+
},
|
|
92
|
+
id: null,
|
|
93
|
+
}),
|
|
94
|
+
{
|
|
95
|
+
status: 405,
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"Cache-Control": "private, no-store",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* DELETE — Session close. Not used in stateless mode.
|
|
106
|
+
*/
|
|
107
|
+
export const DELETE: APIRoute = async () => {
|
|
108
|
+
return new Response(
|
|
109
|
+
JSON.stringify({
|
|
110
|
+
jsonrpc: "2.0",
|
|
111
|
+
error: {
|
|
112
|
+
code: -32000,
|
|
113
|
+
message: "Method not allowed. This is a stateless MCP endpoint.",
|
|
114
|
+
},
|
|
115
|
+
id: null,
|
|
116
|
+
}),
|
|
117
|
+
{
|
|
118
|
+
status: 405,
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
"Cache-Control": "private, no-store",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirm media upload endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/media/{id}/confirm
|
|
5
|
+
*
|
|
6
|
+
* Confirms that the client has successfully uploaded the file to storage.
|
|
7
|
+
* Marks the media record as ready and optionally updates metadata.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
import { MediaRepository } from "dineway";
|
|
12
|
+
|
|
13
|
+
import { requirePerm } from "#api/authorize.js";
|
|
14
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
|
+
import { isParseError, parseOptionalBody } from "#api/parse.js";
|
|
16
|
+
import { mediaConfirmBody } from "#api/schemas.js";
|
|
17
|
+
import type { MediaItem } from "#types";
|
|
18
|
+
|
|
19
|
+
export const prerender = false;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Add URL to media item (relative URL for portability)
|
|
23
|
+
*/
|
|
24
|
+
function addUrlToMedia(item: MediaItem): MediaItem & { url: string } {
|
|
25
|
+
return {
|
|
26
|
+
...item,
|
|
27
|
+
url: `/_dineway/api/media/file/${item.storageKey}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Confirm upload completion
|
|
33
|
+
*/
|
|
34
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
35
|
+
const { dineway, user } = locals;
|
|
36
|
+
const { id } = params;
|
|
37
|
+
|
|
38
|
+
const denied = requirePerm(user, "media:upload");
|
|
39
|
+
if (denied) return denied;
|
|
40
|
+
|
|
41
|
+
if (!id) {
|
|
42
|
+
return apiError("INVALID_REQUEST", "Media ID is required", 400);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!dineway?.db) {
|
|
46
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const body = await parseOptionalBody(request, mediaConfirmBody, {});
|
|
51
|
+
if (isParseError(body)) return body;
|
|
52
|
+
|
|
53
|
+
const repo = new MediaRepository(dineway.db);
|
|
54
|
+
|
|
55
|
+
// Get the media item first to check status
|
|
56
|
+
const existing = await repo.findById(id);
|
|
57
|
+
if (!existing) {
|
|
58
|
+
return apiError("NOT_FOUND", `Media item not found: ${id}`, 404);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (existing.status !== "pending") {
|
|
62
|
+
return apiError("INVALID_STATE", `Media item is not pending: ${existing.status}`, 400);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Optionally verify the file exists in storage
|
|
66
|
+
if (dineway.storage) {
|
|
67
|
+
const exists = await dineway.storage.exists(existing.storageKey);
|
|
68
|
+
if (!exists) {
|
|
69
|
+
// Mark as failed
|
|
70
|
+
await repo.markFailed(id);
|
|
71
|
+
return apiError("FILE_NOT_FOUND", "File was not uploaded to storage", 400);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Confirm the upload
|
|
76
|
+
const item = await repo.confirmUpload(id, {
|
|
77
|
+
size: body.size,
|
|
78
|
+
width: body.width,
|
|
79
|
+
height: body.height,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!item) {
|
|
83
|
+
return apiError("CONFIRM_FAILED", "Failed to confirm upload", 500);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Add URL to the response (relative URL for portability)
|
|
87
|
+
const itemWithUrl = addUrlToMedia(item);
|
|
88
|
+
|
|
89
|
+
return apiSuccess({ item: itemWithUrl });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return handleError(error, "Failed to confirm upload", "CONFIRM_ERROR");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single media item endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/media/:id - Get media item
|
|
5
|
+
* PUT /_dineway/api/media/:id - Update media metadata
|
|
6
|
+
* DELETE /_dineway/api/media/:id - Delete media item
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
import { requireOwnerPerm, requirePerm } from "#api/authorize.js";
|
|
12
|
+
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
13
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
|
+
import { mediaUpdateBody } from "#api/schemas.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get media item
|
|
20
|
+
*/
|
|
21
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
22
|
+
const { dineway, user } = locals;
|
|
23
|
+
const { id } = params;
|
|
24
|
+
|
|
25
|
+
const readDenied = requirePerm(user, "media:read");
|
|
26
|
+
if (readDenied) return readDenied;
|
|
27
|
+
|
|
28
|
+
if (!id) {
|
|
29
|
+
return apiError("INVALID_REQUEST", "Media ID required", 400);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!dineway?.handleMediaGet) {
|
|
33
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = await dineway.handleMediaGet(id);
|
|
37
|
+
return unwrapResult(result);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Update media metadata
|
|
42
|
+
*
|
|
43
|
+
* Authors can edit their own media; editors+ can edit any.
|
|
44
|
+
*/
|
|
45
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
46
|
+
const { dineway, user } = locals;
|
|
47
|
+
const { id } = params;
|
|
48
|
+
|
|
49
|
+
// Minimum permission gate — ownership checked below
|
|
50
|
+
const editDenied = requirePerm(user, "media:edit_own");
|
|
51
|
+
if (editDenied) return editDenied;
|
|
52
|
+
|
|
53
|
+
if (!id) {
|
|
54
|
+
return apiError("INVALID_REQUEST", "Media ID required", 400);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!dineway?.handleMediaGet || !dineway?.handleMediaUpdate) {
|
|
58
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Fetch media item for ownership check
|
|
63
|
+
const getResult = await dineway.handleMediaGet(id);
|
|
64
|
+
if (!getResult.success || !getResult.data?.item) {
|
|
65
|
+
return apiError("NOT_FOUND", "Media item not found", 404);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const media = getResult.data.item;
|
|
69
|
+
|
|
70
|
+
// Ownership check: authors can edit own, editors+ can edit any
|
|
71
|
+
const ownerDenied = requireOwnerPerm(user, media.authorId, "media:edit_own", "media:edit_any");
|
|
72
|
+
if (ownerDenied) return ownerDenied;
|
|
73
|
+
|
|
74
|
+
const body = await parseBody(request, mediaUpdateBody);
|
|
75
|
+
if (isParseError(body)) return body;
|
|
76
|
+
|
|
77
|
+
const result = await dineway.handleMediaUpdate(id, {
|
|
78
|
+
alt: body.alt,
|
|
79
|
+
caption: body.caption,
|
|
80
|
+
width: body.width,
|
|
81
|
+
height: body.height,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return unwrapResult(result);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return handleError(error, "Failed to update media", "MEDIA_UPDATE_ERROR");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Delete media item
|
|
92
|
+
*
|
|
93
|
+
* Authors can delete their own media; editors+ can delete any.
|
|
94
|
+
*/
|
|
95
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
96
|
+
const { dineway, user } = locals;
|
|
97
|
+
const { id } = params;
|
|
98
|
+
|
|
99
|
+
// Minimum permission gate — ownership checked below
|
|
100
|
+
const deleteDenied = requirePerm(user, "media:delete_own");
|
|
101
|
+
if (deleteDenied) return deleteDenied;
|
|
102
|
+
|
|
103
|
+
if (!id) {
|
|
104
|
+
return apiError("INVALID_REQUEST", "Media ID required", 400);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!dineway?.handleMediaGet || !dineway?.handleMediaDelete) {
|
|
108
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Fetch media item for ownership check and storage key
|
|
113
|
+
const getResult = await dineway.handleMediaGet(id);
|
|
114
|
+
if (!getResult.success || !getResult.data?.item) {
|
|
115
|
+
return apiError("NOT_FOUND", "Media item not found", 404);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const media = getResult.data.item;
|
|
119
|
+
|
|
120
|
+
// Ownership check: authors can delete own, editors+ can delete any
|
|
121
|
+
const ownerDenied = requireOwnerPerm(
|
|
122
|
+
user,
|
|
123
|
+
media.authorId,
|
|
124
|
+
"media:delete_own",
|
|
125
|
+
"media:delete_any",
|
|
126
|
+
);
|
|
127
|
+
if (ownerDenied) return ownerDenied;
|
|
128
|
+
|
|
129
|
+
// Delete file from storage via the storage adapter
|
|
130
|
+
if (dineway.storage) {
|
|
131
|
+
try {
|
|
132
|
+
await dineway.storage.delete(media.storageKey);
|
|
133
|
+
} catch {
|
|
134
|
+
// Best-effort — continue with database deletion
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Delete from database
|
|
139
|
+
const result = await dineway.handleMediaDelete(id);
|
|
140
|
+
|
|
141
|
+
return unwrapResult(result);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return handleError(error, "Failed to delete media", "MEDIA_DELETE_ERROR");
|
|
144
|
+
}
|
|
145
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve uploaded media files
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/media/file/:key - Serve file from storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { apiError, handleError } from "#api/error.js";
|
|
10
|
+
|
|
11
|
+
export const prerender = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Content types that are safe to display inline (simple raster/vector images, video, audio).
|
|
15
|
+
* Everything else gets Content-Disposition: attachment to prevent script execution.
|
|
16
|
+
*/
|
|
17
|
+
const SAFE_INLINE_TYPES = new Set([
|
|
18
|
+
"image/jpeg",
|
|
19
|
+
"image/png",
|
|
20
|
+
"image/gif",
|
|
21
|
+
"image/webp",
|
|
22
|
+
"image/avif",
|
|
23
|
+
"image/x-icon",
|
|
24
|
+
"video/mp4",
|
|
25
|
+
"video/webm",
|
|
26
|
+
"audio/mpeg",
|
|
27
|
+
"audio/wav",
|
|
28
|
+
"audio/ogg",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
32
|
+
const { key } = params;
|
|
33
|
+
const { dineway } = locals;
|
|
34
|
+
|
|
35
|
+
if (!key) {
|
|
36
|
+
return apiError("NOT_FOUND", "File not found", 404);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!dineway?.storage) {
|
|
40
|
+
return apiError("NOT_CONFIGURED", "Storage not configured", 500);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await dineway.storage.download(key);
|
|
45
|
+
|
|
46
|
+
const headers: Record<string, string> = {
|
|
47
|
+
"Content-Type": result.contentType,
|
|
48
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
49
|
+
"X-Content-Type-Options": "nosniff",
|
|
50
|
+
// Sandbox CSP on all user-uploaded content — prevents script execution
|
|
51
|
+
// even for SVGs navigated to directly or content types that support scripting.
|
|
52
|
+
"Content-Security-Policy":
|
|
53
|
+
"sandbox; default-src 'none'; img-src 'self'; style-src 'unsafe-inline'",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (result.size) {
|
|
57
|
+
headers["Content-Length"] = String(result.size);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Safe image/media types can render inline; everything else (SVG, PDF,
|
|
61
|
+
// HTML, JS, etc.) must be downloaded to prevent stored XSS.
|
|
62
|
+
if (SAFE_INLINE_TYPES.has(result.contentType)) {
|
|
63
|
+
headers["Content-Disposition"] = "inline";
|
|
64
|
+
} else {
|
|
65
|
+
headers["Content-Disposition"] = "attachment";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return new Response(result.body, { status: 200, headers });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Check if it's a "not found" error
|
|
71
|
+
if (
|
|
72
|
+
error instanceof Error &&
|
|
73
|
+
(error.message.includes("not found") || error.message.includes("NOT_FOUND"))
|
|
74
|
+
) {
|
|
75
|
+
return apiError("NOT_FOUND", "File not found", 404);
|
|
76
|
+
}
|
|
77
|
+
return handleError(error, "Failed to serve file", "FILE_SERVE_ERROR");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Provider Item Endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/media/providers/:providerId/:itemId - Get single item
|
|
5
|
+
* DELETE /_dineway/api/media/providers/:providerId/:itemId - Delete item
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { requirePerm } from "#api/authorize.js";
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get a single media item from a provider
|
|
17
|
+
*/
|
|
18
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
const denied = requirePerm(user, "media:read");
|
|
21
|
+
if (denied) return denied;
|
|
22
|
+
const { providerId, itemId } = params;
|
|
23
|
+
|
|
24
|
+
if (!providerId || !itemId) {
|
|
25
|
+
return apiError("INVALID_REQUEST", "Provider ID and Item ID required", 400);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!dineway?.getMediaProvider) {
|
|
29
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const provider = dineway.getMediaProvider(providerId);
|
|
33
|
+
if (!provider) {
|
|
34
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!provider.get) {
|
|
38
|
+
return apiError(
|
|
39
|
+
"NOT_SUPPORTED",
|
|
40
|
+
`Provider "${providerId}" does not support getting individual items`,
|
|
41
|
+
400,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const item = await provider.get(itemId);
|
|
47
|
+
|
|
48
|
+
if (!item) {
|
|
49
|
+
return apiError("NOT_FOUND", "Item not found", 404);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return apiSuccess({ item });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return handleError(error, "Failed to get item from provider", "PROVIDER_GET_ERROR");
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Delete a media item from a provider
|
|
60
|
+
*/
|
|
61
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
62
|
+
const { dineway, user } = locals;
|
|
63
|
+
const denied = requirePerm(user, "media:delete_any");
|
|
64
|
+
if (denied) return denied;
|
|
65
|
+
const { providerId, itemId } = params;
|
|
66
|
+
|
|
67
|
+
if (!providerId || !itemId) {
|
|
68
|
+
return apiError("INVALID_REQUEST", "Provider ID and Item ID required", 400);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!dineway?.getMediaProvider) {
|
|
72
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const provider = dineway.getMediaProvider(providerId);
|
|
76
|
+
if (!provider) {
|
|
77
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!provider.delete) {
|
|
81
|
+
return apiError("NOT_SUPPORTED", `Provider "${providerId}" does not support deletion`, 400);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await provider.delete(itemId);
|
|
86
|
+
|
|
87
|
+
return apiSuccess({ deleted: true });
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return handleError(error, "Failed to delete item from provider", "PROVIDER_DELETE_ERROR");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Provider List/Upload Endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/media/providers/:providerId - List media from provider
|
|
5
|
+
* POST /_dineway/api/media/providers/:providerId - Upload to provider
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { requirePerm } from "#api/authorize.js";
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* List media from a specific provider
|
|
17
|
+
*/
|
|
18
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
const { providerId } = params;
|
|
21
|
+
|
|
22
|
+
const readDenied = requirePerm(user, "media:read");
|
|
23
|
+
if (readDenied) return readDenied;
|
|
24
|
+
|
|
25
|
+
if (!providerId) {
|
|
26
|
+
return apiError("INVALID_REQUEST", "Provider ID required", 400);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!dineway?.getMediaProvider) {
|
|
30
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const provider = dineway.getMediaProvider(providerId);
|
|
34
|
+
if (!provider) {
|
|
35
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const url = new URL(request.url);
|
|
39
|
+
const cursor = url.searchParams.get("cursor") || undefined;
|
|
40
|
+
const limit = url.searchParams.get("limit")
|
|
41
|
+
? parseInt(url.searchParams.get("limit")!, 10)
|
|
42
|
+
: undefined;
|
|
43
|
+
const query = url.searchParams.get("query") || undefined;
|
|
44
|
+
const mimeType = url.searchParams.get("mimeType") || undefined;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await provider.list({
|
|
48
|
+
cursor,
|
|
49
|
+
limit,
|
|
50
|
+
query,
|
|
51
|
+
mimeType,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return apiSuccess({
|
|
55
|
+
items: result.items,
|
|
56
|
+
nextCursor: result.nextCursor,
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return handleError(error, "Failed to list media from provider", "PROVIDER_LIST_ERROR");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Upload media to a specific provider
|
|
65
|
+
*/
|
|
66
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
67
|
+
const { dineway, user } = locals;
|
|
68
|
+
const { providerId } = params;
|
|
69
|
+
|
|
70
|
+
const uploadDenied = requirePerm(user, "media:upload");
|
|
71
|
+
if (uploadDenied) return uploadDenied;
|
|
72
|
+
|
|
73
|
+
if (!providerId) {
|
|
74
|
+
return apiError("INVALID_REQUEST", "Provider ID required", 400);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!dineway?.getMediaProvider) {
|
|
78
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const provider = dineway.getMediaProvider(providerId);
|
|
82
|
+
if (!provider) {
|
|
83
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!provider.upload) {
|
|
87
|
+
return apiError("NOT_SUPPORTED", `Provider "${providerId}" does not support uploads`, 400);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const formData = await request.formData();
|
|
92
|
+
const fileEntry = formData.get("file");
|
|
93
|
+
const file = fileEntry instanceof File ? fileEntry : null;
|
|
94
|
+
const altEntry = formData.get("alt");
|
|
95
|
+
const alt = typeof altEntry === "string" ? altEntry : null;
|
|
96
|
+
|
|
97
|
+
if (!file) {
|
|
98
|
+
return apiError("NO_FILE", "No file provided", 400);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const item = await provider.upload({
|
|
102
|
+
file,
|
|
103
|
+
filename: file.name,
|
|
104
|
+
alt: alt || undefined,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return apiSuccess({ item }, 201);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return handleError(error, "Failed to upload to provider", "PROVIDER_UPLOAD_ERROR");
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Providers List Endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/media/providers - List all configured media providers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, apiSuccess } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* List all configured media providers
|
|
16
|
+
*/
|
|
17
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
18
|
+
const { dineway, user } = locals;
|
|
19
|
+
|
|
20
|
+
const denied = requirePerm(user, "media:read");
|
|
21
|
+
if (denied) return denied;
|
|
22
|
+
|
|
23
|
+
if (!dineway?.getMediaProviderList) {
|
|
24
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const providers = dineway.getMediaProviderList();
|
|
28
|
+
|
|
29
|
+
return apiSuccess({ items: providers });
|
|
30
|
+
};
|