dineway 0.1.4 → 0.1.6

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.
Files changed (193) hide show
  1. package/README.md +6 -3
  2. package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
  3. package/dist/astro/index.d.mts +18 -9
  4. package/dist/astro/index.mjs +238 -16
  5. package/dist/astro/middleware/auth.d.mts +16 -5
  6. package/dist/astro/middleware/auth.mjs +74 -37
  7. package/dist/astro/middleware/redirect.mjs +24 -8
  8. package/dist/astro/middleware/request-context.mjs +18 -5
  9. package/dist/astro/middleware/setup.mjs +1 -1
  10. package/dist/astro/middleware.mjs +411 -169
  11. package/dist/astro/types.d.mts +25 -8
  12. package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
  13. package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
  14. package/dist/cache-BdSY-gQN.mjs +42 -0
  15. package/dist/chunks--4F8ddV4.mjs +18 -0
  16. package/dist/cli/index.mjs +935 -15
  17. package/dist/client/external-auth-headers.d.mts +1 -1
  18. package/dist/client/index.d.mts +11 -3
  19. package/dist/client/index.mjs +4 -3
  20. package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
  21. package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
  22. package/dist/database/instrumentation.d.mts +34 -0
  23. package/dist/database/instrumentation.mjs +53 -0
  24. package/dist/db/index.d.mts +3 -3
  25. package/dist/db/index.mjs +2 -2
  26. package/dist/db/libsql.d.mts +1 -1
  27. package/dist/db/libsql.mjs +11 -5
  28. package/dist/db/postgres.d.mts +1 -1
  29. package/dist/db/sqlite.d.mts +1 -1
  30. package/dist/db/sqlite.mjs +7 -1
  31. package/dist/db-errors-CEqD7qH9.mjs +23 -0
  32. package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
  33. package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
  34. package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
  35. package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
  36. package/dist/index.d.mts +11 -11
  37. package/dist/index.mjs +24 -22
  38. package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
  39. package/dist/media/index.d.mts +1 -1
  40. package/dist/media/index.mjs +1 -1
  41. package/dist/media/local-runtime.d.mts +7 -7
  42. package/dist/page/index.d.mts +10 -2
  43. package/dist/page/index.mjs +22 -1
  44. package/dist/patterns-CrCYkMBb.mjs +92 -0
  45. package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
  46. package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
  47. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  48. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  49. package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
  50. package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
  51. package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
  52. package/dist/request-cache-Dk5qPSOx.mjs +66 -0
  53. package/dist/request-context.d.mts +4 -16
  54. package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
  55. package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
  56. package/dist/runtime.d.mts +6 -6
  57. package/dist/runtime.mjs +2 -2
  58. package/dist/{search-BNruJHDL.mjs → search-BApX1xhM.mjs} +570 -424
  59. package/dist/seed/index.d.mts +2 -2
  60. package/dist/seed/index.mjs +11 -10
  61. package/dist/seo/index.d.mts +1 -1
  62. package/dist/storage/local.d.mts +1 -1
  63. package/dist/storage/local.mjs +1 -1
  64. package/dist/storage/s3.d.mts +11 -3
  65. package/dist/storage/s3.mjs +78 -15
  66. package/dist/taxonomies-1s5PaS_8.mjs +266 -0
  67. package/dist/transaction-Cn2rjY78.mjs +27 -0
  68. package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
  69. package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
  70. package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
  71. package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
  72. package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
  73. package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
  74. package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
  75. package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
  76. package/dist/version-hmtC3Cmv.mjs +6 -0
  77. package/package.json +49 -38
  78. package/src/astro/routes/admin.astro +25 -9
  79. package/src/astro/routes/api/admin/api-tokens/[id].ts +4 -0
  80. package/src/astro/routes/api/admin/api-tokens/index.ts +24 -2
  81. package/src/astro/routes/api/admin/briefing.ts +76 -0
  82. package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -0
  83. package/src/astro/routes/api/admin/bylines/index.ts +2 -0
  84. package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
  85. package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
  86. package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
  87. package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
  88. package/src/astro/routes/api/admin/context/diff.ts +35 -0
  89. package/src/astro/routes/api/admin/context/index.ts +69 -0
  90. package/src/astro/routes/api/admin/context/stale.ts +35 -0
  91. package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
  92. package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
  93. package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
  94. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +58 -17
  95. package/src/astro/routes/api/admin/oauth-clients/[id].ts +28 -1
  96. package/src/astro/routes/api/admin/oauth-clients/index.ts +25 -1
  97. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +54 -2
  98. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +54 -2
  99. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +51 -1
  100. package/src/astro/routes/api/admin/plugins/[id]/update.ts +98 -3
  101. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +72 -1
  102. package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
  103. package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
  104. package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
  105. package/src/astro/routes/api/admin/users/[id]/disable.ts +26 -23
  106. package/src/astro/routes/api/admin/users/[id]/index.ts +41 -21
  107. package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
  108. package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
  109. package/src/astro/routes/api/auth/passkey/options.ts +2 -1
  110. package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
  111. package/src/astro/routes/api/auth/signup/request.ts +20 -8
  112. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +3 -4
  113. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
  114. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +16 -2
  115. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +16 -0
  116. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +9 -0
  117. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
  118. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +45 -1
  119. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +12 -2
  120. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
  121. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +24 -0
  122. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +3 -0
  123. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +20 -0
  124. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +13 -0
  125. package/src/astro/routes/api/content/[collection]/[id].ts +36 -0
  126. package/src/astro/routes/api/content/[collection]/index.ts +48 -4
  127. package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
  128. package/src/astro/routes/api/health.ts +54 -0
  129. package/src/astro/routes/api/import/wordpress/analyze.ts +2 -10
  130. package/src/astro/routes/api/import/wordpress/execute.ts +40 -6
  131. package/src/astro/routes/api/import/wordpress/prepare.ts +36 -5
  132. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +33 -1
  133. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +3 -3
  134. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +57 -15
  135. package/src/astro/routes/api/manifest.ts +13 -1
  136. package/src/astro/routes/api/mcp.ts +1 -0
  137. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
  138. package/src/astro/routes/api/media/upload-url.ts +11 -2
  139. package/src/astro/routes/api/media.ts +9 -7
  140. package/src/astro/routes/api/menus/[name]/items.ts +124 -5
  141. package/src/astro/routes/api/menus/[name]/reorder.ts +47 -1
  142. package/src/astro/routes/api/menus/[name].ts +84 -4
  143. package/src/astro/routes/api/menus/index.ts +46 -2
  144. package/src/astro/routes/api/oauth/authorize.ts +21 -8
  145. package/src/astro/routes/api/oauth/device/code.ts +2 -1
  146. package/src/astro/routes/api/oauth/device/token.ts +2 -1
  147. package/src/astro/routes/api/oauth/register.ts +182 -0
  148. package/src/astro/routes/api/oauth/token.ts +18 -7
  149. package/src/astro/routes/api/openapi.json.ts +3 -2
  150. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +21 -4
  151. package/src/astro/routes/api/redirects/[id].ts +103 -4
  152. package/src/astro/routes/api/redirects/index.ts +50 -2
  153. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +28 -0
  154. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +15 -0
  155. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +13 -0
  156. package/src/astro/routes/api/schema/collections/[slug]/index.ts +27 -0
  157. package/src/astro/routes/api/schema/collections/index.ts +14 -0
  158. package/src/astro/routes/api/search/index.ts +1 -0
  159. package/src/astro/routes/api/search/suggest.ts +1 -0
  160. package/src/astro/routes/api/sections/[slug].ts +123 -4
  161. package/src/astro/routes/api/sections/index.ts +57 -2
  162. package/src/astro/routes/api/settings.ts +51 -2
  163. package/src/astro/routes/api/setup/admin-verify.ts +25 -5
  164. package/src/astro/routes/api/setup/admin.ts +16 -8
  165. package/src/astro/routes/api/setup/index.ts +3 -2
  166. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +141 -4
  167. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +64 -2
  168. package/src/astro/routes/api/taxonomies/index.ts +57 -2
  169. package/src/astro/routes/api/well-known/auth.ts +3 -1
  170. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +8 -5
  171. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
  172. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +58 -16
  173. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +124 -38
  174. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +66 -20
  175. package/src/astro/routes/api/widget-areas/[name].ts +55 -7
  176. package/src/astro/routes/api/widget-areas/index.ts +56 -6
  177. package/src/components/DinewayHead.astro +15 -7
  178. package/src/components/DinewayMedia.astro +1 -1
  179. package/src/components/InlinePortableTextEditor.tsx +1 -1
  180. package/src/components/Table.astro +68 -41
  181. package/src/components/index.ts +2 -12
  182. package/src/components/marks.ts +19 -0
  183. package/LICENSE +0 -9
  184. /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
  185. /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
  186. /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
  187. /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
  188. /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
  189. /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
  190. /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
  191. /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
  192. /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
  193. /package/dist/{types-DShnjzb6.mjs → types-griIBQOQ.mjs} +0 -0
@@ -7,15 +7,36 @@
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 { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
13
14
  import { handleTermDelete, handleTermGet, handleTermUpdate } from "#api/handlers/taxonomies.js";
14
- import { isParseError, parseBody } from "#api/parse.js";
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 { updateTermBody } from "#api/schemas.js";
22
+ import {
23
+ activityChangedKeys,
24
+ logTaxonomyActivity,
25
+ RiskPolicyEvaluator,
26
+ taxonomyApiRouteSource,
27
+ TaxonomyHitlPayloadBuilder,
28
+ } from "#site-context/index.js";
16
29
 
17
30
  export const prerender = false;
18
31
 
32
+ const updateTermHitlBody = updateTermBody.extend({
33
+ hitlRequestId: z.string().min(1).optional(),
34
+ });
35
+
36
+ const deleteTermQuery = z.object({
37
+ hitlRequestId: z.string().min(1).optional(),
38
+ });
39
+
19
40
  /**
20
41
  * Get a single term
21
42
  */
@@ -59,10 +80,68 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
59
80
  if (denied) return denied;
60
81
 
61
82
  try {
62
- const body = await parseBody(request, updateTermBody);
83
+ const body = await parseBody(request, updateTermHitlBody);
63
84
  if (isParseError(body)) return body;
64
85
 
65
- const result = await handleTermUpdate(dineway.db, name, slug, body);
86
+ const current = await handleTermGet(dineway.db, name, slug);
87
+ if (!current.success) return unwrapResult(current);
88
+
89
+ const { hitlRequestId, ...termInput } = body;
90
+ const actor = resolveHitlRouteActor(locals);
91
+ const evaluator = new RiskPolicyEvaluator({
92
+ db: dineway.db,
93
+ handlers: dineway,
94
+ });
95
+ let approvedHitlRequestId: string | null = null;
96
+
97
+ if (evaluator.requiresWorkflowHitl(actor.identity)) {
98
+ const payloadBuilder = new TaxonomyHitlPayloadBuilder(dineway.db);
99
+ const taxonomy = await payloadBuilder.loadTaxonomyDefinition(name);
100
+ if (!taxonomy) {
101
+ return apiError("NOT_FOUND", `Taxonomy '${name}' not found`, 404);
102
+ }
103
+
104
+ const action = await payloadBuilder.buildUpdateTermRequest({
105
+ taxonomy,
106
+ currentTerm: {
107
+ id: current.data.term.id,
108
+ name: current.data.term.name,
109
+ slug: current.data.term.slug,
110
+ label: current.data.term.label,
111
+ parentId: current.data.term.parentId,
112
+ description: current.data.term.description ?? null,
113
+ },
114
+ ...termInput,
115
+ });
116
+ const decision = await evaluator.evaluateWorkflowHitl({
117
+ actor: actor.identity,
118
+ hitlRequestId,
119
+ action,
120
+ });
121
+ if (!decision.allowed) {
122
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
123
+ return hitlRequiredRouteError(decision, ensured);
124
+ }
125
+ approvedHitlRequestId = decision.hitlRequest.id;
126
+ }
127
+
128
+ const result = await handleTermUpdate(dineway.db, name, slug, termInput);
129
+ if (result.success) {
130
+ await logTaxonomyActivity(dineway.db, locals, {
131
+ action: "term_updated",
132
+ taxonomyName: name,
133
+ termId: result.data.term.id,
134
+ termSlug: result.data.term.slug,
135
+ ...taxonomyApiRouteSource("term_updated"),
136
+ detail: {
137
+ changedKeys: activityChangedKeys(termInput),
138
+ label: result.data.term.label,
139
+ parentId: result.data.term.parentId,
140
+ description: result.data.term.description ?? null,
141
+ hitlRequestId: approvedHitlRequestId,
142
+ },
143
+ });
144
+ }
66
145
  return unwrapResult(result);
67
146
  } catch (error) {
68
147
  return handleError(error, "Failed to update term", "TERM_UPDATE_ERROR");
@@ -72,7 +151,7 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
72
151
  /**
73
152
  * Delete a term
74
153
  */
75
- export const DELETE: APIRoute = async ({ params, locals }) => {
154
+ export const DELETE: APIRoute = async ({ params, request, locals }) => {
76
155
  const { dineway, user } = locals;
77
156
  const { name, slug } = params;
78
157
 
@@ -86,8 +165,66 @@ export const DELETE: APIRoute = async ({ params, locals }) => {
86
165
  const denied = requirePerm(user, "taxonomies:manage");
87
166
  if (denied) return denied;
88
167
 
168
+ const query = parseQuery(new URL(request.url), deleteTermQuery);
169
+ if (isParseError(query)) return query;
170
+
89
171
  try {
172
+ const current = await handleTermGet(dineway.db, name, slug);
173
+ if (!current.success) return unwrapResult(current);
174
+
175
+ const actor = resolveHitlRouteActor(locals);
176
+ const evaluator = new RiskPolicyEvaluator({
177
+ db: dineway.db,
178
+ handlers: dineway,
179
+ });
180
+ let approvedHitlRequestId: string | null = null;
181
+
182
+ if (evaluator.requiresWorkflowHitl(actor.identity)) {
183
+ const payloadBuilder = new TaxonomyHitlPayloadBuilder(dineway.db);
184
+ const taxonomy = await payloadBuilder.loadTaxonomyDefinition(name);
185
+ if (!taxonomy) {
186
+ return apiError("NOT_FOUND", `Taxonomy '${name}' not found`, 404);
187
+ }
188
+
189
+ const action = await payloadBuilder.buildDeleteTermRequest({
190
+ taxonomy,
191
+ currentTerm: {
192
+ id: current.data.term.id,
193
+ name: current.data.term.name,
194
+ slug: current.data.term.slug,
195
+ label: current.data.term.label,
196
+ parentId: current.data.term.parentId,
197
+ description: current.data.term.description ?? null,
198
+ },
199
+ });
200
+ const decision = await evaluator.evaluateWorkflowHitl({
201
+ actor: actor.identity,
202
+ hitlRequestId: query.hitlRequestId,
203
+ action,
204
+ });
205
+ if (!decision.allowed) {
206
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
207
+ return hitlRequiredRouteError(decision, ensured);
208
+ }
209
+ approvedHitlRequestId = decision.hitlRequest.id;
210
+ }
211
+
90
212
  const result = await handleTermDelete(dineway.db, name, slug);
213
+ if (result.success) {
214
+ await logTaxonomyActivity(dineway.db, locals, {
215
+ action: "term_deleted",
216
+ taxonomyName: name,
217
+ termId: current.data.term.id,
218
+ termSlug: current.data.term.slug,
219
+ ...taxonomyApiRouteSource("term_deleted"),
220
+ detail: {
221
+ label: current.data.term.label,
222
+ parentId: current.data.term.parentId,
223
+ description: current.data.term.description ?? null,
224
+ hitlRequestId: approvedHitlRequestId,
225
+ },
226
+ });
227
+ }
91
228
  return unwrapResult(result);
92
229
  } catch (error) {
93
230
  return handleError(error, "Failed to delete term", "TERM_DELETE_ERROR");
@@ -6,15 +6,31 @@
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, handleError, requireDb, unwrapResult } from "#api/error.js";
12
13
  import { handleTermCreate, handleTermList } from "#api/handlers/taxonomies.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 { createTermBody } from "#api/schemas.js";
21
+ import {
22
+ logTaxonomyActivity,
23
+ RiskPolicyEvaluator,
24
+ taxonomyApiRouteSource,
25
+ TaxonomyHitlPayloadBuilder,
26
+ } from "#site-context/index.js";
15
27
 
16
28
  export const prerender = false;
17
29
 
30
+ const createTermHitlBody = createTermBody.extend({
31
+ hitlRequestId: z.string().min(1).optional(),
32
+ });
33
+
18
34
  /**
19
35
  * List all terms for a taxonomy
20
36
  */
@@ -58,10 +74,56 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
58
74
  if (denied) return denied;
59
75
 
60
76
  try {
61
- const body = await parseBody(request, createTermBody);
77
+ const body = await parseBody(request, createTermHitlBody);
62
78
  if (isParseError(body)) return body;
63
79
 
64
- const result = await handleTermCreate(dineway.db, name, body);
80
+ const { hitlRequestId, ...termInput } = body;
81
+ const actor = resolveHitlRouteActor(locals);
82
+ const evaluator = new RiskPolicyEvaluator({
83
+ db: dineway.db,
84
+ handlers: dineway,
85
+ });
86
+ let approvedHitlRequestId: string | null = null;
87
+
88
+ if (evaluator.requiresWorkflowHitl(actor.identity)) {
89
+ const payloadBuilder = new TaxonomyHitlPayloadBuilder(dineway.db);
90
+ const taxonomy = await payloadBuilder.loadTaxonomyDefinition(name);
91
+ if (!taxonomy) {
92
+ return apiError("NOT_FOUND", `Taxonomy '${name}' not found`, 404);
93
+ }
94
+
95
+ const action = await payloadBuilder.buildCreateTermRequest({
96
+ taxonomy,
97
+ ...termInput,
98
+ });
99
+ const decision = await evaluator.evaluateWorkflowHitl({
100
+ actor: actor.identity,
101
+ hitlRequestId,
102
+ action,
103
+ });
104
+ if (!decision.allowed) {
105
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
106
+ return hitlRequiredRouteError(decision, ensured);
107
+ }
108
+ approvedHitlRequestId = decision.hitlRequest.id;
109
+ }
110
+
111
+ const result = await handleTermCreate(dineway.db, name, termInput);
112
+ if (result.success) {
113
+ await logTaxonomyActivity(dineway.db, locals, {
114
+ action: "term_created",
115
+ taxonomyName: name,
116
+ termId: result.data.term.id,
117
+ termSlug: result.data.term.slug,
118
+ ...taxonomyApiRouteSource("term_created"),
119
+ detail: {
120
+ label: result.data.term.label,
121
+ parentId: result.data.term.parentId,
122
+ description: result.data.term.description ?? null,
123
+ hitlRequestId: approvedHitlRequestId,
124
+ },
125
+ });
126
+ }
65
127
  return unwrapResult(result, 201);
66
128
  } catch (error) {
67
129
  return handleError(error, "Failed to create term", "TERM_CREATE_ERROR");
@@ -6,15 +6,31 @@
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 { handleError, requireDb, unwrapResult } from "#api/error.js";
12
13
  import { handleTaxonomyCreate, handleTaxonomyList } from "#api/handlers/taxonomies.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 { createTaxonomyDefBody } from "#api/schemas.js";
21
+ import {
22
+ logTaxonomyActivity,
23
+ RiskPolicyEvaluator,
24
+ taxonomyApiRouteSource,
25
+ TaxonomyHitlPayloadBuilder,
26
+ } from "#site-context/index.js";
15
27
 
16
28
  export const prerender = false;
17
29
 
30
+ const createTaxonomyHitlBody = createTaxonomyDefBody.extend({
31
+ hitlRequestId: z.string().min(1).optional(),
32
+ });
33
+
18
34
  /**
19
35
  * List taxonomy definitions
20
36
  */
@@ -48,10 +64,49 @@ export const POST: APIRoute = async ({ request, locals }) => {
48
64
  if (denied) return denied;
49
65
 
50
66
  try {
51
- const body = await parseBody(request, createTaxonomyDefBody);
67
+ const body = await parseBody(request, createTaxonomyHitlBody);
52
68
  if (isParseError(body)) return body;
53
69
 
54
- const result = await handleTaxonomyCreate(dineway.db, body);
70
+ const { hitlRequestId, ...taxonomyInput } = body;
71
+ const actor = resolveHitlRouteActor(locals);
72
+ const evaluator = new RiskPolicyEvaluator({
73
+ db: dineway.db,
74
+ handlers: dineway,
75
+ });
76
+ let approvedHitlRequestId: string | null = null;
77
+
78
+ if (evaluator.requiresWorkflowHitl(actor.identity)) {
79
+ const action = await new TaxonomyHitlPayloadBuilder(dineway.db).buildCreateTaxonomyRequest(
80
+ taxonomyInput,
81
+ );
82
+ const decision = await evaluator.evaluateWorkflowHitl({
83
+ actor: actor.identity,
84
+ hitlRequestId,
85
+ action,
86
+ });
87
+ if (!decision.allowed) {
88
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
89
+ return hitlRequiredRouteError(decision, ensured);
90
+ }
91
+ approvedHitlRequestId = decision.hitlRequest.id;
92
+ }
93
+
94
+ const result = await handleTaxonomyCreate(dineway.db, taxonomyInput);
95
+ if (result.success) dineway.invalidateManifest();
96
+ if (result.success) {
97
+ await logTaxonomyActivity(dineway.db, locals, {
98
+ action: "created",
99
+ taxonomyId: result.data.taxonomy.id,
100
+ taxonomyName: result.data.taxonomy.name,
101
+ ...taxonomyApiRouteSource("created"),
102
+ detail: {
103
+ label: result.data.taxonomy.label,
104
+ hierarchical: result.data.taxonomy.hierarchical,
105
+ collections: result.data.taxonomy.collections,
106
+ hitlRequestId: approvedHitlRequestId,
107
+ },
108
+ });
109
+ }
55
110
  return unwrapResult(result, 201);
56
111
  } catch (error) {
57
112
  return handleError(error, "Failed to create taxonomy", "TAXONOMY_CREATE_ERROR");
@@ -10,6 +10,8 @@ import type { APIRoute } from "astro";
10
10
  import { getAuthMode } from "#auth/mode.js";
11
11
  import { OptionsRepository } from "#db/repositories/options.js";
12
12
 
13
+ import { VERSION } from "../../../../version.js";
14
+
13
15
  export const prerender = false;
14
16
 
15
17
  export const GET: APIRoute = async ({ locals }) => {
@@ -35,7 +37,7 @@ export const GET: APIRoute = async ({ locals }) => {
35
37
  const response: Record<string, unknown> = {
36
38
  instance: {
37
39
  name: siteName,
38
- version: "0.1.0",
40
+ version: VERSION,
39
41
  },
40
42
  auth: {
41
43
  mode: isExternal ? "external" : "passkey",
@@ -1,8 +1,9 @@
1
1
  /**
2
- * GET /_dineway/.well-known/oauth-authorization-server
2
+ * GET /.well-known/oauth-authorization-server/_dineway
3
3
  *
4
- * RFC 8414 Authorization Server Metadata. Tells MCP clients which
5
- * endpoints to use for OAuth authorization, token exchange, etc.
4
+ * RFC 8414 Authorization Server Metadata. The issuer pathname (`/_dineway`)
5
+ * is appended after the well-known prefix so standards-compliant OAuth/MCP
6
+ * clients can discover the metadata from the issuer URL.
6
7
  *
7
8
  * Public, unauthenticated.
8
9
  */
@@ -10,7 +11,8 @@
10
11
  import type { APIRoute } from "astro";
11
12
 
12
13
  import { getPublicOrigin } from "#api/public-url.js";
13
- import { VALID_SCOPES } from "#auth/api-tokens.js";
14
+ import { ALL_VALID_SCOPES } from "#auth/api-tokens.js";
15
+ import { filterExperimentalSiteContextWorkflowScopes } from "#site-context/experimental-workflows.js";
14
16
 
15
17
  export const prerender = false;
16
18
 
@@ -23,7 +25,7 @@ export const GET: APIRoute = async ({ url, locals }) => {
23
25
  issuer,
24
26
  authorization_endpoint: `${origin}/_dineway/oauth/authorize`,
25
27
  token_endpoint: `${origin}/_dineway/api/oauth/token`,
26
- scopes_supported: [...VALID_SCOPES],
28
+ scopes_supported: filterExperimentalSiteContextWorkflowScopes(ALL_VALID_SCOPES),
27
29
  response_types_supported: ["code"],
28
30
  grant_types_supported: [
29
31
  "authorization_code",
@@ -31,6 +33,7 @@ export const GET: APIRoute = async ({ url, locals }) => {
31
33
  "urn:ietf:params:oauth:grant-type:device_code",
32
34
  ],
33
35
  code_challenge_methods_supported: ["S256"],
36
+ registration_endpoint: `${origin}/_dineway/api/oauth/register`,
34
37
  token_endpoint_auth_methods_supported: ["none"],
35
38
  client_id_metadata_document_supported: true,
36
39
  device_authorization_endpoint: `${origin}/_dineway/api/oauth/device/code`,
@@ -14,7 +14,8 @@
14
14
  import type { APIRoute } from "astro";
15
15
 
16
16
  import { getPublicOrigin } from "#api/public-url.js";
17
- import { VALID_SCOPES } from "#auth/api-tokens.js";
17
+ import { ALL_VALID_SCOPES } from "#auth/api-tokens.js";
18
+ import { filterExperimentalSiteContextWorkflowScopes } from "#site-context/experimental-workflows.js";
18
19
 
19
20
  export const prerender = false;
20
21
 
@@ -25,7 +26,7 @@ export const GET: APIRoute = async ({ url, locals }) => {
25
26
  {
26
27
  resource: `${origin}/_dineway/api/mcp`,
27
28
  authorization_servers: [`${origin}/_dineway`],
28
- scopes_supported: [...VALID_SCOPES],
29
+ scopes_supported: filterExperimentalSiteContextWorkflowScopes(ALL_VALID_SCOPES),
29
30
  bearer_methods_supported: ["header"],
30
31
  },
31
32
  {
@@ -5,14 +5,30 @@
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 { apiError, apiSuccess, handleError } from "#api/error.js";
12
+ import {
13
+ ensureWorkflowHitlRouteRequest,
14
+ hitlRequiredRouteError,
15
+ resolveHitlRouteActor,
16
+ } from "#api/hitl-route-helpers.js";
11
17
  import { isParseError, parseBody } from "#api/parse.js";
12
18
  import { reorderWidgetsBody } from "#api/schemas.js";
19
+ import {
20
+ logWidgetActivity,
21
+ RiskPolicyEvaluator,
22
+ widgetApiRouteSource,
23
+ WidgetHitlPayloadBuilder,
24
+ } from "#site-context/index.js";
13
25
 
14
26
  export const prerender = false;
15
27
 
28
+ const reorderWidgetsHitlBody = reorderWidgetsBody.extend({
29
+ hitlRequestId: z.string().min(1).optional(),
30
+ });
31
+
16
32
  export const POST: APIRoute = async ({ params, request, locals }) => {
17
33
  const { dineway, user } = locals;
18
34
  const db = dineway.db;
@@ -26,37 +42,52 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
26
42
  }
27
43
 
28
44
  try {
29
- // Get the area
30
- const area = await db
31
- .selectFrom("_dineway_widget_areas")
32
- .select("id")
33
- .where("name", "=", name)
34
- .executeTakeFirst();
45
+ const payloadBuilder = new WidgetHitlPayloadBuilder(db);
46
+ const area = await payloadBuilder.loadWidgetAreaSnapshot(name);
35
47
 
36
48
  if (!area) {
37
49
  return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
38
50
  }
39
51
 
40
- const body = await parseBody(request, reorderWidgetsBody);
52
+ const body = await parseBody(request, reorderWidgetsHitlBody);
41
53
  if (isParseError(body)) return body;
54
+ const { hitlRequestId, widgetIds } = body;
42
55
 
43
56
  // Verify all widget IDs belong to this area
44
- const existingWidgets = await db
45
- .selectFrom("_dineway_widgets")
46
- .select("id")
47
- .where("area_id", "=", area.id)
48
- .execute();
49
-
50
- const existingIds = new Set(existingWidgets.map((w) => w.id));
51
- for (const id of body.widgetIds) {
57
+ const existingIds = new Set(area.widgets.map((widget) => widget.id));
58
+ for (const id of widgetIds) {
52
59
  if (!existingIds.has(id)) {
53
60
  return apiError("VALIDATION_ERROR", `Widget "${id}" not found in area "${name}"`, 400);
54
61
  }
55
62
  }
56
63
 
64
+ const actor = resolveHitlRouteActor(locals);
65
+ const evaluator = new RiskPolicyEvaluator({
66
+ db,
67
+ handlers: dineway,
68
+ });
69
+ let approvedHitlRequestId: string | null = null;
70
+
71
+ if (evaluator.requiresWorkflowHitl(actor.identity)) {
72
+ const action = await payloadBuilder.buildReorderWidgetsRequest({
73
+ area,
74
+ widgetIds,
75
+ });
76
+ const decision = await evaluator.evaluateWorkflowHitl({
77
+ actor: actor.identity,
78
+ hitlRequestId,
79
+ action,
80
+ });
81
+ if (!decision.allowed) {
82
+ const ensured = await ensureWorkflowHitlRouteRequest(db, locals, decision.action);
83
+ return hitlRequiredRouteError(decision, ensured);
84
+ }
85
+ approvedHitlRequestId = decision.hitlRequest.id;
86
+ }
87
+
57
88
  // Update sort_order for each widget
58
89
  await Promise.all(
59
- body.widgetIds.map((id, index) =>
90
+ widgetIds.map((id, index) =>
60
91
  db
61
92
  .updateTable("_dineway_widgets")
62
93
  .set({ sort_order: index })
@@ -65,6 +96,17 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
65
96
  ),
66
97
  );
67
98
 
99
+ await logWidgetActivity(db, locals, {
100
+ action: "reordered",
101
+ areaId: area.id,
102
+ areaName: area.name,
103
+ ...widgetApiRouteSource("reordered"),
104
+ detail: {
105
+ widgetIds,
106
+ hitlRequestId: approvedHitlRequestId,
107
+ },
108
+ });
109
+
68
110
  return apiSuccess({ success: true });
69
111
  } catch (error) {
70
112
  return handleError(error, "Failed to reorder widgets", "WIDGET_REORDER_ERROR");