@aphexcms/cms-core 9.8.0 → 9.9.0

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 (236) hide show
  1. package/dist/api/organizations.d.ts +2 -1
  2. package/dist/api/organizations.d.ts.map +1 -1
  3. package/dist/api/organizations.js +2 -1
  4. package/dist/api/schemas/jobs.d.ts +15 -0
  5. package/dist/api/schemas/jobs.d.ts.map +1 -0
  6. package/dist/api/schemas/jobs.js +17 -0
  7. package/dist/api/schemas/user.d.ts.map +1 -1
  8. package/dist/api/schemas/user.js +6 -2
  9. package/dist/auth/account-deletion.d.ts +35 -0
  10. package/dist/auth/account-deletion.d.ts.map +1 -0
  11. package/dist/auth/account-deletion.js +65 -0
  12. package/dist/auth/bootstrap.d.ts +104 -0
  13. package/dist/auth/bootstrap.d.ts.map +1 -0
  14. package/dist/auth/bootstrap.js +164 -0
  15. package/dist/auth/instance-state.d.ts +13 -0
  16. package/dist/auth/instance-state.d.ts.map +1 -0
  17. package/dist/auth/instance-state.js +30 -0
  18. package/dist/auth/invitation-status.d.ts +25 -0
  19. package/dist/auth/invitation-status.d.ts.map +1 -0
  20. package/dist/auth/invitation-status.js +41 -0
  21. package/dist/auth/sign-up-policy.d.ts +62 -0
  22. package/dist/auth/sign-up-policy.d.ts.map +1 -0
  23. package/dist/auth/sign-up-policy.js +120 -0
  24. package/dist/components/admin/ActivityView.svelte +263 -13
  25. package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
  26. package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
  27. package/dist/db/interfaces/events.d.ts +29 -2
  28. package/dist/db/interfaces/events.d.ts.map +1 -1
  29. package/dist/db/interfaces/index.d.ts +1 -0
  30. package/dist/db/interfaces/index.d.ts.map +1 -1
  31. package/dist/db/interfaces/index.js +1 -0
  32. package/dist/db/interfaces/instance.d.ts +29 -0
  33. package/dist/db/interfaces/instance.d.ts.map +1 -1
  34. package/dist/db/interfaces/instance.js +8 -1
  35. package/dist/events/built-in-consumers.d.ts +20 -0
  36. package/dist/events/built-in-consumers.d.ts.map +1 -0
  37. package/dist/events/built-in-consumers.js +49 -0
  38. package/dist/events/catalog.d.ts +16 -0
  39. package/dist/events/catalog.d.ts.map +1 -1
  40. package/dist/events/catalog.js +17 -0
  41. package/dist/events/consumer.d.ts +14 -0
  42. package/dist/events/consumer.d.ts.map +1 -1
  43. package/dist/events/consumer.js +1 -0
  44. package/dist/events/emit.d.ts +23 -0
  45. package/dist/events/emit.d.ts.map +1 -1
  46. package/dist/events/emit.js +32 -1
  47. package/dist/events/index.d.ts +2 -0
  48. package/dist/events/index.d.ts.map +1 -1
  49. package/dist/events/index.js +2 -0
  50. package/dist/field-validation/utils.d.ts +27 -4
  51. package/dist/field-validation/utils.d.ts.map +1 -1
  52. package/dist/field-validation/utils.js +78 -10
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3 -0
  56. package/dist/jobs/run-batch.d.ts +7 -1
  57. package/dist/jobs/run-batch.d.ts.map +1 -1
  58. package/dist/jobs/run-batch.js +3 -1
  59. package/dist/lib/api/organizations.d.ts +2 -1
  60. package/dist/lib/api/organizations.d.ts.map +1 -1
  61. package/dist/lib/api/organizations.js +2 -1
  62. package/dist/lib/api/organizations.js.map +1 -1
  63. package/dist/lib/api/schemas/api-keys.d.ts +4 -4
  64. package/dist/lib/api/schemas/jobs.d.ts +15 -0
  65. package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
  66. package/dist/lib/api/schemas/jobs.js +18 -0
  67. package/dist/lib/api/schemas/jobs.js.map +1 -0
  68. package/dist/lib/api/schemas/user.d.ts.map +1 -1
  69. package/dist/lib/api/schemas/user.js +6 -2
  70. package/dist/lib/api/schemas/user.js.map +1 -1
  71. package/dist/lib/auth/account-deletion.d.ts +35 -0
  72. package/dist/lib/auth/account-deletion.d.ts.map +1 -0
  73. package/dist/lib/auth/account-deletion.js +66 -0
  74. package/dist/lib/auth/account-deletion.js.map +1 -0
  75. package/dist/lib/auth/bootstrap.d.ts +104 -0
  76. package/dist/lib/auth/bootstrap.d.ts.map +1 -0
  77. package/dist/lib/auth/bootstrap.js +165 -0
  78. package/dist/lib/auth/bootstrap.js.map +1 -0
  79. package/dist/lib/auth/instance-state.d.ts +13 -0
  80. package/dist/lib/auth/instance-state.d.ts.map +1 -0
  81. package/dist/lib/auth/instance-state.js +31 -0
  82. package/dist/lib/auth/instance-state.js.map +1 -0
  83. package/dist/lib/auth/invitation-status.d.ts +25 -0
  84. package/dist/lib/auth/invitation-status.d.ts.map +1 -0
  85. package/dist/lib/auth/invitation-status.js +42 -0
  86. package/dist/lib/auth/invitation-status.js.map +1 -0
  87. package/dist/lib/auth/sign-up-policy.d.ts +62 -0
  88. package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
  89. package/dist/lib/auth/sign-up-policy.js +121 -0
  90. package/dist/lib/auth/sign-up-policy.js.map +1 -0
  91. package/dist/lib/db/interfaces/events.d.ts +29 -2
  92. package/dist/lib/db/interfaces/events.d.ts.map +1 -1
  93. package/dist/lib/db/interfaces/index.d.ts +1 -0
  94. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  95. package/dist/lib/db/interfaces/index.js +1 -0
  96. package/dist/lib/db/interfaces/index.js.map +1 -1
  97. package/dist/lib/db/interfaces/instance.d.ts +29 -0
  98. package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
  99. package/dist/lib/db/interfaces/instance.js +8 -1
  100. package/dist/lib/db/interfaces/instance.js.map +1 -1
  101. package/dist/lib/events/built-in-consumers.d.ts +20 -0
  102. package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
  103. package/dist/lib/events/built-in-consumers.js +50 -0
  104. package/dist/lib/events/built-in-consumers.js.map +1 -0
  105. package/dist/lib/events/catalog.d.ts +16 -0
  106. package/dist/lib/events/catalog.d.ts.map +1 -1
  107. package/dist/lib/events/catalog.js +17 -0
  108. package/dist/lib/events/catalog.js.map +1 -1
  109. package/dist/lib/events/consumer.d.ts +14 -0
  110. package/dist/lib/events/consumer.d.ts.map +1 -1
  111. package/dist/lib/events/consumer.js +1 -0
  112. package/dist/lib/events/consumer.js.map +1 -1
  113. package/dist/lib/events/emit.d.ts +23 -0
  114. package/dist/lib/events/emit.d.ts.map +1 -1
  115. package/dist/lib/events/emit.js +32 -1
  116. package/dist/lib/events/emit.js.map +1 -1
  117. package/dist/lib/events/index.d.ts +2 -0
  118. package/dist/lib/events/index.d.ts.map +1 -1
  119. package/dist/lib/events/index.js +2 -0
  120. package/dist/lib/events/index.js.map +1 -1
  121. package/dist/lib/field-validation/utils.d.ts +27 -4
  122. package/dist/lib/field-validation/utils.d.ts.map +1 -1
  123. package/dist/lib/field-validation/utils.js +78 -10
  124. package/dist/lib/field-validation/utils.js.map +1 -1
  125. package/dist/lib/index.d.ts +1 -0
  126. package/dist/lib/index.d.ts.map +1 -1
  127. package/dist/lib/index.js +3 -0
  128. package/dist/lib/index.js.map +1 -1
  129. package/dist/lib/jobs/run-batch.d.ts +7 -1
  130. package/dist/lib/jobs/run-batch.d.ts.map +1 -1
  131. package/dist/lib/jobs/run-batch.js +3 -1
  132. package/dist/lib/jobs/run-batch.js.map +1 -1
  133. package/dist/lib/local-api/collection-api.d.ts +42 -1
  134. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  135. package/dist/lib/local-api/collection-api.js +68 -8
  136. package/dist/lib/local-api/collection-api.js.map +1 -1
  137. package/dist/lib/local-api/index.d.ts +1 -1
  138. package/dist/lib/local-api/index.d.ts.map +1 -1
  139. package/dist/lib/local-api/index.js +1 -1
  140. package/dist/lib/local-api/index.js.map +1 -1
  141. package/dist/lib/plugins/resolver.d.ts.map +1 -1
  142. package/dist/lib/plugins/resolver.js +7 -1
  143. package/dist/lib/plugins/resolver.js.map +1 -1
  144. package/dist/lib/server/api/rate-limit.d.ts +38 -0
  145. package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
  146. package/dist/lib/server/api/rate-limit.js +71 -0
  147. package/dist/lib/server/api/rate-limit.js.map +1 -0
  148. package/dist/lib/server/api/routes/agent-change-sets.js +1 -1
  149. package/dist/lib/server/api/routes/agent-change-sets.js.map +1 -1
  150. package/dist/lib/server/api/routes/document-versions.js +1 -1
  151. package/dist/lib/server/api/routes/document-versions.js.map +1 -1
  152. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  153. package/dist/lib/server/api/routes/documents-by-id.js +11 -2
  154. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  155. package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
  156. package/dist/lib/server/api/routes/documents-publish.js +11 -1
  157. package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
  158. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  159. package/dist/lib/server/api/routes/documents.js +10 -0
  160. package/dist/lib/server/api/routes/documents.js.map +1 -1
  161. package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
  162. package/dist/lib/server/api/routes/jobs.js +174 -20
  163. package/dist/lib/server/api/routes/jobs.js.map +1 -1
  164. package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
  165. package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
  166. package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
  167. package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
  168. package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
  169. package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
  170. package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
  171. package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  172. package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
  173. package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
  174. package/dist/lib/server/api/routes/user.d.ts +0 -8
  175. package/dist/lib/server/api/routes/user.d.ts.map +1 -1
  176. package/dist/lib/server/api/routes/user.js +144 -0
  177. package/dist/lib/server/api/routes/user.js.map +1 -1
  178. package/dist/lib/server/index.d.ts +6 -1
  179. package/dist/lib/server/index.d.ts.map +1 -1
  180. package/dist/lib/server/index.js +9 -1
  181. package/dist/lib/server/index.js.map +1 -1
  182. package/dist/lib/services/references-service.d.ts +1 -1
  183. package/dist/lib/services/references-service.d.ts.map +1 -1
  184. package/dist/lib/types/capabilities.d.ts +14 -0
  185. package/dist/lib/types/capabilities.d.ts.map +1 -1
  186. package/dist/lib/types/capabilities.js +37 -18
  187. package/dist/lib/types/capabilities.js.map +1 -1
  188. package/dist/lib/types/events.d.ts +27 -4
  189. package/dist/lib/types/events.d.ts.map +1 -1
  190. package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
  191. package/dist/lib/utils/fetch-remote-file.js +32 -3
  192. package/dist/lib/utils/fetch-remote-file.js.map +1 -1
  193. package/dist/local-api/collection-api.d.ts +42 -1
  194. package/dist/local-api/collection-api.d.ts.map +1 -1
  195. package/dist/local-api/collection-api.js +68 -8
  196. package/dist/local-api/index.d.ts +1 -1
  197. package/dist/local-api/index.d.ts.map +1 -1
  198. package/dist/local-api/index.js +1 -1
  199. package/dist/plugins/resolver.d.ts.map +1 -1
  200. package/dist/plugins/resolver.js +7 -1
  201. package/dist/server/api/rate-limit.d.ts +38 -0
  202. package/dist/server/api/rate-limit.d.ts.map +1 -0
  203. package/dist/server/api/rate-limit.js +70 -0
  204. package/dist/server/api/routes/agent-change-sets.js +1 -1
  205. package/dist/server/api/routes/document-versions.js +1 -1
  206. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  207. package/dist/server/api/routes/documents-by-id.js +11 -2
  208. package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
  209. package/dist/server/api/routes/documents-publish.js +11 -1
  210. package/dist/server/api/routes/documents.d.ts.map +1 -1
  211. package/dist/server/api/routes/documents.js +10 -0
  212. package/dist/server/api/routes/jobs.d.ts.map +1 -1
  213. package/dist/server/api/routes/jobs.js +174 -20
  214. package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
  215. package/dist/server/api/routes/organizations-by-id.js +61 -3
  216. package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
  217. package/dist/server/api/routes/organizations-invitations.js +31 -2
  218. package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
  219. package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  220. package/dist/server/api/routes/resolve-organization-names.js +25 -0
  221. package/dist/server/api/routes/user.d.ts +0 -8
  222. package/dist/server/api/routes/user.d.ts.map +1 -1
  223. package/dist/server/api/routes/user.js +144 -0
  224. package/dist/server/index.d.ts +6 -1
  225. package/dist/server/index.d.ts.map +1 -1
  226. package/dist/server/index.js +9 -1
  227. package/dist/services/references-service.d.ts +1 -1
  228. package/dist/services/references-service.d.ts.map +1 -1
  229. package/dist/types/capabilities.d.ts +14 -0
  230. package/dist/types/capabilities.d.ts.map +1 -1
  231. package/dist/types/capabilities.js +37 -18
  232. package/dist/types/events.d.ts +27 -4
  233. package/dist/types/events.d.ts.map +1 -1
  234. package/dist/utils/fetch-remote-file.d.ts.map +1 -1
  235. package/dist/utils/fetch-remote-file.js +32 -3
  236. package/package.json +6 -6
@@ -2,7 +2,7 @@ import { Hono } from 'hono';
2
2
  import { z } from 'zod';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { hasCapability } from '../../../types/capabilities.js';
5
- import { RevisionConflictError } from '../../../db/interfaces.js';
5
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
6
6
  import { cmsLogger } from '../../../utils/logger.js';
7
7
  import { withCreatedByNames } from './resolve-created-by.js';
8
8
  const listChangeSetsQuery = z.object({
@@ -3,7 +3,7 @@ import { zValidator } from '@hono/zod-validator';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { cmsLogger } from '../../../utils/logger.js';
5
5
  import { hasCapability } from '../../../types/capabilities.js';
6
- import { RevisionConflictError } from '../../../db/interfaces.js';
6
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
7
7
  import { listVersionsQuery, restoreVersionRequest } from '../../../api/schemas/documents.js';
8
8
  export const documentVersionsRouter = new Hono()
9
9
  .get('/:id/versions', zValidator('query', listVersionsQuery, (result, c) => {
@@ -1 +1 @@
1
- {"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-by-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAQ5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAiO5C,CAAC"}
1
+ {"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-by-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CA6O5C,CAAC"}
@@ -2,8 +2,8 @@ import { Hono } from 'hono';
2
2
  import { zValidator } from '@hono/zod-validator';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { PermissionError } from '../../../local-api/permissions.js';
5
- import { SingletonOperationError } from '../../../local-api/collection-api.js';
6
- import { RevisionConflictError } from '../../../db/interfaces.js';
5
+ import { SingletonOperationError, DocumentValidationError } from '../../../local-api/collection-api.js';
6
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
7
7
  import { cmsLogger } from '../../../utils/logger.js';
8
8
  import { updateDocumentRequest } from '../../../api/schemas/documents.js';
9
9
  export const documentsByIdRouter = new Hono()
@@ -108,6 +108,15 @@ export const documentsByIdRouter = new Hono()
108
108
  currentRevision: error.currentRevision
109
109
  }, 409);
110
110
  }
111
+ // A malformed payload is the caller's fault, not the server's.
112
+ if (error instanceof DocumentValidationError) {
113
+ return c.json({
114
+ success: false,
115
+ error: 'Validation failed',
116
+ message: error.message,
117
+ issues: error.errors
118
+ }, 400);
119
+ }
111
120
  if (error instanceof Error && error.message.includes('validation errors')) {
112
121
  return c.json({ success: false, error: 'Validation failed', message: error.message }, 400);
113
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"documents-publish.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CAkU/C,CAAC"}
1
+ {"version":3,"file":"documents-publish.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents-publish.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,QAAQ,CA8U/C,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { Hono } from 'hono';
2
2
  import { authToContext } from '../../../local-api/auth-helpers.js';
3
3
  import { PermissionError } from '../../../local-api/permissions.js';
4
- import { RevisionConflictError } from '../../../db/interfaces.js';
4
+ import { RevisionConflictError } from '../../../db/interfaces/index.js';
5
+ import { DocumentValidationError } from '../../../local-api/collection-api.js';
5
6
  import { cmsLogger } from '../../../utils/logger.js';
6
7
  import { scheduleDocumentRequest, publishDocumentRequest, unpublishDocumentRequest } from '../../../api/schemas/documents.js';
7
8
  export const documentsPublishRouter = new Hono()
@@ -178,6 +179,15 @@ export const documentsPublishRouter = new Hono()
178
179
  currentRevision: error.currentRevision
179
180
  }, 409);
180
181
  }
182
+ // A malformed payload is the caller's fault, not the server's.
183
+ if (error instanceof DocumentValidationError) {
184
+ return c.json({
185
+ success: false,
186
+ error: 'Validation failed',
187
+ message: error.message,
188
+ issues: error.errors
189
+ }, 400);
190
+ }
181
191
  if (error instanceof Error && error.message.includes('validation errors')) {
182
192
  return c.json({
183
193
  success: false,
@@ -1 +1 @@
1
- {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAKzC,eAAO,MAAM,eAAe,EAAE,IAAI,CAAC,QAAQ,CA6MzC,CAAC"}
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/documents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAKzC,eAAO,MAAM,eAAe,EAAE,IAAI,CAAC,QAAQ,CAyNzC,CAAC"}
@@ -2,6 +2,7 @@ import { Hono } from 'hono';
2
2
  import { zValidator } from '@hono/zod-validator';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { PermissionError } from '../../../local-api/permissions.js';
5
+ import { DocumentValidationError } from '../../../local-api/collection-api.js';
5
6
  import { cmsLogger } from '../../../utils/logger.js';
6
7
  import { createDocumentRequest, getDocumentsByIdsQuery, listDocumentsQuery } from '../../../api/schemas/documents.js';
7
8
  const DEFAULT_PAGE_SIZE = 20;
@@ -148,6 +149,15 @@ export const documentsRouter = new Hono()
148
149
  if (error instanceof PermissionError) {
149
150
  return c.json({ success: false, error: 'Forbidden', message: error.message }, 403);
150
151
  }
152
+ // A malformed payload is the caller's fault, not the server's.
153
+ if (error instanceof DocumentValidationError) {
154
+ return c.json({
155
+ success: false,
156
+ error: 'Validation failed',
157
+ message: error.message,
158
+ issues: error.errors
159
+ }, 400);
160
+ }
151
161
  if (error instanceof Error && error.message.includes('validation errors')) {
152
162
  return c.json({ success: false, error: 'Validation failed', message: error.message }, 400);
153
163
  }
@@ -1 +1 @@
1
- {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AA6CzC,eAAO,MAAM,UAAU,EAAE,IAAI,CAAC,QAAQ,CAkDnC,CAAC"}
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AA6HzC,eAAO,MAAM,UAAU,EAAE,IAAI,CAAC,QAAQ,CAuKnC,CAAC"}
@@ -2,23 +2,35 @@ import { Hono } from 'hono';
2
2
  import { z } from 'zod';
3
3
  import { authToContext } from '../../../local-api/auth-helpers.js';
4
4
  import { hasCapability } from '../../../types/capabilities.js';
5
+ import { jobActionRequestSchema } from '../../../api/schemas/jobs.js';
5
6
  import { cmsLogger } from '../../../utils/logger.js';
6
7
  import { withCreatedByNames } from './resolve-created-by.js';
7
- // Read-only history / observability for the durable spine. Gated on `document.read`:
8
- // the operational log of publish work is visible to anyone who can read content.
8
+ import { withOrganizationNames } from './resolve-organization-names.js';
9
+ // Read-only history / observability for the durable spine, plus the two operator actions
10
+ // that make a dead letter recoverable. Reads are gated on `document.read`: the operational
11
+ // log of publish work is visible to anyone who can read content. Mutations are gated on
12
+ // `org.settings` — see `requireJobControl`.
9
13
  // Mirrors JobStatus (types/events) as a zod enum — the API contract's source of truth.
10
14
  const jobStatus = z.enum(['pending', 'leased', 'completed', 'failed', 'cancelled']);
15
+ /**
16
+ * `?scope=all` widens a history read from the active organization to the whole instance.
17
+ * Super admins only — it's the one place the admin UI crosses a tenant boundary.
18
+ */
19
+ const historyScope = z.enum(['organization', 'all']);
11
20
  const listJobsQuery = z.object({
12
21
  status: jobStatus.optional(),
13
22
  type: z.string().optional(),
23
+ scope: historyScope.optional(),
14
24
  limit: z.coerce.number().int().min(1).max(200).optional(),
15
25
  offset: z.coerce.number().int().min(0).optional()
16
26
  });
17
27
  const listEventsQuery = z.object({
18
28
  type: z.string().optional(),
29
+ scope: historyScope.optional(),
19
30
  limit: z.coerce.number().int().min(1).max(200).optional(),
20
31
  offset: z.coerce.number().int().min(0).optional()
21
32
  });
33
+ const healthQuery = z.object({ scope: historyScope.optional() });
22
34
  /** Map an adapter Page into the ApiResponse `pagination` shape the client expects. */
23
35
  function toPagination(page) {
24
36
  const pageSize = page.limit || 1;
@@ -31,6 +43,9 @@ function toPagination(page) {
31
43
  hasPrevPage: page.offset > 0
32
44
  };
33
45
  }
46
+ function isSuperAdmin(auth) {
47
+ return auth.type === 'session' && auth.user.role === 'super_admin';
48
+ }
34
49
  function requireHistoryAccess(c) {
35
50
  const auth = c.var.auth;
36
51
  if (!auth || auth.type === 'partial_session') {
@@ -39,7 +54,55 @@ function requireHistoryAccess(c) {
39
54
  if (!hasCapability(auth, 'document.read')) {
40
55
  return { error: c.json({ success: false, error: 'Insufficient permissions' }, 403) };
41
56
  }
42
- return { organizationId: authToContext(auth).organizationId };
57
+ return { auth, organizationId: authToContext(auth).organizationId };
58
+ }
59
+ /**
60
+ * Gate for retry/cancel — `org.settings`, which only `admin` and `owner` hold.
61
+ *
62
+ * Requeueing a job *runs* it, and a job can be anything: a scheduled publish, a GDPR
63
+ * erasure, a plugin's webhook delivery. There's no single content capability that covers
64
+ * that surface, so the gate is the operational one. It's not a widening: both roles that
65
+ * hold `org.settings` already hold every document and asset capability, so retrying can't
66
+ * let anyone cause an effect they couldn't cause directly.
67
+ */
68
+ function requireJobControl(c) {
69
+ const auth = c.var.auth;
70
+ if (!auth || auth.type === 'partial_session') {
71
+ return { error: c.json({ success: false, error: 'Authentication required' }, 401) };
72
+ }
73
+ if (!hasCapability(auth, 'org.settings')) {
74
+ return { error: c.json({ success: false, error: 'Insufficient permissions' }, 403) };
75
+ }
76
+ return { auth, organizationId: authToContext(auth).organizationId };
77
+ }
78
+ /**
79
+ * Which organization a read covers: the caller's, or every one of them.
80
+ *
81
+ * `undefined` means instance-wide to the adapter, which bypasses RLS — so this returns it
82
+ * only for a super admin who explicitly asked. Anyone else asking for `scope=all` is quietly
83
+ * scoped back to their own org rather than refused: the parameter is a view preference, and
84
+ * failing the whole page over it would be a worse experience than showing what they can see.
85
+ */
86
+ function readScope(auth, organizationId, scope) {
87
+ return scope === 'all' && isSuperAdmin(auth) ? undefined : organizationId;
88
+ }
89
+ /**
90
+ * Resolve which organization an action targets, and prove the caller may act there.
91
+ *
92
+ * A body `organizationId` is only honored for a super admin acting from the instance-wide
93
+ * view. For everyone else it must match the active organization — a mismatch is a 403, not a
94
+ * silent fallback, because a body that names another tenant is either a bug or an attempt.
95
+ */
96
+ function resolveActionOrganization(c, auth, activeOrganizationId, requested) {
97
+ if (!requested || requested === activeOrganizationId) {
98
+ return { organizationId: activeOrganizationId };
99
+ }
100
+ if (!isSuperAdmin(auth)) {
101
+ return {
102
+ error: c.json({ success: false, error: 'Cannot act on jobs outside your active organization' }, 403)
103
+ };
104
+ }
105
+ return { organizationId: requested };
43
106
  }
44
107
  export const jobsRouter = new Hono()
45
108
  .get('/jobs', async (c) => {
@@ -51,21 +114,115 @@ export const jobsRouter = new Hono()
51
114
  if (!parsed.success) {
52
115
  return c.json({ success: false, error: 'Invalid query', issues: parsed.error.issues }, 400);
53
116
  }
54
- const { status, type, limit, offset } = parsed.data;
117
+ const { status, type, scope, limit, offset } = parsed.data;
118
+ const organizationId = readScope(gate.auth, gate.organizationId, scope);
55
119
  const { databaseAdapter } = c.var.aphexCMS;
56
- const page = await databaseAdapter.listJobs({
57
- organizationId: gate.organizationId,
58
- status,
59
- type,
60
- limit,
61
- offset
62
- });
63
- return c.json({ success: true, data: page.items, pagination: toPagination(page) });
120
+ const page = await databaseAdapter.listJobs({ organizationId, status, type, limit, offset });
121
+ // Only the instance-wide view needs org names; org-scoped rows are all the same org.
122
+ const items = organizationId
123
+ ? page.items
124
+ : await withOrganizationNames(page.items, databaseAdapter);
125
+ return c.json({ success: true, data: items, pagination: toPagination(page) });
64
126
  }
65
127
  catch (error) {
66
128
  cmsLogger.error('Failed to list jobs:', error);
67
129
  return c.json({ success: false, error: 'Failed to list jobs' }, 500);
68
130
  }
131
+ })
132
+ .get('/jobs/health', async (c) => {
133
+ try {
134
+ const gate = requireHistoryAccess(c);
135
+ if ('error' in gate)
136
+ return gate.error;
137
+ const parsed = healthQuery.safeParse(c.req.query());
138
+ if (!parsed.success) {
139
+ return c.json({ success: false, error: 'Invalid query', issues: parsed.error.issues }, 400);
140
+ }
141
+ const organizationId = readScope(gate.auth, gate.organizationId, parsed.data.scope);
142
+ const { databaseAdapter } = c.var.aphexCMS;
143
+ const health = await databaseAdapter.outboxHealth({ organizationId });
144
+ return c.json({ success: true, data: health });
145
+ }
146
+ catch (error) {
147
+ cmsLogger.error('Failed to read outbox health:', error);
148
+ return c.json({ success: false, error: 'Failed to read outbox health' }, 500);
149
+ }
150
+ })
151
+ .post('/jobs/:id/retry', async (c) => {
152
+ try {
153
+ const gate = requireJobControl(c);
154
+ if ('error' in gate)
155
+ return gate.error;
156
+ const body = await c.req.json().catch(() => ({}));
157
+ const parsed = jobActionRequestSchema.safeParse(body);
158
+ if (!parsed.success) {
159
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
160
+ }
161
+ const target = resolveActionOrganization(c, gate.auth, gate.organizationId, parsed.data.organizationId);
162
+ if ('error' in target)
163
+ return target.error;
164
+ const id = c.req.param('id');
165
+ const { databaseAdapter } = c.var.aphexCMS;
166
+ const job = await databaseAdapter.requeueJob(target.organizationId, id, {
167
+ runAt: new Date()
168
+ });
169
+ if (!job) {
170
+ // The guard matched nothing: either the job isn't there, or it's in a state that
171
+ // can't be requeued. Read it back so the message says which — "already running"
172
+ // and "no such job" call for completely different reactions from an operator.
173
+ const existing = await databaseAdapter.getJob(target.organizationId, id);
174
+ if (!existing)
175
+ return c.json({ success: false, error: 'Job not found' }, 404);
176
+ return c.json({
177
+ success: false,
178
+ error: `Only failed or cancelled jobs can be retried — this one is ${existing.status}.`
179
+ }, 409);
180
+ }
181
+ cmsLogger.info(`[jobs] Job ${id} (${job.type}) requeued by ${gate.auth.type === 'session' ? gate.auth.user.id : 'api key'}`);
182
+ return c.json({ success: true, data: job });
183
+ }
184
+ catch (error) {
185
+ cmsLogger.error('Failed to retry job:', error);
186
+ return c.json({ success: false, error: 'Failed to retry job' }, 500);
187
+ }
188
+ })
189
+ .post('/jobs/:id/cancel', async (c) => {
190
+ try {
191
+ const gate = requireJobControl(c);
192
+ if ('error' in gate)
193
+ return gate.error;
194
+ const body = await c.req.json().catch(() => ({}));
195
+ const parsed = jobActionRequestSchema.safeParse(body);
196
+ if (!parsed.success) {
197
+ return c.json({ success: false, error: 'Invalid request', issues: parsed.error.issues }, 400);
198
+ }
199
+ const target = resolveActionOrganization(c, gate.auth, gate.organizationId, parsed.data.organizationId);
200
+ if ('error' in target)
201
+ return target.error;
202
+ const id = c.req.param('id');
203
+ const { databaseAdapter } = c.var.aphexCMS;
204
+ const existing = await databaseAdapter.getJob(target.organizationId, id);
205
+ if (!existing)
206
+ return c.json({ success: false, error: 'Job not found' }, 404);
207
+ // `cancelJob` itself is unguarded — the scheduled-publish flow calls it on jobs it
208
+ // knows are pending. Here the state has to be checked first: cancelling a `leased`
209
+ // job would only look like it worked, because the worker holding it settles the row
210
+ // afterwards and overwrites the status.
211
+ if (existing.status !== 'pending' && existing.status !== 'failed') {
212
+ return c.json({
213
+ success: false,
214
+ error: `Only pending or failed jobs can be cancelled — this one is ${existing.status}.`
215
+ }, 409);
216
+ }
217
+ await databaseAdapter.cancelJob(target.organizationId, id);
218
+ const job = await databaseAdapter.getJob(target.organizationId, id);
219
+ cmsLogger.info(`[jobs] Job ${id} (${existing.type}) cancelled by ${gate.auth.type === 'session' ? gate.auth.user.id : 'api key'}`);
220
+ return c.json({ success: true, data: job });
221
+ }
222
+ catch (error) {
223
+ cmsLogger.error('Failed to cancel job:', error);
224
+ return c.json({ success: false, error: 'Failed to cancel job' }, 500);
225
+ }
69
226
  })
70
227
  .get('/events', async (c) => {
71
228
  try {
@@ -76,15 +233,12 @@ export const jobsRouter = new Hono()
76
233
  if (!parsed.success) {
77
234
  return c.json({ success: false, error: 'Invalid query', issues: parsed.error.issues }, 400);
78
235
  }
79
- const { type, limit, offset } = parsed.data;
236
+ const { type, scope, limit, offset } = parsed.data;
237
+ const organizationId = readScope(gate.auth, gate.organizationId, scope);
80
238
  const { databaseAdapter, auth } = c.var.aphexCMS;
81
- const page = await databaseAdapter.listEvents({
82
- organizationId: gate.organizationId,
83
- type,
84
- limit,
85
- offset
86
- });
87
- const items = await withCreatedByNames(page.items, auth);
239
+ const page = await databaseAdapter.listEvents({ organizationId, type, limit, offset });
240
+ const named = await withCreatedByNames(page.items, auth);
241
+ const items = organizationId ? named : await withOrganizationNames(named, databaseAdapter);
88
242
  return c.json({ success: true, data: items, pagination: toPagination(page) });
89
243
  }
90
244
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"organizations-by-id.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/organizations-by-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,eAAO,MAAM,uBAAuB,EAAE,IAAI,CAAC,QAAQ,CAuOhD,CAAC"}
1
+ {"version":3,"file":"organizations-by-id.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/organizations-by-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAkDzC,eAAO,MAAM,uBAAuB,EAAE,IAAI,CAAC,QAAQ,CAiQhD,CAAC"}
@@ -2,7 +2,42 @@ import { Hono } from 'hono';
2
2
  import { zValidator } from '@hono/zod-validator';
3
3
  import { cmsLogger } from '../../../utils/logger.js';
4
4
  import { updateOrganizationRequest } from '../../../api/schemas/organizations.js';
5
- import { hasCapability } from '../../../types/capabilities.js';
5
+ import { isInstanceRole } from '../../../types/capabilities.js';
6
+ /** How many assets to pull per page while erasing an organization's media. */
7
+ const ASSET_ERASE_PAGE_SIZE = 200;
8
+ /**
9
+ * Delete every asset in an organization — object storage included.
10
+ *
11
+ * Paged rather than loaded at once so a large media library doesn't have to fit in
12
+ * memory. Always reads page zero: each pass deletes what it read, so the next unerased
13
+ * asset is always at the front. Best-effort per asset — one failed object shouldn't
14
+ * abandon the rest, and the caller's retry will pick up whatever is left.
15
+ */
16
+ async function eraseOrganizationAssets(assetService, organizationId) {
17
+ for (;;) {
18
+ const page = await assetService.findAssets(organizationId, {
19
+ limit: ASSET_ERASE_PAGE_SIZE,
20
+ offset: 0
21
+ });
22
+ if (page.length === 0)
23
+ return;
24
+ let erased = 0;
25
+ for (const asset of page) {
26
+ try {
27
+ await assetService.deleteAsset(organizationId, asset.id);
28
+ erased++;
29
+ }
30
+ catch (error) {
31
+ cmsLogger.warn(`Could not erase asset ${asset.id} while deleting org:`, error);
32
+ }
33
+ }
34
+ // Nothing went away, so paging again would loop forever on the same failures.
35
+ if (erased === 0) {
36
+ cmsLogger.error(`Stopped erasing assets for org ${organizationId}: ${page.length} remain and none could be deleted.`);
37
+ return;
38
+ }
39
+ }
40
+ }
6
41
  export const organizationsByIdRouter = new Hono()
7
42
  .get('/:id', async (c) => {
8
43
  try {
@@ -56,7 +91,7 @@ export const organizationsByIdRouter = new Hono()
56
91
  }
57
92
  }), async (c) => {
58
93
  try {
59
- const { databaseAdapter } = c.var.aphexCMS;
94
+ const { databaseAdapter, rolesService } = c.var.aphexCMS;
60
95
  const auth = c.var.auth;
61
96
  const id = c.req.param('id');
62
97
  if (!auth || auth.type !== 'session') {
@@ -74,7 +109,17 @@ export const organizationsByIdRouter = new Hono()
74
109
  }, 400);
75
110
  }
76
111
  const membership = await databaseAdapter.findUserMembership(auth.user.id, id);
77
- if (!membership || !hasCapability(auth, 'org.settings')) {
112
+ // Capabilities have to be evaluated against the *target* organization.
113
+ // `auth.capabilities` is resolved once per request for the caller's
114
+ // **active** org, so checking it here let an admin in org A who is only a
115
+ // viewer in org B edit B's settings simply by leaving A active.
116
+ // Instance roles still override — they hold every capability everywhere.
117
+ let canEditSettings = isInstanceRole(auth);
118
+ if (membership && !canEditSettings) {
119
+ const targetCapabilities = await rolesService.getCapabilities(id, membership.role);
120
+ canEditSettings = targetCapabilities.includes('org.settings');
121
+ }
122
+ if (!membership || !canEditSettings) {
78
123
  return c.json({
79
124
  success: false,
80
125
  error: 'Forbidden',
@@ -155,6 +200,19 @@ export const organizationsByIdRouter = new Hono()
155
200
  }
156
201
  }
157
202
  }
203
+ // Files first, while the rows that point at them still exist. Deleting the
204
+ // organization cascades its `cms_assets` rows away, and those rows hold the
205
+ // only record of each object's storage path — so anything not erased here
206
+ // stays in the bucket forever, unreferenced and unfindable, still publicly
207
+ // readable by URL. `assetService.deleteAsset` removes the object *and* the
208
+ // row; the cascade then has less to do.
209
+ //
210
+ // Synchronous and unbounded, which is the known limit: an organization with
211
+ // tens of thousands of assets will make this request slow. The durable fix is
212
+ // to drain them through the job queue, which needs a deletion state machine
213
+ // (the org has to stay readable while its assets are erased) — deliberately
214
+ // not built here. It is safe to retry: an already-erased asset is a no-op.
215
+ await eraseOrganizationAssets(c.var.aphexCMS.assetService, id);
158
216
  await databaseAdapter.removeAllMembers(id);
159
217
  await databaseAdapter.removeAllInvitations(id);
160
218
  await databaseAdapter.deleteOrganization(id);
@@ -1 +1 @@
1
- {"version":3,"file":"organizations-invitations.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/organizations-invitations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,EAAE,IAAI,CAAC,QAAQ,CA4MxD,CAAC"}
1
+ {"version":3,"file":"organizations-invitations.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/organizations-invitations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,EAAE,IAAI,CAAC,QAAQ,CA4OxD,CAAC"}
@@ -3,6 +3,7 @@ import { zValidator } from '@hono/zod-validator';
3
3
  import { cmsLogger } from '../../../utils/logger.js';
4
4
  import { inviteMemberRequest, cancelInvitationRequest } from '../../../api/schemas/organizations.js';
5
5
  import { hasCapability } from '../../../types/capabilities.js';
6
+ import { isPendingInvitation, isStaleInvitation, invitationExpiryFrom } from '../../../auth/invitation-status.js';
6
7
  /**
7
8
  * Note: in studio, invitations are wrapped by a SvelteKit `+server.ts`
8
9
  * that adds email sending after the invite row is created. While that
@@ -38,6 +39,18 @@ export const organizationsInvitationsRouter = new Hono()
38
39
  }, 403);
39
40
  }
40
41
  const body = c.req.valid('json');
42
+ // The same restriction the member role-change route enforces, and for
43
+ // the same reason: `member.changeRole` refuses to promote anyone to
44
+ // owner, so admins must not be able to reach the same place by inviting
45
+ // a brand-new one instead — that detour would let an admin seat an
46
+ // accomplice above the actual owner.
47
+ if (body.role === 'owner' && auth.organizationRole !== 'owner') {
48
+ return c.json({
49
+ success: false,
50
+ error: 'Forbidden',
51
+ message: 'Only owners can invite members as owner'
52
+ }, 403);
53
+ }
41
54
  const roleRow = await databaseAdapter.findRoleByName(auth.organizationId, body.role);
42
55
  if (!roleRow) {
43
56
  return c.json({
@@ -67,7 +80,10 @@ export const organizationsInvitationsRouter = new Hono()
67
80
  }
68
81
  }
69
82
  const existingInvitations = await databaseAdapter.findOrganizationInvitations(auth.organizationId);
70
- const pendingInvitation = existingInvitations.find((inv) => inv.email.toLowerCase() === body.email.toLowerCase() && inv.acceptedAt === null);
83
+ const sameEmail = existingInvitations.filter((inv) => inv.email.toLowerCase() === body.email.toLowerCase());
84
+ // Arrow, not point-free: `.find(isPendingInvitation)` would pass the array
85
+ // index as the `now` argument.
86
+ const pendingInvitation = sameEmail.find((inv) => isPendingInvitation(inv));
71
87
  if (pendingInvitation) {
72
88
  return c.json({
73
89
  success: false,
@@ -75,6 +91,19 @@ export const organizationsInvitationsRouter = new Hono()
75
91
  message: 'This email has already been invited to the organization'
76
92
  }, 400);
77
93
  }
94
+ // Clear lapsed invitations for this address so re-inviting replaces rather
95
+ // than accumulates. `isStaleInvitation` deliberately spares accepted ones —
96
+ // those are the record that somebody joined by invitation. Best-effort: a
97
+ // stale row is inert to every other path, so failing to tidy up shouldn't
98
+ // block the invitation actually being asked for.
99
+ for (const stale of sameEmail.filter((inv) => isStaleInvitation(inv))) {
100
+ try {
101
+ await databaseAdapter.deleteInvitation(stale.id, auth.organizationId);
102
+ }
103
+ catch (error) {
104
+ cmsLogger.warn('Failed to clear expired invitation:', error);
105
+ }
106
+ }
78
107
  const token = crypto.randomUUID();
79
108
  const invitation = await databaseAdapter.createInvitation({
80
109
  organizationId: auth.organizationId,
@@ -82,7 +111,7 @@ export const organizationsInvitationsRouter = new Hono()
82
111
  role: body.role,
83
112
  invitedBy: auth.user.id,
84
113
  token,
85
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
114
+ expiresAt: invitationExpiryFrom()
86
115
  });
87
116
  return c.json({
88
117
  success: true,
@@ -0,0 +1,17 @@
1
+ import type { DatabaseAdapter } from '../../../db/interfaces/index.js';
2
+ /**
3
+ * Resolve `organizationId` on a list of rows to the organization's name.
4
+ *
5
+ * Only needed by the instance-wide history views, where rows from several tenants sit in one
6
+ * table and a bare uuid tells a super admin nothing about which customer is affected. The
7
+ * org-scoped views skip this entirely — every row belongs to the org you're already in.
8
+ *
9
+ * Lookups are deduped and run in parallel; an org that can't be read resolves to `null`
10
+ * rather than failing the page, since a missing name is cosmetic and a 500 is not.
11
+ */
12
+ export declare function withOrganizationNames<T extends {
13
+ organizationId: string;
14
+ }>(rows: T[], db: DatabaseAdapter): Promise<Array<T & {
15
+ organizationName: string | null;
16
+ }>>;
17
+ //# sourceMappingURL=resolve-organization-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-organization-names.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/resolve-organization-names.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,SAAS;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,EAC/E,IAAI,EAAE,CAAC,EAAE,EACT,EAAE,EAAE,eAAe,GACjB,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG;IAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC,CAgBzD"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Resolve `organizationId` on a list of rows to the organization's name.
3
+ *
4
+ * Only needed by the instance-wide history views, where rows from several tenants sit in one
5
+ * table and a bare uuid tells a super admin nothing about which customer is affected. The
6
+ * org-scoped views skip this entirely — every row belongs to the org you're already in.
7
+ *
8
+ * Lookups are deduped and run in parallel; an org that can't be read resolves to `null`
9
+ * rather than failing the page, since a missing name is cosmetic and a 500 is not.
10
+ */
11
+ export async function withOrganizationNames(rows, db) {
12
+ const ids = [...new Set(rows.map((r) => r.organizationId))];
13
+ const names = new Map();
14
+ await Promise.all(ids.map(async (id) => {
15
+ try {
16
+ const org = await db.findOrganizationById(id);
17
+ if (org)
18
+ names.set(id, org.name);
19
+ }
20
+ catch {
21
+ // Unreadable org — leave the name null and show the id.
22
+ }
23
+ }));
24
+ return rows.map((r) => ({ ...r, organizationName: names.get(r.organizationId) ?? null }));
25
+ }
@@ -1,12 +1,4 @@
1
1
  import { Hono } from 'hono';
2
2
  import type { AphexEnv } from '../index.js';
3
- /**
4
- * User account routes that delegate to the configured AuthProvider.
5
- *
6
- * cms-core ships these as Hono routers so studio doesn't need to maintain
7
- * SvelteKit `+server.ts` files for them. The wire format and side effects
8
- * (e.g. the password-reset email) are owned by the AuthProvider impl —
9
- * cms-core's role is just to expose them over HTTP.
10
- */
11
3
  export declare const userRouter: Hono<AphexEnv>;
12
4
  //# sourceMappingURL=user.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAQ5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,IAAI,CAAC,QAAQ,CA2JpC,CAAC"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../../src/lib/server/api/routes/user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAoJzC,eAAO,MAAM,UAAU,EAAE,IAAI,CAAC,QAAQ,CAmNpC,CAAC"}