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.
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-ByRGV2pq.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-BKXPsfmJ.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
@@ -11,6 +11,11 @@ import { requireOwnerPerm } from "#api/authorize.js";
11
11
  import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
12
12
  import { parseBody, isParseError } from "#api/parse.js";
13
13
  import { contentScheduleBody } from "#api/schemas.js";
14
+ import {
15
+ contentApiRouteSource,
16
+ extractActivityItemId,
17
+ logContentActivity,
18
+ } from "#site-context/activity-events.js";
14
19
 
15
20
  export const prerender = false;
16
21
 
@@ -69,6 +74,17 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
69
74
 
70
75
  if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });
71
76
 
77
+ await logContentActivity(dineway.db, locals, {
78
+ action: "scheduled",
79
+ collection,
80
+ entryId: extractActivityItemId(result.data, resolvedId ?? id),
81
+ ...contentApiRouteSource("scheduled"),
82
+ summary: `Scheduled content in ${collection}`,
83
+ detail: {
84
+ scheduledAt: body.scheduledAt,
85
+ },
86
+ });
87
+
72
88
  return unwrapResult(result);
73
89
  };
74
90
 
@@ -101,5 +117,13 @@ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
101
117
 
102
118
  if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });
103
119
 
120
+ await logContentActivity(dineway.db, locals, {
121
+ action: "unscheduled",
122
+ collection,
123
+ entryId: extractActivityItemId(result.data, resolvedId ?? id),
124
+ ...contentApiRouteSource("unscheduled"),
125
+ summary: `Unscheduled content in ${collection}`,
126
+ });
127
+
104
128
  return unwrapResult(result);
105
129
  };
@@ -12,6 +12,7 @@ import { apiError, apiSuccess, handleError, requireDb } from "#api/error.js";
12
12
  import { parseBody, isParseError } from "#api/parse.js";
13
13
  import { contentTermsBody } from "#api/schemas.js";
14
14
  import { TaxonomyRepository } from "#db/repositories/taxonomy.js";
15
+ import { invalidateTermCache } from "#taxonomies/index.js";
15
16
 
16
17
  export const prerender = false;
17
18
 
@@ -122,6 +123,8 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
122
123
  // Set the terms (replaces existing)
123
124
  await repo.setTermsForEntry(collection, id, taxonomy, termIds);
124
125
 
126
+ invalidateTermCache();
127
+
125
128
  // Get the updated terms
126
129
  const terms = await repo.getTermsForEntry(collection, id, taxonomy);
127
130
 
@@ -6,6 +6,7 @@
6
6
  * Returns all locale variants linked to the same translation group.
7
7
  */
8
8
 
9
+ import { hasPermission } from "@dineway-ai/auth";
9
10
  import type { APIRoute } from "astro";
10
11
 
11
12
  import { requirePerm } from "#api/authorize.js";
@@ -13,6 +14,12 @@ import { apiError, unwrapResult } from "#api/error.js";
13
14
 
14
15
  export const prerender = false;
15
16
 
17
+ function isPublishedTranslation(value: unknown): boolean {
18
+ return (
19
+ typeof value === "object" && value !== null && "status" in value && value.status === "published"
20
+ );
21
+ }
22
+
16
23
  export const GET: APIRoute = async ({ params, locals }) => {
17
24
  const { dineway, user } = locals;
18
25
  const denied = requirePerm(user, "content:read");
@@ -26,5 +33,18 @@ export const GET: APIRoute = async ({ params, locals }) => {
26
33
 
27
34
  const result = await dineway.handleContentTranslations(collection, id);
28
35
 
36
+ if (result.success && !hasPermission(user, "content:read_drafts")) {
37
+ const data = result.data && typeof result.data === "object" ? result.data : {};
38
+ const translations =
39
+ "translations" in data && Array.isArray(data.translations) ? data.translations : [];
40
+ return unwrapResult({
41
+ success: true,
42
+ data: {
43
+ ...data,
44
+ translations: translations.filter(isPublishedTranslation),
45
+ },
46
+ });
47
+ }
48
+
29
49
  return unwrapResult(result);
30
50
  };
@@ -8,6 +8,11 @@ import type { APIRoute } from "astro";
8
8
 
9
9
  import { requireOwnerPerm } from "#api/authorize.js";
10
10
  import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
11
+ import {
12
+ contentApiRouteSource,
13
+ extractActivityItemId,
14
+ logContentActivity,
15
+ } from "#site-context/activity-events.js";
11
16
 
12
17
  export const prerender = false;
13
18
 
@@ -52,5 +57,13 @@ export const POST: APIRoute = async ({ params, locals, cache }) => {
52
57
 
53
58
  if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
54
59
 
60
+ await logContentActivity(dineway.db, locals, {
61
+ action: "unpublished",
62
+ collection,
63
+ entryId: extractActivityItemId(result.data, resolvedId),
64
+ ...contentApiRouteSource("unpublished"),
65
+ summary: `Unpublished content in ${collection}`,
66
+ });
67
+
55
68
  return unwrapResult(result);
56
69
  };
@@ -13,6 +13,12 @@ import { requirePerm, requireOwnerPerm } from "#api/authorize.js";
13
13
  import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
14
14
  import { parseBody, isParseError } from "#api/parse.js";
15
15
  import { contentUpdateBody } from "#api/schemas.js";
16
+ import {
17
+ activityChangedKeys,
18
+ contentApiRouteSource,
19
+ extractActivityItemId,
20
+ logContentActivity,
21
+ } from "#site-context/activity-events.js";
16
22
 
17
23
  export const prerender = false;
18
24
 
@@ -30,6 +36,14 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
30
36
 
31
37
  const result = await dineway.handleContentGet(collection, id, locale);
32
38
 
39
+ if (result.success && !hasPermission(user, "content:read_drafts")) {
40
+ const item = result.data?.item;
41
+ const status = item && typeof item === "object" && "status" in item ? item.status : undefined;
42
+ if (status !== "published") {
43
+ return apiError("NOT_FOUND", "Content not found", 404);
44
+ }
45
+ }
46
+
33
47
  return unwrapResult(result);
34
48
  };
35
49
 
@@ -87,6 +101,20 @@ export const PUT: APIRoute = async ({ params, request, locals, cache }) => {
87
101
 
88
102
  if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
89
103
 
104
+ await logContentActivity(dineway.db, locals, {
105
+ action: "updated",
106
+ collection,
107
+ entryId: extractActivityItemId(result.data, resolvedId),
108
+ ...contentApiRouteSource("updated"),
109
+ summary: `Updated content in ${collection}`,
110
+ detail: {
111
+ changedFields: activityChangedKeys(body.data),
112
+ status: body.status ?? null,
113
+ slugChanged: body.slug !== undefined,
114
+ authorChanged: canChangeAuthor,
115
+ },
116
+ });
117
+
90
118
  return unwrapResult(result);
91
119
  };
92
120
 
@@ -133,5 +161,13 @@ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
133
161
 
134
162
  if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
135
163
 
164
+ await logContentActivity(dineway.db, locals, {
165
+ action: "deleted",
166
+ collection,
167
+ entryId: resolvedId,
168
+ ...contentApiRouteSource("deleted"),
169
+ summary: `Deleted content in ${collection}`,
170
+ });
171
+
136
172
  return unwrapResult(result);
137
173
  };
@@ -5,12 +5,18 @@
5
5
  * POST /_dineway/api/content/{collection} - Create content
6
6
  */
7
7
 
8
+ import { hasPermission } from "@dineway-ai/auth";
8
9
  import type { APIRoute } from "astro";
9
10
 
10
- import { requirePerm } from "#api/authorize.js";
11
- import { apiError, unwrapResult } from "#api/error.js";
11
+ import { requirePerm, requireOwnerPerm } from "#api/authorize.js";
12
+ import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
12
13
  import { parseBody, parseQuery, isParseError } from "#api/parse.js";
13
14
  import { contentListQuery, contentCreateBody } from "#api/schemas.js";
15
+ import {
16
+ contentApiRouteSource,
17
+ extractActivityItemId,
18
+ logContentActivity,
19
+ } from "#site-context/activity-events.js";
14
20
 
15
21
  export const prerender = false;
16
22
 
@@ -26,7 +32,11 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
26
32
  return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
27
33
  }
28
34
 
29
- const result = await dineway.handleContentList(collection, query);
35
+ const listQuery = hasPermission(user, "content:read_drafts")
36
+ ? query
37
+ : { ...query, status: "published" };
38
+
39
+ const result = await dineway.handleContentList(collection, listQuery);
30
40
 
31
41
  return unwrapResult(result);
32
42
  };
@@ -39,10 +49,31 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
39
49
  const body = await parseBody(request, contentCreateBody);
40
50
  if (isParseError(body)) return body;
41
51
 
42
- if (!dineway?.handleContentCreate) {
52
+ if (!dineway?.handleContentCreate || (body.translationOf && !dineway?.handleContentGet)) {
43
53
  return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
44
54
  }
45
55
 
56
+ if (body.translationOf && dineway.handleContentGet) {
57
+ const source = await dineway.handleContentGet(collection, body.translationOf);
58
+ if (!source.success) {
59
+ return apiError(
60
+ source.error?.code ?? "NOT_FOUND",
61
+ source.error?.message ?? "Translation source not found",
62
+ mapErrorStatus(source.error?.code),
63
+ );
64
+ }
65
+
66
+ const sourceAuthorId =
67
+ typeof source.data?.item.authorId === "string" ? source.data.item.authorId : "";
68
+ const translationDenied = requireOwnerPerm(
69
+ user,
70
+ sourceAuthorId,
71
+ "content:edit_own",
72
+ "content:edit_any",
73
+ );
74
+ if (translationDenied) return translationDenied;
75
+ }
76
+
46
77
  // Auto-set authorId to current user when creating content
47
78
  const result = await dineway.handleContentCreate(collection, {
48
79
  ...body,
@@ -55,5 +86,18 @@ export const POST: APIRoute = async ({ params, request, locals, cache }) => {
55
86
 
56
87
  if (cache.enabled) await cache.invalidate({ tags: [collection] });
57
88
 
89
+ await logContentActivity(dineway.db, locals, {
90
+ action: "created",
91
+ collection,
92
+ entryId: extractActivityItemId(result.data),
93
+ ...contentApiRouteSource("created"),
94
+ summary: `Created content in ${collection}`,
95
+ detail: {
96
+ status: body.status ?? "draft",
97
+ locale: body.locale ?? null,
98
+ translationOf: body.translationOf ?? null,
99
+ },
100
+ });
101
+
58
102
  return unwrapResult(result, 201);
59
103
  };
@@ -17,7 +17,7 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
17
17
  const { dineway, user } = locals;
18
18
  const collection = params.collection!;
19
19
 
20
- const denied = requirePerm(user, "content:read");
20
+ const denied = requirePerm(user, "content:read_drafts");
21
21
  if (denied) return denied;
22
22
 
23
23
  if (!dineway?.handleContentListTrashed) {
@@ -0,0 +1,54 @@
1
+ import type { APIRoute } from "astro";
2
+ import { sql } from "kysely";
3
+
4
+ import { VERSION } from "../../../version.js";
5
+
6
+ export const prerender = false;
7
+
8
+ interface HealthResponse {
9
+ status: "ok" | "error";
10
+ version: string;
11
+ database?: "connected" | "disconnected";
12
+ }
13
+
14
+ export const GET: APIRoute = async ({ locals, url }) => {
15
+ const check = url.searchParams.get("check");
16
+
17
+ if (check !== "ready") {
18
+ return Response.json({
19
+ status: "ok",
20
+ version: VERSION,
21
+ } satisfies HealthResponse);
22
+ }
23
+
24
+ try {
25
+ const { dineway } = locals;
26
+ if (!dineway?.db) {
27
+ return Response.json(
28
+ {
29
+ status: "error",
30
+ version: VERSION,
31
+ database: "disconnected",
32
+ } satisfies HealthResponse,
33
+ { status: 503 },
34
+ );
35
+ }
36
+
37
+ await sql`select 1`.execute(dineway.db);
38
+
39
+ return Response.json({
40
+ status: "ok",
41
+ version: VERSION,
42
+ database: "connected",
43
+ } satisfies HealthResponse);
44
+ } catch {
45
+ return Response.json(
46
+ {
47
+ status: "error",
48
+ version: VERSION,
49
+ database: "disconnected",
50
+ } satisfies HealthResponse,
51
+ { status: 503 },
52
+ );
53
+ }
54
+ };
@@ -13,14 +13,12 @@ import mime from "mime/lite";
13
13
 
14
14
  import { requirePerm } from "#api/authorize.js";
15
15
  import { apiError, apiSuccess, handleError } from "#api/error.js";
16
- import { RESERVED_COLLECTION_SLUGS } from "#schema/types.js";
16
+ import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
17
17
  import type { DinewayHandlers } from "#types";
18
18
 
19
19
  export const prerender = false;
20
20
 
21
21
  const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
22
- const INVALID_SLUG_CHARS = /[^a-z0-9_]/g;
23
- const LEADING_NON_ALPHA = /^[^a-z]+/;
24
22
 
25
23
  /** Field compatibility status */
26
24
  export type FieldCompatibility =
@@ -457,13 +455,7 @@ function isInternalMetaKey(key: string): boolean {
457
455
  }
458
456
 
459
457
  function sanitizeSlug(slug: string): string {
460
- const sanitized = slug
461
- .toLowerCase()
462
- .replace(INVALID_SLUG_CHARS, "_")
463
- .replace(LEADING_NON_ALPHA, "");
464
- if (!sanitized) return "imported";
465
- if (RESERVED_COLLECTION_SLUGS.includes(sanitized)) return `wp_${sanitized}`;
466
- return sanitized;
458
+ return sanitizeWordPressImportSlug(slug);
467
459
  }
468
460
 
469
461
  function mapPostTypeToCollection(postType: string): string {
@@ -18,13 +18,18 @@ import {
18
18
 
19
19
  import { requirePerm } from "#api/authorize.js";
20
20
  import { apiError, apiSuccess, handleError } from "#api/error.js";
21
+ import {
22
+ ensureWorkflowHitlRouteRequest,
23
+ hitlRequiredRouteError,
24
+ resolveHitlRouteActor,
25
+ } from "#api/hitl-route-helpers.js";
21
26
  import { BylineRepository } from "#db/repositories/byline.js";
22
27
  import { resolveImportByline } from "#import/utils.js";
28
+ import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
29
+ import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
23
30
  import type { DinewayHandlers, DinewayManifest } from "#types";
24
31
  import { slugify } from "#utils/slugify.js";
25
32
 
26
- import { sanitizeSlug } from "./analyze.js";
27
-
28
33
  export const prerender = false;
29
34
 
30
35
  export interface ImportConfig {
@@ -75,6 +80,11 @@ export const POST: APIRoute = async ({ request, locals }) => {
75
80
  const file = fileEntry instanceof File ? fileEntry : null;
76
81
  const configEntry = formData.get("config");
77
82
  const configJson = typeof configEntry === "string" ? configEntry : null;
83
+ const hitlRequestEntry = formData.get("hitlRequestId");
84
+ const hitlRequestId =
85
+ typeof hitlRequestEntry === "string" && hitlRequestEntry.length > 0
86
+ ? hitlRequestEntry
87
+ : undefined;
78
88
 
79
89
  if (!file) {
80
90
  return apiError("VALIDATION_ERROR", "No file provided", 400);
@@ -85,9 +95,33 @@ export const POST: APIRoute = async ({ request, locals }) => {
85
95
  }
86
96
 
87
97
  const config: ImportConfig = JSON.parse(configJson);
98
+ const actor = resolveHitlRouteActor(locals);
99
+ const evaluator = new RiskPolicyEvaluator({
100
+ db: dineway.db,
101
+ handlers: dineway,
102
+ });
103
+ let text: string;
104
+ if (evaluator.requiresWorkflowHitl(actor.identity)) {
105
+ const fileBuffer = await file.arrayBuffer();
106
+ const action = await new WordPressImportHitlPayloadBuilder().buildExecuteRequest({
107
+ fileName: file.name,
108
+ fileBuffer,
109
+ config,
110
+ });
111
+ const decision = await evaluator.evaluateWorkflowHitl({
112
+ actor: actor.identity,
113
+ hitlRequestId,
114
+ action,
115
+ });
116
+ if (!decision.allowed) {
117
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
118
+ return hitlRequiredRouteError(decision, ensured);
119
+ }
120
+ text = new TextDecoder().decode(fileBuffer);
121
+ } else {
122
+ text = await file.text();
123
+ }
88
124
 
89
- // Parse WXR
90
- const text = await file.text();
91
125
  const wxr = await parseWxrString(text);
92
126
 
93
127
  // Build attachment ID -> URL map for featured images
@@ -170,7 +204,7 @@ async function importContent(
170
204
 
171
205
  // Defensive: mapping.collection is already sanitized by prepare, but the user
172
206
  // could manually edit the import config between prepare and execute.
173
- const collection = sanitizeSlug(mapping.collection);
207
+ const collection = sanitizeWordPressImportSlug(mapping.collection);
174
208
 
175
209
  // Check if collection exists in manifest
176
210
  if (!manifest?.collections[collection]) {
@@ -271,7 +305,7 @@ async function importContent(
271
305
  console.error(`Import error for "${post.title || "Untitled"}":`, error);
272
306
  result.errors.push({
273
307
  title: post.title || "Untitled",
274
- error: "Failed to import item",
308
+ error: error instanceof Error && error.message ? error.message : "Failed to import item",
275
309
  });
276
310
  }
277
311
  }
@@ -8,15 +8,23 @@
8
8
  */
9
9
 
10
10
  import type { APIRoute } from "astro";
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 { wpPrepareBody } from "#api/schemas.js";
22
+ import { sanitizeWordPressImportSlug } from "#import/wordpress-slugs.js";
16
23
  import { FIELD_TYPES, type FieldType } from "#schema/types.js";
24
+ import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
17
25
  import type { DinewayHandlers } from "#types";
18
26
 
19
- import { capitalize, sanitizeSlug, singularize, type ImportFieldDef } from "./analyze.js";
27
+ import { capitalize, singularize, type ImportFieldDef } from "./analyze.js";
20
28
 
21
29
  /** Validate that a string is a known FieldType, returning undefined if not */
22
30
  function asFieldType(value: string): FieldType | undefined {
@@ -26,6 +34,10 @@ function asFieldType(value: string): FieldType | undefined {
26
34
 
27
35
  export const prerender = false;
28
36
 
37
+ const prepareBodySchema = wpPrepareBody.extend({
38
+ hitlRequestId: z.string().min(1).optional(),
39
+ });
40
+
29
41
  interface PrepareRequest {
30
42
  postTypes: Array<{
31
43
  name: string;
@@ -52,11 +64,30 @@ export const POST: APIRoute = async ({ request, locals }) => {
52
64
  if (denied) return denied;
53
65
 
54
66
  try {
55
- const body = await parseBody(request, wpPrepareBody);
67
+ const body = await parseBody(request, prepareBodySchema);
56
68
  if (isParseError(body)) return body;
57
69
 
58
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Zod schema output narrowed to PrepareRequest
59
- const result = await prepareImport(dineway.db, body as PrepareRequest);
70
+ const actor = resolveHitlRouteActor(locals);
71
+ const evaluator = new RiskPolicyEvaluator({
72
+ db: dineway.db,
73
+ handlers: dineway,
74
+ });
75
+ const action = await new WordPressImportHitlPayloadBuilder().buildPrepareRequest({
76
+ postTypes: body.postTypes,
77
+ });
78
+ const decision = await evaluator.evaluateWorkflowHitl({
79
+ actor: actor.identity,
80
+ hitlRequestId: body.hitlRequestId,
81
+ action,
82
+ });
83
+ if (!decision.allowed) {
84
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
85
+ return hitlRequiredRouteError(decision, ensured);
86
+ }
87
+
88
+ const result = await prepareImport(dineway.db, {
89
+ postTypes: body.postTypes,
90
+ });
60
91
 
61
92
  return apiSuccess(result, result.success ? 200 : 400);
62
93
  } catch (error) {
@@ -79,7 +110,7 @@ async function prepareImport(
79
110
  };
80
111
 
81
112
  for (const postType of request.postTypes) {
82
- const collectionSlug = sanitizeSlug(postType.collection);
113
+ const collectionSlug = sanitizeWordPressImportSlug(postType.collection);
83
114
 
84
115
  try {
85
116
  // Check if collection exists
@@ -12,17 +12,29 @@
12
12
 
13
13
  import type { APIRoute } from "astro";
14
14
  import { sql } from "kysely";
15
+ import { z } from "zod";
15
16
 
16
17
  import { requirePerm } from "#api/authorize.js";
17
18
  import { apiError, apiSuccess, handleError } from "#api/error.js";
19
+ import {
20
+ ensureWorkflowHitlRouteRequest,
21
+ hitlRequiredRouteError,
22
+ resolveHitlRouteActor,
23
+ } from "#api/hitl-route-helpers.js";
18
24
  import { isParseError, parseBody } from "#api/parse.js";
19
25
  import { wpRewriteUrlsBody } from "#api/schemas.js";
26
+ import { validateIdentifier } from "#db/validate.js";
20
27
  import { normalizeMediaValue } from "#media/normalize.js";
21
28
  import type { MediaProvider } from "#media/types.js";
29
+ import { RiskPolicyEvaluator, WordPressImportHitlPayloadBuilder } from "#site-context/index.js";
22
30
  import type { DinewayHandlers } from "#types";
23
31
 
24
32
  export const prerender = false;
25
33
 
34
+ const rewriteUrlsBodySchema = wpRewriteUrlsBody.extend({
35
+ hitlRequestId: z.string().min(1).optional(),
36
+ });
37
+
26
38
  const REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
27
39
 
28
40
  export interface RewriteUrlsResult {
@@ -47,7 +59,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
47
59
  if (denied) return denied;
48
60
 
49
61
  try {
50
- const body = await parseBody(request, wpRewriteUrlsBody);
62
+ const body = await parseBody(request, rewriteUrlsBodySchema);
51
63
  if (isParseError(body)) return body;
52
64
 
53
65
  const urlEntries = Object.entries(body.urlMap);
@@ -60,6 +72,25 @@ export const POST: APIRoute = async ({ request, locals }) => {
60
72
  });
61
73
  }
62
74
 
75
+ const actor = resolveHitlRouteActor(locals);
76
+ const evaluator = new RiskPolicyEvaluator({
77
+ db: dineway.db,
78
+ handlers: dineway,
79
+ });
80
+ const action = await new WordPressImportHitlPayloadBuilder().buildRewriteUrlsRequest({
81
+ urlMap: body.urlMap,
82
+ collections: body.collections,
83
+ });
84
+ const decision = await evaluator.evaluateWorkflowHitl({
85
+ actor: actor.identity,
86
+ hitlRequestId: body.hitlRequestId,
87
+ action,
88
+ });
89
+ if (!decision.allowed) {
90
+ const ensured = await ensureWorkflowHitlRouteRequest(dineway.db, locals, decision.action);
91
+ return hitlRequiredRouteError(decision, ensured);
92
+ }
93
+
63
94
  const getProvider = (id: string) => dineway.getMediaProvider(id);
64
95
  const result = await rewriteUrls(dineway.db, body.urlMap, getProvider, body.collections);
65
96
 
@@ -280,6 +311,7 @@ async function rewriteUrls(
280
311
  continue;
281
312
 
282
313
  // Get table name
314
+ validateIdentifier(collection.slug, "collection slug");
283
315
  const tableName = `ec_${collection.slug}`;
284
316
 
285
317
  try {
@@ -15,7 +15,7 @@ import { apiError, apiSuccess, handleError } from "#api/error.js";
15
15
  import { isParseError, parseBody } from "#api/parse.js";
16
16
  import { wpPluginAnalyzeBody } from "#api/schemas.js";
17
17
  import { getSource } from "#import/index.js";
18
- import { validateExternalUrl, SsrfError } from "#import/ssrf.js";
18
+ import { resolveAndValidateExternalUrl, SsrfError } from "#import/ssrf.js";
19
19
  import type { ImportAnalysis } from "#import/types.js";
20
20
  import type { DinewayHandlers } from "#types";
21
21
 
@@ -37,9 +37,9 @@ export const POST: APIRoute = async ({ request, locals }) => {
37
37
  const body = await parseBody(request, wpPluginAnalyzeBody);
38
38
  if (isParseError(body)) return body;
39
39
 
40
- // SSRF: reject internal/private network targets
40
+ // SSRF: reject internal/private network targets before import work starts.
41
41
  try {
42
- validateExternalUrl(body.url);
42
+ await resolveAndValidateExternalUrl(body.url);
43
43
  } catch (e) {
44
44
  const msg = e instanceof SsrfError ? e.message : "Invalid URL";
45
45
  return apiError("SSRF_BLOCKED", msg, 400);