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
|
@@ -6,14 +6,35 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
9
|
+
import { z } from "zod";
|
|
9
10
|
|
|
10
11
|
import { requirePerm } from "#api/authorize.js";
|
|
11
12
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ensureWorkflowHitlRouteRequest,
|
|
15
|
+
hitlRequiredRouteError,
|
|
16
|
+
resolveHitlRouteActor,
|
|
17
|
+
} from "#api/hitl-route-helpers.js";
|
|
18
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
13
19
|
import { updateWidgetBody } from "#api/schemas.js";
|
|
20
|
+
import {
|
|
21
|
+
activityChangedKeys,
|
|
22
|
+
logWidgetActivity,
|
|
23
|
+
RiskPolicyEvaluator,
|
|
24
|
+
widgetApiRouteSource,
|
|
25
|
+
WidgetHitlPayloadBuilder,
|
|
26
|
+
} from "#site-context/index.js";
|
|
14
27
|
|
|
15
28
|
export const prerender = false;
|
|
16
29
|
|
|
30
|
+
const updateWidgetHitlBody = updateWidgetBody.extend({
|
|
31
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const deleteWidgetQuery = z.object({
|
|
35
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
17
38
|
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
18
39
|
const { dineway, user } = locals;
|
|
19
40
|
const db = dineway.db;
|
|
@@ -27,42 +48,50 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
27
48
|
}
|
|
28
49
|
|
|
29
50
|
try {
|
|
30
|
-
|
|
31
|
-
const area = await
|
|
32
|
-
.selectFrom("_dineway_widget_areas")
|
|
33
|
-
.select("id")
|
|
34
|
-
.where("name", "=", name)
|
|
35
|
-
.executeTakeFirst();
|
|
51
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
52
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
36
53
|
|
|
37
54
|
if (!area) {
|
|
38
55
|
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
39
56
|
}
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
const existingWidget = await db
|
|
43
|
-
.selectFrom("_dineway_widgets")
|
|
44
|
-
.select("id")
|
|
45
|
-
.where("id", "=", id)
|
|
46
|
-
.where("area_id", "=", area.id)
|
|
47
|
-
.executeTakeFirst();
|
|
58
|
+
const existingWidget = area.widgets.find((widget) => widget.id === id);
|
|
48
59
|
|
|
49
60
|
if (!existingWidget) {
|
|
50
61
|
return apiError("NOT_FOUND", `Widget "${id}" not found in area "${name}"`, 404);
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
const body = await parseBody(request,
|
|
64
|
+
const body = await parseBody(request, updateWidgetHitlBody);
|
|
54
65
|
if (isParseError(body)) return body;
|
|
66
|
+
const { hitlRequestId, ...widgetInput } = body;
|
|
67
|
+
|
|
68
|
+
const actor = resolveHitlRouteActor(locals);
|
|
69
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
70
|
+
db,
|
|
71
|
+
handlers: dineway,
|
|
72
|
+
});
|
|
73
|
+
let approvedHitlRequestId: string | null = null;
|
|
74
|
+
|
|
75
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
76
|
+
const action = await payloadBuilder.buildUpdateWidgetRequest({
|
|
77
|
+
area,
|
|
78
|
+
currentWidget: existingWidget,
|
|
79
|
+
...widgetInput,
|
|
80
|
+
});
|
|
81
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
82
|
+
actor: actor.identity,
|
|
83
|
+
hitlRequestId,
|
|
84
|
+
action,
|
|
85
|
+
});
|
|
86
|
+
if (!decision.allowed) {
|
|
87
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
88
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
89
|
+
}
|
|
90
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
91
|
+
}
|
|
55
92
|
|
|
56
93
|
// Build update object (only update provided fields)
|
|
57
|
-
const updates
|
|
58
|
-
if (body.title !== undefined) updates.title = body.title || null;
|
|
59
|
-
if (body.type !== undefined) updates.type = body.type;
|
|
60
|
-
if (body.content !== undefined)
|
|
61
|
-
updates.content = body.content ? JSON.stringify(body.content) : null;
|
|
62
|
-
if (body.menuName !== undefined) updates.menu_name = body.menuName || null;
|
|
63
|
-
if (body.componentId !== undefined) updates.component_id = body.componentId || null;
|
|
64
|
-
if (body.componentProps !== undefined)
|
|
65
|
-
updates.component_props = body.componentProps ? JSON.stringify(body.componentProps) : null;
|
|
94
|
+
const updates = buildWidgetUpdates(widgetInput);
|
|
66
95
|
|
|
67
96
|
if (Object.keys(updates).length === 0) {
|
|
68
97
|
return apiError("VALIDATION_ERROR", "No fields to update", 400);
|
|
@@ -76,13 +105,26 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
76
105
|
.where("id", "=", id)
|
|
77
106
|
.executeTakeFirstOrThrow();
|
|
78
107
|
|
|
108
|
+
await logWidgetActivity(db, locals, {
|
|
109
|
+
action: "updated",
|
|
110
|
+
areaId: area.id,
|
|
111
|
+
areaName: area.name,
|
|
112
|
+
widgetId: widget.id,
|
|
113
|
+
...widgetApiRouteSource("updated"),
|
|
114
|
+
detail: {
|
|
115
|
+
changedKeys: activityChangedKeys(widgetInput as Record<string, unknown>),
|
|
116
|
+
type: widget.type,
|
|
117
|
+
hitlRequestId: approvedHitlRequestId,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
79
121
|
return apiSuccess(widget);
|
|
80
122
|
} catch (error) {
|
|
81
123
|
return handleError(error, "Failed to update widget", "WIDGET_UPDATE_ERROR");
|
|
82
124
|
}
|
|
83
125
|
};
|
|
84
126
|
|
|
85
|
-
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
127
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
86
128
|
const { dineway, user } = locals;
|
|
87
129
|
const db = dineway.db;
|
|
88
130
|
const { name, id } = params;
|
|
@@ -94,34 +136,78 @@ export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
|
94
136
|
return apiError("VALIDATION_ERROR", "name and id are required", 400);
|
|
95
137
|
}
|
|
96
138
|
|
|
139
|
+
const query = parseQuery(new URL(request.url), deleteWidgetQuery);
|
|
140
|
+
if (isParseError(query)) return query;
|
|
141
|
+
|
|
97
142
|
try {
|
|
98
|
-
|
|
99
|
-
const area = await
|
|
100
|
-
.selectFrom("_dineway_widget_areas")
|
|
101
|
-
.select("id")
|
|
102
|
-
.where("name", "=", name)
|
|
103
|
-
.executeTakeFirst();
|
|
143
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
144
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
104
145
|
|
|
105
146
|
if (!area) {
|
|
106
147
|
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
107
148
|
}
|
|
108
149
|
|
|
109
|
-
|
|
110
|
-
const existingWidget = await db
|
|
111
|
-
.selectFrom("_dineway_widgets")
|
|
112
|
-
.select("id")
|
|
113
|
-
.where("id", "=", id)
|
|
114
|
-
.where("area_id", "=", area.id)
|
|
115
|
-
.executeTakeFirst();
|
|
150
|
+
const existingWidget = area.widgets.find((widget) => widget.id === id);
|
|
116
151
|
|
|
117
152
|
if (!existingWidget) {
|
|
118
153
|
return apiError("NOT_FOUND", `Widget "${id}" not found in area "${name}"`, 404);
|
|
119
154
|
}
|
|
120
155
|
|
|
156
|
+
const actor = resolveHitlRouteActor(locals);
|
|
157
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
158
|
+
db,
|
|
159
|
+
handlers: dineway,
|
|
160
|
+
});
|
|
161
|
+
let approvedHitlRequestId: string | null = null;
|
|
162
|
+
|
|
163
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
164
|
+
const action = await payloadBuilder.buildDeleteWidgetRequest({
|
|
165
|
+
area,
|
|
166
|
+
currentWidget: existingWidget,
|
|
167
|
+
});
|
|
168
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
169
|
+
actor: actor.identity,
|
|
170
|
+
hitlRequestId: query.hitlRequestId,
|
|
171
|
+
action,
|
|
172
|
+
});
|
|
173
|
+
if (!decision.allowed) {
|
|
174
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
175
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
176
|
+
}
|
|
177
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
178
|
+
}
|
|
179
|
+
|
|
121
180
|
await db.deleteFrom("_dineway_widgets").where("id", "=", id).execute();
|
|
122
181
|
|
|
182
|
+
await logWidgetActivity(db, locals, {
|
|
183
|
+
action: "deleted",
|
|
184
|
+
areaId: area.id,
|
|
185
|
+
areaName: area.name,
|
|
186
|
+
widgetId: existingWidget.id,
|
|
187
|
+
...widgetApiRouteSource("deleted"),
|
|
188
|
+
detail: {
|
|
189
|
+
type: existingWidget.type,
|
|
190
|
+
sortOrder: existingWidget.sortOrder,
|
|
191
|
+
hitlRequestId: approvedHitlRequestId,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
123
195
|
return apiSuccess({ deleted: true });
|
|
124
196
|
} catch (error) {
|
|
125
197
|
return handleError(error, "Failed to delete widget", "WIDGET_DELETE_ERROR");
|
|
126
198
|
}
|
|
127
199
|
};
|
|
200
|
+
|
|
201
|
+
function buildWidgetUpdates(body: z.infer<typeof updateWidgetBody>): Record<string, unknown> {
|
|
202
|
+
const updates: Record<string, unknown> = {};
|
|
203
|
+
if (body.title !== undefined) updates.title = body.title || null;
|
|
204
|
+
if (body.type !== undefined) updates.type = body.type;
|
|
205
|
+
if (body.content !== undefined)
|
|
206
|
+
updates.content = body.content ? JSON.stringify(body.content) : null;
|
|
207
|
+
if (body.menuName !== undefined) updates.menu_name = body.menuName || null;
|
|
208
|
+
if (body.componentId !== undefined) updates.component_id = body.componentId || null;
|
|
209
|
+
if (body.componentProps !== undefined) {
|
|
210
|
+
updates.component_props = body.componentProps ? JSON.stringify(body.componentProps) : null;
|
|
211
|
+
}
|
|
212
|
+
return updates;
|
|
213
|
+
}
|
|
@@ -6,14 +6,30 @@
|
|
|
6
6
|
|
|
7
7
|
import type { APIRoute } from "astro";
|
|
8
8
|
import { ulid } from "ulidx";
|
|
9
|
+
import { z } from "zod";
|
|
9
10
|
|
|
10
11
|
import { requirePerm } from "#api/authorize.js";
|
|
11
12
|
import { apiError, apiSuccess, handleError } from "#api/error.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 { createWidgetBody } from "#api/schemas.js";
|
|
20
|
+
import {
|
|
21
|
+
logWidgetActivity,
|
|
22
|
+
RiskPolicyEvaluator,
|
|
23
|
+
widgetApiRouteSource,
|
|
24
|
+
WidgetHitlPayloadBuilder,
|
|
25
|
+
} from "#site-context/index.js";
|
|
14
26
|
|
|
15
27
|
export const prerender = false;
|
|
16
28
|
|
|
29
|
+
const createWidgetHitlBody = createWidgetBody.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 db = dineway.db;
|
|
@@ -27,28 +43,42 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
try {
|
|
30
|
-
|
|
31
|
-
const area = await
|
|
32
|
-
.selectFrom("_dineway_widget_areas")
|
|
33
|
-
.select("id")
|
|
34
|
-
.where("name", "=", name)
|
|
35
|
-
.executeTakeFirst();
|
|
46
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
47
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
36
48
|
|
|
37
49
|
if (!area) {
|
|
38
50
|
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
const body = await parseBody(request,
|
|
53
|
+
const body = await parseBody(request, createWidgetHitlBody);
|
|
42
54
|
if (isParseError(body)) return body;
|
|
55
|
+
const { hitlRequestId, ...widgetInput } = body;
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
const actor = resolveHitlRouteActor(locals);
|
|
58
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
59
|
+
db,
|
|
60
|
+
handlers: dineway,
|
|
61
|
+
});
|
|
62
|
+
let approvedHitlRequestId: string | null = null;
|
|
50
63
|
|
|
51
|
-
|
|
64
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
65
|
+
const action = await payloadBuilder.buildCreateWidgetRequest({
|
|
66
|
+
area,
|
|
67
|
+
...widgetInput,
|
|
68
|
+
});
|
|
69
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
70
|
+
actor: actor.identity,
|
|
71
|
+
hitlRequestId,
|
|
72
|
+
action,
|
|
73
|
+
});
|
|
74
|
+
if (!decision.allowed) {
|
|
75
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
76
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
77
|
+
}
|
|
78
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sortOrder = (area.widgets.at(-1)?.sortOrder ?? -1) + 1;
|
|
52
82
|
|
|
53
83
|
// Prepare values
|
|
54
84
|
const id = ulid();
|
|
@@ -58,12 +88,14 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
58
88
|
id,
|
|
59
89
|
area_id: area.id,
|
|
60
90
|
sort_order: sortOrder,
|
|
61
|
-
type:
|
|
62
|
-
title:
|
|
63
|
-
content:
|
|
64
|
-
menu_name:
|
|
65
|
-
component_id:
|
|
66
|
-
component_props:
|
|
91
|
+
type: widgetInput.type,
|
|
92
|
+
title: widgetInput.title ?? null,
|
|
93
|
+
content: widgetInput.content ? JSON.stringify(widgetInput.content) : null,
|
|
94
|
+
menu_name: widgetInput.menuName ?? null,
|
|
95
|
+
component_id: widgetInput.componentId ?? null,
|
|
96
|
+
component_props: widgetInput.componentProps
|
|
97
|
+
? JSON.stringify(widgetInput.componentProps)
|
|
98
|
+
: null,
|
|
67
99
|
})
|
|
68
100
|
.execute();
|
|
69
101
|
|
|
@@ -73,6 +105,20 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
73
105
|
.where("id", "=", id)
|
|
74
106
|
.executeTakeFirstOrThrow();
|
|
75
107
|
|
|
108
|
+
await logWidgetActivity(db, locals, {
|
|
109
|
+
action: "created",
|
|
110
|
+
areaId: area.id,
|
|
111
|
+
areaName: area.name,
|
|
112
|
+
widgetId: widget.id,
|
|
113
|
+
...widgetApiRouteSource("created"),
|
|
114
|
+
detail: {
|
|
115
|
+
type: widget.type,
|
|
116
|
+
sortOrder: widget.sort_order,
|
|
117
|
+
title: widget.title,
|
|
118
|
+
hitlRequestId: approvedHitlRequestId,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
76
122
|
return apiSuccess(widget, 201);
|
|
77
123
|
} catch (error) {
|
|
78
124
|
return handleError(error, "Failed to create widget", "WIDGET_CREATE_ERROR");
|
|
@@ -6,12 +6,29 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
9
|
+
import { z } from "zod";
|
|
9
10
|
|
|
10
11
|
import { requirePerm } from "#api/authorize.js";
|
|
11
12
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
13
|
+
import {
|
|
14
|
+
ensureWorkflowHitlRouteRequest,
|
|
15
|
+
hitlRequiredRouteError,
|
|
16
|
+
resolveHitlRouteActor,
|
|
17
|
+
} from "#api/hitl-route-helpers.js";
|
|
18
|
+
import { isParseError, parseQuery } from "#api/parse.js";
|
|
19
|
+
import {
|
|
20
|
+
logWidgetActivity,
|
|
21
|
+
RiskPolicyEvaluator,
|
|
22
|
+
widgetApiRouteSource,
|
|
23
|
+
WidgetHitlPayloadBuilder,
|
|
24
|
+
} from "#site-context/index.js";
|
|
12
25
|
|
|
13
26
|
export const prerender = false;
|
|
14
27
|
|
|
28
|
+
const deleteWidgetAreaQuery = z.object({
|
|
29
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
15
32
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
16
33
|
const { dineway, user } = locals;
|
|
17
34
|
const db = dineway.db;
|
|
@@ -53,7 +70,7 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
53
70
|
}
|
|
54
71
|
};
|
|
55
72
|
|
|
56
|
-
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
73
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
57
74
|
const { dineway, user } = locals;
|
|
58
75
|
const db = dineway.db;
|
|
59
76
|
const { name } = params;
|
|
@@ -65,21 +82,52 @@ export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
|
65
82
|
return apiError("VALIDATION_ERROR", "name is required", 400);
|
|
66
83
|
}
|
|
67
84
|
|
|
85
|
+
const query = parseQuery(new URL(request.url), deleteWidgetAreaQuery);
|
|
86
|
+
if (isParseError(query)) return query;
|
|
87
|
+
|
|
68
88
|
try {
|
|
69
|
-
|
|
70
|
-
const area = await
|
|
71
|
-
.selectFrom("_dineway_widget_areas")
|
|
72
|
-
.select("id")
|
|
73
|
-
.where("name", "=", name)
|
|
74
|
-
.executeTakeFirst();
|
|
89
|
+
const payloadBuilder = new WidgetHitlPayloadBuilder(db);
|
|
90
|
+
const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
|
|
75
91
|
|
|
76
92
|
if (!area) {
|
|
77
93
|
return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
|
|
78
94
|
}
|
|
79
95
|
|
|
96
|
+
const actor = resolveHitlRouteActor(locals);
|
|
97
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
98
|
+
db,
|
|
99
|
+
handlers: dineway,
|
|
100
|
+
});
|
|
101
|
+
let approvedHitlRequestId: string | null = null;
|
|
102
|
+
|
|
103
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
104
|
+
const action = await payloadBuilder.buildDeleteWidgetAreaRequest({ area });
|
|
105
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
106
|
+
actor: actor.identity,
|
|
107
|
+
hitlRequestId: query.hitlRequestId,
|
|
108
|
+
action,
|
|
109
|
+
});
|
|
110
|
+
if (!decision.allowed) {
|
|
111
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
112
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
113
|
+
}
|
|
114
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
115
|
+
}
|
|
116
|
+
|
|
80
117
|
// Delete area (widgets cascade)
|
|
81
118
|
await db.deleteFrom("_dineway_widget_areas").where("id", "=", area.id).execute();
|
|
82
119
|
|
|
120
|
+
await logWidgetActivity(db, locals, {
|
|
121
|
+
action: "area_deleted",
|
|
122
|
+
areaId: area.id,
|
|
123
|
+
areaName: area.name,
|
|
124
|
+
...widgetApiRouteSource("area_deleted"),
|
|
125
|
+
detail: {
|
|
126
|
+
widgetCount: area.widgets.length,
|
|
127
|
+
hitlRequestId: approvedHitlRequestId,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
83
131
|
return apiSuccess({ deleted: true });
|
|
84
132
|
} catch (error) {
|
|
85
133
|
return handleError(error, "Failed to delete widget area", "WIDGET_AREA_DELETE_ERROR");
|
|
@@ -7,14 +7,30 @@
|
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
9
9
|
import { ulid } from "ulidx";
|
|
10
|
+
import { z } from "zod";
|
|
10
11
|
|
|
11
12
|
import { requirePerm } from "#api/authorize.js";
|
|
12
13
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
14
|
+
import {
|
|
15
|
+
ensureWorkflowHitlRouteRequest,
|
|
16
|
+
hitlRequiredRouteError,
|
|
17
|
+
resolveHitlRouteActor,
|
|
18
|
+
} from "#api/hitl-route-helpers.js";
|
|
13
19
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
20
|
import { createWidgetAreaBody } from "#api/schemas.js";
|
|
21
|
+
import {
|
|
22
|
+
logWidgetActivity,
|
|
23
|
+
RiskPolicyEvaluator,
|
|
24
|
+
widgetApiRouteSource,
|
|
25
|
+
WidgetHitlPayloadBuilder,
|
|
26
|
+
} from "#site-context/index.js";
|
|
15
27
|
|
|
16
28
|
export const prerender = false;
|
|
17
29
|
|
|
30
|
+
const createWidgetAreaHitlBody = createWidgetAreaBody.extend({
|
|
31
|
+
hitlRequestId: z.string().min(1).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
18
34
|
export const GET: APIRoute = async ({ locals }) => {
|
|
19
35
|
const { dineway, user } = locals;
|
|
20
36
|
const db = dineway.db;
|
|
@@ -61,18 +77,40 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
61
77
|
if (denied) return denied;
|
|
62
78
|
|
|
63
79
|
try {
|
|
64
|
-
const body = await parseBody(request,
|
|
80
|
+
const body = await parseBody(request, createWidgetAreaHitlBody);
|
|
65
81
|
if (isParseError(body)) return body;
|
|
82
|
+
const { hitlRequestId, ...areaInput } = body;
|
|
66
83
|
|
|
67
84
|
// Check if area name already exists
|
|
68
85
|
const existing = await db
|
|
69
86
|
.selectFrom("_dineway_widget_areas")
|
|
70
87
|
.select("id")
|
|
71
|
-
.where("name", "=",
|
|
88
|
+
.where("name", "=", areaInput.name)
|
|
72
89
|
.executeTakeFirst();
|
|
73
90
|
|
|
74
91
|
if (existing) {
|
|
75
|
-
return apiError("CONFLICT", `Widget area with name "${
|
|
92
|
+
return apiError("CONFLICT", `Widget area with name "${areaInput.name}" already exists`, 409);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const actor = resolveHitlRouteActor(locals);
|
|
96
|
+
const evaluator = new RiskPolicyEvaluator({
|
|
97
|
+
db,
|
|
98
|
+
handlers: dineway,
|
|
99
|
+
});
|
|
100
|
+
let approvedHitlRequestId: string | null = null;
|
|
101
|
+
|
|
102
|
+
if (evaluator.requiresWorkflowHitl(actor.identity)) {
|
|
103
|
+
const action = await new WidgetHitlPayloadBuilder(db).buildCreateWidgetAreaRequest(areaInput);
|
|
104
|
+
const decision = await evaluator.evaluateWorkflowHitl({
|
|
105
|
+
actor: actor.identity,
|
|
106
|
+
hitlRequestId,
|
|
107
|
+
action,
|
|
108
|
+
});
|
|
109
|
+
if (!decision.allowed) {
|
|
110
|
+
const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
|
|
111
|
+
return hitlRequiredRouteError(decision, ensured);
|
|
112
|
+
}
|
|
113
|
+
approvedHitlRequestId = decision.hitlRequest.id;
|
|
76
114
|
}
|
|
77
115
|
|
|
78
116
|
const id = ulid();
|
|
@@ -80,9 +118,9 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
80
118
|
.insertInto("_dineway_widget_areas")
|
|
81
119
|
.values({
|
|
82
120
|
id,
|
|
83
|
-
name:
|
|
84
|
-
label:
|
|
85
|
-
description:
|
|
121
|
+
name: areaInput.name,
|
|
122
|
+
label: areaInput.label,
|
|
123
|
+
description: areaInput.description ?? null,
|
|
86
124
|
})
|
|
87
125
|
.execute();
|
|
88
126
|
|
|
@@ -92,6 +130,18 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
92
130
|
.where("id", "=", id)
|
|
93
131
|
.executeTakeFirstOrThrow();
|
|
94
132
|
|
|
133
|
+
await logWidgetActivity(db, locals, {
|
|
134
|
+
action: "area_created",
|
|
135
|
+
areaId: area.id,
|
|
136
|
+
areaName: area.name,
|
|
137
|
+
...widgetApiRouteSource("area_created"),
|
|
138
|
+
detail: {
|
|
139
|
+
label: area.label,
|
|
140
|
+
description: area.description,
|
|
141
|
+
hitlRequestId: approvedHitlRequestId,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
95
145
|
return apiSuccess(area, 201);
|
|
96
146
|
} catch (error) {
|
|
97
147
|
return handleError(error, "Failed to create widget area", "WIDGET_AREA_CREATE_ERROR");
|
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
import type { PublicPageContext, PageMetadataContribution } from "../plugins/types.js";
|
|
17
17
|
import { resolvePageMetadata, renderPageMetadata } from "../page/metadata.js";
|
|
18
18
|
import { renderFragments } from "../page/fragments.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
generateBaseSeoContributions,
|
|
21
|
+
generateSiteSeoContributions,
|
|
22
|
+
} from "../page/seo-contributions.js";
|
|
20
23
|
import { getPageRuntime } from "../page/index.js";
|
|
24
|
+
import { getSiteSetting } from "../settings/index.js";
|
|
21
25
|
|
|
22
26
|
interface Props {
|
|
23
27
|
page: PublicPageContext;
|
|
@@ -33,14 +37,18 @@ let metadataHtml = "";
|
|
|
33
37
|
let fragmentsHtml = "";
|
|
34
38
|
|
|
35
39
|
if (runtime) {
|
|
36
|
-
// Plugin contributions come BEFORE base, so resolvePageMetadata's
|
|
37
|
-
// first-wins dedup lets plugins override
|
|
38
|
-
const pluginContributions = await
|
|
39
|
-
|
|
40
|
+
// Plugin contributions come BEFORE site/base, so resolvePageMetadata's
|
|
41
|
+
// first-wins dedup lets plugins override defaults.
|
|
42
|
+
const [seoSettings, pluginContributions, fragments] = await Promise.all([
|
|
43
|
+
getSiteSetting("seo"),
|
|
44
|
+
runtime.collectPageMetadata(page),
|
|
45
|
+
runtime.collectPageFragments(page),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const siteContributions = generateSiteSeoContributions(seoSettings);
|
|
49
|
+
const allContributions = [...pluginContributions, ...siteContributions, ...baseContributions];
|
|
40
50
|
const resolved = resolvePageMetadata(allContributions);
|
|
41
51
|
metadataHtml = renderPageMetadata(resolved);
|
|
42
|
-
|
|
43
|
-
const fragments = await runtime.collectPageFragments(page);
|
|
44
52
|
fragmentsHtml = renderFragments(fragments, "head");
|
|
45
53
|
} else {
|
|
46
54
|
// No runtime (Dineway not initialized) — still render base SEO
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Dineway Media component
|
|
4
4
|
*
|
|
5
|
-
* Unified component for rendering media from any provider (local,
|
|
5
|
+
* Unified component for rendering media from any provider (local, remote image services, etc.)
|
|
6
6
|
* Calls the provider's getEmbed() method at render time for proper URL generation.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
@@ -1117,7 +1117,7 @@ function InlineMediaPicker({
|
|
|
1117
1117
|
setUploading(true);
|
|
1118
1118
|
try {
|
|
1119
1119
|
// Detect dimensions and generate a thumbnail for large images to
|
|
1120
|
-
// avoid OOM in server-side blurhash generation on
|
|
1120
|
+
// avoid OOM in server-side blurhash generation on constrained runtimes.
|
|
1121
1121
|
const dims = await new Promise<{
|
|
1122
1122
|
width?: number;
|
|
1123
1123
|
height?: number;
|