dineway 0.1.4 → 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 +49 -38
- package/src/astro/routes/admin.astro +25 -9
- package/src/astro/routes/api/admin/api-tokens/[id].ts +4 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +24 -2
- package/src/astro/routes/api/admin/briefing.ts +76 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -0
- package/src/astro/routes/api/admin/bylines/index.ts +2 -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 +58 -17
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +28 -1
- package/src/astro/routes/api/admin/oauth-clients/index.ts +25 -1
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +51 -1
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +98 -3
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +72 -1
- 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/users/[id]/disable.ts +26 -23
- package/src/astro/routes/api/admin/users/[id]/index.ts +41 -21
- package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
- package/src/astro/routes/api/auth/passkey/options.ts +2 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
- package/src/astro/routes/api/auth/signup/request.ts +20 -8
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +16 -2
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +16 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +9 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +45 -1
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +12 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +24 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +3 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +20 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +13 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +36 -0
- package/src/astro/routes/api/content/[collection]/index.ts +48 -4
- package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
- package/src/astro/routes/api/health.ts +54 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +2 -10
- package/src/astro/routes/api/import/wordpress/execute.ts +40 -6
- package/src/astro/routes/api/import/wordpress/prepare.ts +36 -5
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +33 -1
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +3 -3
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +57 -15
- package/src/astro/routes/api/manifest.ts +13 -1
- package/src/astro/routes/api/mcp.ts +1 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
- package/src/astro/routes/api/media/upload-url.ts +11 -2
- package/src/astro/routes/api/media.ts +9 -7
- package/src/astro/routes/api/menus/[name]/items.ts +124 -5
- package/src/astro/routes/api/menus/[name]/reorder.ts +47 -1
- package/src/astro/routes/api/menus/[name].ts +84 -4
- package/src/astro/routes/api/menus/index.ts +46 -2
- package/src/astro/routes/api/oauth/authorize.ts +21 -8
- package/src/astro/routes/api/oauth/device/code.ts +2 -1
- package/src/astro/routes/api/oauth/device/token.ts +2 -1
- package/src/astro/routes/api/oauth/register.ts +182 -0
- package/src/astro/routes/api/oauth/token.ts +18 -7
- package/src/astro/routes/api/openapi.json.ts +3 -2
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +21 -4
- package/src/astro/routes/api/redirects/[id].ts +103 -4
- package/src/astro/routes/api/redirects/index.ts +50 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +28 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +15 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +13 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +27 -0
- package/src/astro/routes/api/schema/collections/index.ts +14 -0
- package/src/astro/routes/api/search/index.ts +1 -0
- package/src/astro/routes/api/search/suggest.ts +1 -0
- package/src/astro/routes/api/sections/[slug].ts +123 -4
- package/src/astro/routes/api/sections/index.ts +57 -2
- package/src/astro/routes/api/settings.ts +51 -2
- package/src/astro/routes/api/setup/admin-verify.ts +25 -5
- package/src/astro/routes/api/setup/admin.ts +16 -8
- package/src/astro/routes/api/setup/index.ts +3 -2
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +141 -4
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +64 -2
- package/src/astro/routes/api/taxonomies/index.ts +57 -2
- package/src/astro/routes/api/well-known/auth.ts +3 -1
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +8 -5
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +58 -16
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +124 -38
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +66 -20
- package/src/astro/routes/api/widget-areas/[name].ts +55 -7
- package/src/astro/routes/api/widget-areas/index.ts +56 -6
- package/src/components/DinewayHead.astro +15 -7
- package/src/components/DinewayMedia.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +1 -1
- package/src/components/Table.astro +68 -41
- package/src/components/index.ts +2 -12
- package/src/components/marks.ts +19 -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
|
@@ -8,22 +8,33 @@
|
|
|
8
8
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
10
10
|
import { ContentRepository, SchemaRegistry } from "dineway";
|
|
11
|
+
import { z } from "zod";
|
|
11
12
|
|
|
12
13
|
import { requirePerm } from "#api/authorize.js";
|
|
13
14
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
|
+
import {
|
|
16
|
+
ensureWorkflowHitlRouteRequest,
|
|
17
|
+
hitlRequiredRouteError,
|
|
18
|
+
resolveHitlRouteActor,
|
|
19
|
+
} from "#api/hitl-route-helpers.js";
|
|
14
20
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
21
|
import { wpPluginExecuteBody } from "#api/schemas.js";
|
|
16
22
|
import { BylineRepository } from "#db/repositories/byline.js";
|
|
17
23
|
import { getSource } from "#import/index.js";
|
|
18
|
-
import {
|
|
24
|
+
import { resolveAndValidateExternalUrl, SsrfError } from "#import/ssrf.js";
|
|
19
25
|
import type { ImportConfig, ImportResult, NormalizedItem } from "#import/types.js";
|
|
20
26
|
import { resolveImportByline } from "#import/utils.js";
|
|
21
27
|
import type { FieldType } from "#schema/types.js";
|
|
28
|
+
import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
|
|
22
29
|
import type { DinewayHandlers, DinewayManifest } from "#types";
|
|
23
30
|
import { slugify } from "#utils/slugify.js";
|
|
24
31
|
|
|
25
32
|
export const prerender = false;
|
|
26
33
|
|
|
34
|
+
const wpPluginExecuteRouteBody = wpPluginExecuteBody.extend({
|
|
35
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
27
38
|
export interface WpPluginImportConfig extends ImportConfig {
|
|
28
39
|
/** Author mappings (WP author login -> Dineway user ID) */
|
|
29
40
|
authorMappings?: Record<string, string | null>;
|
|
@@ -46,12 +57,12 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
try {
|
|
49
|
-
const body = await parseBody(request,
|
|
60
|
+
const body = await parseBody(request, wpPluginExecuteRouteBody);
|
|
50
61
|
if (isParseError(body)) return body;
|
|
51
62
|
|
|
52
|
-
// SSRF: reject internal/private network targets
|
|
63
|
+
// SSRF: reject internal/private network targets before import work starts.
|
|
53
64
|
try {
|
|
54
|
-
|
|
65
|
+
await resolveAndValidateExternalUrl(body.url);
|
|
55
66
|
} catch (e) {
|
|
56
67
|
const msg = e instanceof SsrfError ? e.message : "Invalid URL";
|
|
57
68
|
return apiError("SSRF_BLOCKED", msg, 400);
|
|
@@ -78,16 +89,39 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
78
89
|
console.log("[WP Plugin Import] Starting import for:", body.url);
|
|
79
90
|
console.log("[WP Plugin Import] Post types:", postTypes);
|
|
80
91
|
|
|
92
|
+
const sourceInput = { type: "url", url: body.url, token: body.token } as const;
|
|
93
|
+
const actor = resolveHitlRouteActor(locals);
|
|
94
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
95
|
+
db: dineway.db,
|
|
96
|
+
handlers: dineway,
|
|
97
|
+
});
|
|
98
|
+
let items: AsyncIterable<NormalizedItem> | Iterable<NormalizedItem>;
|
|
99
|
+
|
|
100
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
101
|
+
const fetchedItems = await collectPluginItems(
|
|
102
|
+
source.fetchContent(sourceInput, { postTypes, includeDrafts: true }),
|
|
103
|
+
);
|
|
104
|
+
const action = await new WordPressImportHitlPayloadBuilder().buildPluginExecuteRequest({
|
|
105
|
+
siteUrl: body.url,
|
|
106
|
+
config,
|
|
107
|
+
items: fetchedItems,
|
|
108
|
+
});
|
|
109
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
110
|
+
actor: actor.identity,
|
|
111
|
+
hitlRequestId: body.hitlRequestId,
|
|
112
|
+
action,
|
|
113
|
+
});
|
|
114
|
+
if (!decision.allowed) {
|
|
115
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
116
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
117
|
+
}
|
|
118
|
+
items = fetchedItems;
|
|
119
|
+
} else {
|
|
120
|
+
items = source.fetchContent(sourceInput, { postTypes, includeDrafts: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
81
123
|
// Import content (including drafts since we have auth)
|
|
82
|
-
const result = await importContent(
|
|
83
|
-
source.fetchContent(
|
|
84
|
-
{ type: "url", url: body.url, token: body.token },
|
|
85
|
-
{ postTypes, includeDrafts: true },
|
|
86
|
-
),
|
|
87
|
-
config,
|
|
88
|
-
dineway,
|
|
89
|
-
dinewayManifest,
|
|
90
|
-
);
|
|
124
|
+
const result = await importContent(items, config, dineway, dinewayManifest);
|
|
91
125
|
|
|
92
126
|
console.log("[WP Plugin Import] Import result:", JSON.stringify(result, null, 2));
|
|
93
127
|
|
|
@@ -134,7 +168,7 @@ const IMPORT_FIELDS: Array<{
|
|
|
134
168
|
];
|
|
135
169
|
|
|
136
170
|
async function importContent(
|
|
137
|
-
items:
|
|
171
|
+
items: AsyncIterable<NormalizedItem> | Iterable<NormalizedItem>,
|
|
138
172
|
config: WpPluginImportConfig,
|
|
139
173
|
dineway: DinewayHandlers,
|
|
140
174
|
manifest: DinewayManifest,
|
|
@@ -332,7 +366,7 @@ async function importContent(
|
|
|
332
366
|
console.error(`Import error for "${item.title || "Untitled"}":`, error);
|
|
333
367
|
result.errors.push({
|
|
334
368
|
title: item.title || "Untitled",
|
|
335
|
-
error: "Failed to import item",
|
|
369
|
+
error: error instanceof Error && error.message ? error.message : "Failed to import item",
|
|
336
370
|
});
|
|
337
371
|
}
|
|
338
372
|
}
|
|
@@ -355,3 +389,11 @@ function mapStatus(wpStatus: string | undefined): string {
|
|
|
355
389
|
return "draft";
|
|
356
390
|
}
|
|
357
391
|
}
|
|
392
|
+
|
|
393
|
+
async function collectPluginItems(items: AsyncIterable<NormalizedItem>): Promise<NormalizedItem[]> {
|
|
394
|
+
const collected: NormalizedItem[] = [];
|
|
395
|
+
for await (const item of items) {
|
|
396
|
+
collected.push(item);
|
|
397
|
+
}
|
|
398
|
+
return collected;
|
|
399
|
+
}
|
|
@@ -11,6 +11,9 @@ import type { APIRoute } from "astro";
|
|
|
11
11
|
|
|
12
12
|
import { getAuthMode } from "#auth/mode.js";
|
|
13
13
|
|
|
14
|
+
import { experimentalSiteContextWorkflowsEnabled } from "../../../site-context/experimental-workflows.js";
|
|
15
|
+
import { COMMIT, VERSION } from "../../../version.js";
|
|
16
|
+
import { getStoredConfig } from "../../integration/runtime.js";
|
|
14
17
|
import type { DinewayManifest } from "../../types.js";
|
|
15
18
|
|
|
16
19
|
export const prerender = false;
|
|
@@ -20,6 +23,7 @@ export const GET: APIRoute = async ({ locals }) => {
|
|
|
20
23
|
|
|
21
24
|
// Determine auth mode from config
|
|
22
25
|
const authMode = getAuthMode(dineway?.config);
|
|
26
|
+
const adminBranding = dinewayManifest?.admin ?? dineway?.config.admin ?? getStoredConfig()?.admin;
|
|
23
27
|
|
|
24
28
|
// Check if self-signup is enabled (any allowed domain with enabled = 1)
|
|
25
29
|
// Only relevant for passkey auth — external auth providers handle their own signup
|
|
@@ -39,17 +43,25 @@ export const GET: APIRoute = async ({ locals }) => {
|
|
|
39
43
|
const manifest: DinewayManifest = dinewayManifest
|
|
40
44
|
? {
|
|
41
45
|
...dinewayManifest,
|
|
46
|
+
features: {
|
|
47
|
+
...dinewayManifest.features,
|
|
48
|
+
siteContextWorkflows: experimentalSiteContextWorkflowsEnabled(),
|
|
49
|
+
},
|
|
42
50
|
authMode: authMode.type === "external" ? authMode.providerType : "passkey",
|
|
43
51
|
signupEnabled,
|
|
52
|
+
admin: adminBranding,
|
|
44
53
|
}
|
|
45
54
|
: {
|
|
46
|
-
version:
|
|
55
|
+
version: VERSION,
|
|
56
|
+
commit: COMMIT,
|
|
47
57
|
hash: "default",
|
|
48
58
|
collections: {},
|
|
49
59
|
plugins: {},
|
|
60
|
+
features: { siteContextWorkflows: experimentalSiteContextWorkflowsEnabled() },
|
|
50
61
|
taxonomies: [],
|
|
51
62
|
authMode: "passkey",
|
|
52
63
|
signupEnabled,
|
|
64
|
+
admin: adminBranding,
|
|
53
65
|
};
|
|
54
66
|
|
|
55
67
|
return Response.json(
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
9
9
|
|
|
10
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
11
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
11
12
|
|
|
12
13
|
export const prerender = false;
|
|
@@ -15,7 +16,9 @@ export const prerender = false;
|
|
|
15
16
|
* Get a single media item from a provider
|
|
16
17
|
*/
|
|
17
18
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
18
|
-
const { dineway } = locals;
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
const denied = requirePerm(user, "media:read");
|
|
21
|
+
if (denied) return denied;
|
|
19
22
|
const { providerId, itemId } = params;
|
|
20
23
|
|
|
21
24
|
if (!providerId || !itemId) {
|
|
@@ -56,7 +59,9 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
56
59
|
* Delete a media item from a provider
|
|
57
60
|
*/
|
|
58
61
|
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
59
|
-
const { dineway } = locals;
|
|
62
|
+
const { dineway, user } = locals;
|
|
63
|
+
const denied = requirePerm(user, "media:delete_any");
|
|
64
|
+
if (denied) return denied;
|
|
60
65
|
const { providerId, itemId } = params;
|
|
61
66
|
|
|
62
67
|
if (!providerId || !itemId) {
|
|
@@ -16,7 +16,7 @@ import { ulid } from "ulidx";
|
|
|
16
16
|
import { requirePerm } from "#api/authorize.js";
|
|
17
17
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
18
18
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
19
|
-
import { mediaUploadUrlBody } from "#api/schemas.js";
|
|
19
|
+
import { DEFAULT_MAX_UPLOAD_SIZE, mediaUploadUrlBody } from "#api/schemas.js";
|
|
20
20
|
|
|
21
21
|
export const prerender = false;
|
|
22
22
|
|
|
@@ -59,7 +59,16 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
try {
|
|
62
|
-
const
|
|
62
|
+
const maxUploadSize = dineway.config.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE;
|
|
63
|
+
if (!Number.isFinite(maxUploadSize) || maxUploadSize <= 0) {
|
|
64
|
+
return apiError(
|
|
65
|
+
"CONFIGURATION_ERROR",
|
|
66
|
+
"Invalid maxUploadSize configuration. Expected a positive finite number.",
|
|
67
|
+
500,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const body = await parseBody(request, mediaUploadUrlBody(maxUploadSize));
|
|
63
72
|
if (isParseError(body)) return body;
|
|
64
73
|
|
|
65
74
|
// Validate content type
|
|
@@ -13,7 +13,7 @@ import { ulid } from "ulidx";
|
|
|
13
13
|
import { requirePerm } from "#api/authorize.js";
|
|
14
14
|
import { apiError, apiSuccess, handleError, unwrapResult } from "#api/error.js";
|
|
15
15
|
import { isParseError, parseQuery } from "#api/parse.js";
|
|
16
|
-
import { mediaListQuery } from "#api/schemas.js";
|
|
16
|
+
import { DEFAULT_MAX_UPLOAD_SIZE, formatFileSize, mediaListQuery } from "#api/schemas.js";
|
|
17
17
|
import { MediaRepository } from "#db/repositories/media.js";
|
|
18
18
|
import { generatePlaceholder } from "#media/placeholder.js";
|
|
19
19
|
import { computeContentHash } from "#utils/hash.js";
|
|
@@ -22,9 +22,6 @@ import type { MediaItem } from "../../types.js";
|
|
|
22
22
|
|
|
23
23
|
export const prerender = false;
|
|
24
24
|
|
|
25
|
-
/** Maximum allowed file upload size (50 MB). */
|
|
26
|
-
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
|
|
27
|
-
|
|
28
25
|
/**
|
|
29
26
|
* Add URL to media items
|
|
30
27
|
* Uses relative URLs to ensure portability across deployments
|
|
@@ -89,9 +86,14 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
try {
|
|
89
|
+
const maxUploadSize = dineway.config.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE;
|
|
90
|
+
if (!Number.isFinite(maxUploadSize) || maxUploadSize <= 0) {
|
|
91
|
+
return apiError("CONFIGURATION_ERROR", "Invalid maxUploadSize configuration", 500);
|
|
92
|
+
}
|
|
93
|
+
|
|
92
94
|
// Best-effort size check before buffering the full multipart body
|
|
93
95
|
const contentLength = request.headers.get("Content-Length");
|
|
94
|
-
if (contentLength && parseInt(contentLength, 10) >
|
|
96
|
+
if (contentLength && parseInt(contentLength, 10) > maxUploadSize) {
|
|
95
97
|
return apiError("PAYLOAD_TOO_LARGE", "Upload too large", 413);
|
|
96
98
|
}
|
|
97
99
|
|
|
@@ -110,10 +112,10 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
// Check file size before buffering
|
|
113
|
-
if (file.size >
|
|
115
|
+
if (file.size > maxUploadSize) {
|
|
114
116
|
return apiError(
|
|
115
117
|
"PAYLOAD_TOO_LARGE",
|
|
116
|
-
`File exceeds maximum size of ${
|
|
118
|
+
`File exceeds maximum size of ${formatFileSize(maxUploadSize)}`,
|
|
117
119
|
413,
|
|
118
120
|
);
|
|
119
121
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
10
|
+
import { z } from "zod";
|
|
10
11
|
|
|
11
12
|
import { requirePerm } from "#api/authorize.js";
|
|
12
13
|
import { handleError, unwrapResult } from "#api/error.js";
|
|
@@ -15,6 +16,11 @@ import {
|
|
|
15
16
|
handleMenuItemDelete,
|
|
16
17
|
handleMenuItemUpdate,
|
|
17
18
|
} from "#api/handlers/menus.js";
|
|
19
|
+
import {
|
|
20
|
+
ensureWorkflowHitlRouteRequest,
|
|
21
|
+
hitlRequiredRouteError,
|
|
22
|
+
resolveHitlRouteActor,
|
|
23
|
+
} from "#api/hitl-route-helpers.js";
|
|
18
24
|
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
19
25
|
import {
|
|
20
26
|
createMenuItemBody,
|
|
@@ -22,9 +28,27 @@ import {
|
|
|
22
28
|
menuItemUpdateQuery,
|
|
23
29
|
updateMenuItemBody,
|
|
24
30
|
} from "#api/schemas.js";
|
|
31
|
+
import {
|
|
32
|
+
logMenuActivity,
|
|
33
|
+
menuApiRouteSource,
|
|
34
|
+
MenuHitlPayloadBuilder,
|
|
35
|
+
RiskPolicyEvaluator,
|
|
36
|
+
} from "#site-context/index.js";
|
|
25
37
|
|
|
26
38
|
export const prerender = false;
|
|
27
39
|
|
|
40
|
+
const createMenuItemHitlBody = createMenuItemBody.extend({
|
|
41
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const updateMenuItemHitlBody = updateMenuItemBody.extend({
|
|
45
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const deleteMenuItemHitlQuery = menuItemDeleteQuery.extend({
|
|
49
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
50
|
+
});
|
|
51
|
+
|
|
28
52
|
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
29
53
|
const { dineway, user } = locals;
|
|
30
54
|
const name = params.name!;
|
|
@@ -33,10 +57,42 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
33
57
|
if (denied) return denied;
|
|
34
58
|
|
|
35
59
|
try {
|
|
36
|
-
const body = await parseBody(request,
|
|
60
|
+
const body = await parseBody(request, createMenuItemHitlBody);
|
|
37
61
|
if (isParseError(body)) return body;
|
|
38
62
|
|
|
39
|
-
const
|
|
63
|
+
const { hitlRequestId, ...menuInput } = body;
|
|
64
|
+
const actor = resolveHitlRouteActor(locals);
|
|
65
|
+
const action = await new MenuHitlPayloadBuilder(dineway.db).buildCreateMenuItemRequest({
|
|
66
|
+
menuName: name,
|
|
67
|
+
...menuInput,
|
|
68
|
+
});
|
|
69
|
+
const decision = await new RiskPolicyEvaluator({
|
|
70
|
+
db: dineway.db,
|
|
71
|
+
handlers: dineway,
|
|
72
|
+
}).evaluateWorkflowHitl({
|
|
73
|
+
actor: actor.identity,
|
|
74
|
+
hitlRequestId,
|
|
75
|
+
action,
|
|
76
|
+
});
|
|
77
|
+
if (!decision.allowed) {
|
|
78
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
79
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = await handleMenuItemCreate(dineway.db, name, menuInput);
|
|
83
|
+
if (!result.success) return unwrapResult(result, 201);
|
|
84
|
+
|
|
85
|
+
await logMenuActivity(dineway.db, locals, {
|
|
86
|
+
action: "item_created",
|
|
87
|
+
menuName: name,
|
|
88
|
+
itemId: result.data.id,
|
|
89
|
+
...menuApiRouteSource("item_created"),
|
|
90
|
+
summary: `Created menu item ${result.data.id} in ${name}`,
|
|
91
|
+
detail: {
|
|
92
|
+
label: result.data.label,
|
|
93
|
+
hitlRequestId: decision.required ? decision.hitlRequest.id : null,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
40
96
|
return unwrapResult(result, 201);
|
|
41
97
|
} catch (error) {
|
|
42
98
|
return handleError(error, "Failed to create menu item", "MENU_ITEM_CREATE_ERROR");
|
|
@@ -56,10 +112,43 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
56
112
|
const itemId = query.id;
|
|
57
113
|
|
|
58
114
|
try {
|
|
59
|
-
const body = await parseBody(request,
|
|
115
|
+
const body = await parseBody(request, updateMenuItemHitlBody);
|
|
60
116
|
if (isParseError(body)) return body;
|
|
61
117
|
|
|
62
|
-
const
|
|
118
|
+
const { hitlRequestId, ...menuInput } = body;
|
|
119
|
+
const actor = resolveHitlRouteActor(locals);
|
|
120
|
+
const action = await new MenuHitlPayloadBuilder(dineway.db).buildUpdateMenuItemRequest({
|
|
121
|
+
menuName: name,
|
|
122
|
+
itemId,
|
|
123
|
+
...menuInput,
|
|
124
|
+
});
|
|
125
|
+
const decision = await new RiskPolicyEvaluator({
|
|
126
|
+
db: dineway.db,
|
|
127
|
+
handlers: dineway,
|
|
128
|
+
}).evaluateWorkflowHitl({
|
|
129
|
+
actor: actor.identity,
|
|
130
|
+
hitlRequestId,
|
|
131
|
+
action,
|
|
132
|
+
});
|
|
133
|
+
if (!decision.allowed) {
|
|
134
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
135
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = await handleMenuItemUpdate(dineway.db, name, itemId, menuInput);
|
|
139
|
+
if (!result.success) return unwrapResult(result);
|
|
140
|
+
|
|
141
|
+
await logMenuActivity(dineway.db, locals, {
|
|
142
|
+
action: "item_updated",
|
|
143
|
+
menuName: name,
|
|
144
|
+
itemId,
|
|
145
|
+
...menuApiRouteSource("item_updated"),
|
|
146
|
+
summary: `Updated menu item ${itemId} in ${name}`,
|
|
147
|
+
detail: {
|
|
148
|
+
label: result.data.label,
|
|
149
|
+
hitlRequestId: decision.required ? decision.hitlRequest.id : null,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
63
152
|
return unwrapResult(result);
|
|
64
153
|
} catch (error) {
|
|
65
154
|
return handleError(error, "Failed to update menu item", "MENU_ITEM_UPDATE_ERROR");
|
|
@@ -74,12 +163,42 @@ export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
|
74
163
|
if (denied) return denied;
|
|
75
164
|
|
|
76
165
|
const url = new URL(request.url);
|
|
77
|
-
const query = parseQuery(url,
|
|
166
|
+
const query = parseQuery(url, deleteMenuItemHitlQuery);
|
|
78
167
|
if (isParseError(query)) return query;
|
|
79
168
|
const itemId = query.id;
|
|
80
169
|
|
|
81
170
|
try {
|
|
171
|
+
const actor = resolveHitlRouteActor(locals);
|
|
172
|
+
const action = await new MenuHitlPayloadBuilder(dineway.db).buildDeleteMenuItemRequest({
|
|
173
|
+
menuName: name,
|
|
174
|
+
itemId,
|
|
175
|
+
});
|
|
176
|
+
const decision = await new RiskPolicyEvaluator({
|
|
177
|
+
db: dineway.db,
|
|
178
|
+
handlers: dineway,
|
|
179
|
+
}).evaluateWorkflowHitl({
|
|
180
|
+
actor: actor.identity,
|
|
181
|
+
hitlRequestId: query.hitlRequestId,
|
|
182
|
+
action,
|
|
183
|
+
});
|
|
184
|
+
if (!decision.allowed) {
|
|
185
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
186
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
187
|
+
}
|
|
188
|
+
|
|
82
189
|
const result = await handleMenuItemDelete(dineway.db, name, itemId);
|
|
190
|
+
if (!result.success) return unwrapResult(result);
|
|
191
|
+
|
|
192
|
+
await logMenuActivity(dineway.db, locals, {
|
|
193
|
+
action: "item_deleted",
|
|
194
|
+
menuName: name,
|
|
195
|
+
itemId,
|
|
196
|
+
...menuApiRouteSource("item_deleted"),
|
|
197
|
+
summary: `Deleted menu item ${itemId} from ${name}`,
|
|
198
|
+
detail: {
|
|
199
|
+
hitlRequestId: decision.required ? decision.hitlRequest.id : null,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
83
202
|
return unwrapResult(result);
|
|
84
203
|
} catch (error) {
|
|
85
204
|
return handleError(error, "Failed to delete menu item", "MENU_ITEM_DELETE_ERROR");
|
|
@@ -5,15 +5,31 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { APIRoute } from "astro";
|
|
8
|
+
import { z } from "zod";
|
|
8
9
|
|
|
9
10
|
import { requirePerm } from "#api/authorize.js";
|
|
10
11
|
import { handleError, unwrapResult } from "#api/error.js";
|
|
11
12
|
import { handleMenuItemReorder } from "#api/handlers/menus.js";
|
|
13
|
+
import {
|
|
14
|
+
ensureWorkflowHitlRouteRequest,
|
|
15
|
+
hitlRequiredRouteError,
|
|
16
|
+
resolveHitlRouteActor,
|
|
17
|
+
} from "#api/hitl-route-helpers.js";
|
|
12
18
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
13
19
|
import { reorderMenuItemsBody } from "#api/schemas.js";
|
|
20
|
+
import {
|
|
21
|
+
logMenuActivity,
|
|
22
|
+
menuApiRouteSource,
|
|
23
|
+
MenuHitlPayloadBuilder,
|
|
24
|
+
RiskPolicyEvaluator,
|
|
25
|
+
} from "#site-context/index.js";
|
|
14
26
|
|
|
15
27
|
export const prerender = false;
|
|
16
28
|
|
|
29
|
+
const reorderMenuItemsHitlBody = reorderMenuItemsBody.extend({
|
|
30
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
17
33
|
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
18
34
|
const { dineway, user } = locals;
|
|
19
35
|
const name = params.name!;
|
|
@@ -22,10 +38,40 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
22
38
|
if (denied) return denied;
|
|
23
39
|
|
|
24
40
|
try {
|
|
25
|
-
const body = await parseBody(request,
|
|
41
|
+
const body = await parseBody(request, reorderMenuItemsHitlBody);
|
|
26
42
|
if (isParseError(body)) return body;
|
|
27
43
|
|
|
44
|
+
const actor = resolveHitlRouteActor(locals);
|
|
45
|
+
const action = await new MenuHitlPayloadBuilder(dineway.db).buildReorderMenuItemsRequest({
|
|
46
|
+
menuName: name,
|
|
47
|
+
items: body.items,
|
|
48
|
+
});
|
|
49
|
+
const decision = await new RiskPolicyEvaluator({
|
|
50
|
+
db: dineway.db,
|
|
51
|
+
handlers: dineway,
|
|
52
|
+
}).evaluateWorkflowHitl({
|
|
53
|
+
actor: actor.identity,
|
|
54
|
+
hitlRequestId: body.hitlRequestId,
|
|
55
|
+
action,
|
|
56
|
+
});
|
|
57
|
+
if (!decision.allowed) {
|
|
58
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
59
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
60
|
+
}
|
|
61
|
+
|
|
28
62
|
const result = await handleMenuItemReorder(dineway.db, name, body.items);
|
|
63
|
+
if (!result.success) return unwrapResult(result);
|
|
64
|
+
|
|
65
|
+
await logMenuActivity(dineway.db, locals, {
|
|
66
|
+
action: "reordered",
|
|
67
|
+
menuName: name,
|
|
68
|
+
...menuApiRouteSource("reordered"),
|
|
69
|
+
summary: `Reordered menu ${name}`,
|
|
70
|
+
detail: {
|
|
71
|
+
itemCount: body.items.length,
|
|
72
|
+
hitlRequestId: decision.required ? decision.hitlRequest.id : null,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
29
75
|
return unwrapResult(result);
|
|
30
76
|
} catch (error) {
|
|
31
77
|
return handleError(error, "Failed to reorder menu items", "MENU_REORDER_ERROR");
|
|
@@ -7,15 +7,35 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
10
|
+
import { z } from "zod";
|
|
10
11
|
|
|
11
12
|
import { requirePerm } from "#api/authorize.js";
|
|
12
13
|
import { handleError, unwrapResult } from "#api/error.js";
|
|
13
14
|
import { handleMenuDelete, handleMenuGet, handleMenuUpdate } from "#api/handlers/menus.js";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
ensureWorkflowHitlRouteRequest,
|
|
17
|
+
hitlRequiredRouteError,
|
|
18
|
+
resolveHitlRouteActor,
|
|
19
|
+
} from "#api/hitl-route-helpers.js";
|
|
20
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
15
21
|
import { updateMenuBody } from "#api/schemas.js";
|
|
22
|
+
import {
|
|
23
|
+
logMenuActivity,
|
|
24
|
+
menuApiRouteSource,
|
|
25
|
+
MenuHitlPayloadBuilder,
|
|
26
|
+
RiskPolicyEvaluator,
|
|
27
|
+
} from "#site-context/index.js";
|
|
16
28
|
|
|
17
29
|
export const prerender = false;
|
|
18
30
|
|
|
31
|
+
const updateMenuHitlBody = updateMenuBody.extend({
|
|
32
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const deleteMenuQuery = z.object({
|
|
36
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
37
|
+
});
|
|
38
|
+
|
|
19
39
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
20
40
|
const { dineway, user } = locals;
|
|
21
41
|
const name = params.name!;
|
|
@@ -39,25 +59,85 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
39
59
|
if (denied) return denied;
|
|
40
60
|
|
|
41
61
|
try {
|
|
42
|
-
const body = await parseBody(request,
|
|
62
|
+
const body = await parseBody(request, updateMenuHitlBody);
|
|
43
63
|
if (isParseError(body)) return body;
|
|
44
64
|
|
|
45
|
-
const
|
|
65
|
+
const { hitlRequestId, ...menuInput } = body;
|
|
66
|
+
const actor = resolveHitlRouteActor(locals);
|
|
67
|
+
const action = await new MenuHitlPayloadBuilder(dineway.db).buildUpdateMenuRequest({
|
|
68
|
+
name,
|
|
69
|
+
...menuInput,
|
|
70
|
+
});
|
|
71
|
+
const decision = await new RiskPolicyEvaluator({
|
|
72
|
+
db: dineway.db,
|
|
73
|
+
handlers: dineway,
|
|
74
|
+
}).evaluateWorkflowHitl({
|
|
75
|
+
actor: actor.identity,
|
|
76
|
+
hitlRequestId,
|
|
77
|
+
action,
|
|
78
|
+
});
|
|
79
|
+
if (!decision.allowed) {
|
|
80
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
81
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = await handleMenuUpdate(dineway.db, name, menuInput);
|
|
85
|
+
if (!result.success) return unwrapResult(result);
|
|
86
|
+
|
|
87
|
+
await logMenuActivity(dineway.db, locals, {
|
|
88
|
+
action: "updated",
|
|
89
|
+
menuName: result.data.name,
|
|
90
|
+
...menuApiRouteSource("updated"),
|
|
91
|
+
summary: `Updated menu ${result.data.name}`,
|
|
92
|
+
detail: {
|
|
93
|
+
label: result.data.label,
|
|
94
|
+
hitlRequestId: decision.required ? decision.hitlRequest.id : null,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
46
97
|
return unwrapResult(result);
|
|
47
98
|
} catch (error) {
|
|
48
99
|
return handleError(error, "Failed to update menu", "MENU_UPDATE_ERROR");
|
|
49
100
|
}
|
|
50
101
|
};
|
|
51
102
|
|
|
52
|
-
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
103
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
53
104
|
const { dineway, user } = locals;
|
|
54
105
|
const name = params.name!;
|
|
55
106
|
|
|
56
107
|
const denied = requirePerm(user, "menus:manage");
|
|
57
108
|
if (denied) return denied;
|
|
58
109
|
|
|
110
|
+
const query = parseQuery(new URL(request.url), deleteMenuQuery);
|
|
111
|
+
if (isParseError(query)) return query;
|
|
112
|
+
|
|
59
113
|
try {
|
|
114
|
+
const actor = resolveHitlRouteActor(locals);
|
|
115
|
+
const action = await new MenuHitlPayloadBuilder(dineway.db).buildDeleteMenuRequest({ name });
|
|
116
|
+
const decision = await new RiskPolicyEvaluator({
|
|
117
|
+
db: dineway.db,
|
|
118
|
+
handlers: dineway,
|
|
119
|
+
}).evaluateWorkflowHitl({
|
|
120
|
+
actor: actor.identity,
|
|
121
|
+
hitlRequestId: query.hitlRequestId,
|
|
122
|
+
action,
|
|
123
|
+
});
|
|
124
|
+
if (!decision.allowed) {
|
|
125
|
+
const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
|
|
126
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
127
|
+
}
|
|
128
|
+
|
|
60
129
|
const result = await handleMenuDelete(dineway.db, name);
|
|
130
|
+
if (!result.success) return unwrapResult(result);
|
|
131
|
+
|
|
132
|
+
await logMenuActivity(dineway.db, locals, {
|
|
133
|
+
action: "deleted",
|
|
134
|
+
menuName: name,
|
|
135
|
+
...menuApiRouteSource("deleted"),
|
|
136
|
+
summary: `Deleted menu ${name}`,
|
|
137
|
+
detail: {
|
|
138
|
+
hitlRequestId: decision.required ? decision.hitlRequest.id : null,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
61
141
|
return unwrapResult(result);
|
|
62
142
|
} catch (error) {
|
|
63
143
|
return handleError(error, "Failed to delete menu", "MENU_DELETE_ERROR");
|