@aphexcms/cms-core 9.8.1 → 9.10.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 (259) 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/documents.d.ts +1 -0
  5. package/dist/api/schemas/documents.d.ts.map +1 -1
  6. package/dist/api/schemas/documents.js +1 -0
  7. package/dist/api/schemas/jobs.d.ts +15 -0
  8. package/dist/api/schemas/jobs.d.ts.map +1 -0
  9. package/dist/api/schemas/jobs.js +17 -0
  10. package/dist/api/schemas/user.d.ts.map +1 -1
  11. package/dist/api/schemas/user.js +6 -2
  12. package/dist/auth/account-deletion.d.ts +35 -0
  13. package/dist/auth/account-deletion.d.ts.map +1 -0
  14. package/dist/auth/account-deletion.js +65 -0
  15. package/dist/auth/bootstrap.d.ts +104 -0
  16. package/dist/auth/bootstrap.d.ts.map +1 -0
  17. package/dist/auth/bootstrap.js +164 -0
  18. package/dist/auth/instance-state.d.ts +13 -0
  19. package/dist/auth/instance-state.d.ts.map +1 -0
  20. package/dist/auth/instance-state.js +30 -0
  21. package/dist/auth/invitation-status.d.ts +25 -0
  22. package/dist/auth/invitation-status.d.ts.map +1 -0
  23. package/dist/auth/invitation-status.js +41 -0
  24. package/dist/auth/sign-up-policy.d.ts +62 -0
  25. package/dist/auth/sign-up-policy.d.ts.map +1 -0
  26. package/dist/auth/sign-up-policy.js +120 -0
  27. package/dist/components/AdminApp.svelte +83 -6
  28. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  29. package/dist/components/admin/ActivityView.svelte +263 -13
  30. package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
  31. package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
  32. package/dist/db/interfaces/document.d.ts +9 -0
  33. package/dist/db/interfaces/document.d.ts.map +1 -1
  34. package/dist/db/interfaces/events.d.ts +29 -2
  35. package/dist/db/interfaces/events.d.ts.map +1 -1
  36. package/dist/db/interfaces/index.d.ts +1 -0
  37. package/dist/db/interfaces/index.d.ts.map +1 -1
  38. package/dist/db/interfaces/index.js +1 -0
  39. package/dist/db/interfaces/instance.d.ts +29 -0
  40. package/dist/db/interfaces/instance.d.ts.map +1 -1
  41. package/dist/db/interfaces/instance.js +8 -1
  42. package/dist/events/built-in-consumers.d.ts +20 -0
  43. package/dist/events/built-in-consumers.d.ts.map +1 -0
  44. package/dist/events/built-in-consumers.js +49 -0
  45. package/dist/events/catalog.d.ts +16 -0
  46. package/dist/events/catalog.d.ts.map +1 -1
  47. package/dist/events/catalog.js +17 -0
  48. package/dist/events/consumer.d.ts +14 -0
  49. package/dist/events/consumer.d.ts.map +1 -1
  50. package/dist/events/consumer.js +1 -0
  51. package/dist/events/emit.d.ts +23 -0
  52. package/dist/events/emit.d.ts.map +1 -1
  53. package/dist/events/emit.js +32 -1
  54. package/dist/events/index.d.ts +2 -0
  55. package/dist/events/index.d.ts.map +1 -1
  56. package/dist/events/index.js +2 -0
  57. package/dist/field-validation/utils.d.ts +27 -4
  58. package/dist/field-validation/utils.d.ts.map +1 -1
  59. package/dist/field-validation/utils.js +78 -10
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +3 -0
  63. package/dist/jobs/run-batch.d.ts +7 -1
  64. package/dist/jobs/run-batch.d.ts.map +1 -1
  65. package/dist/jobs/run-batch.js +3 -1
  66. package/dist/lib/api/organizations.d.ts +2 -1
  67. package/dist/lib/api/organizations.d.ts.map +1 -1
  68. package/dist/lib/api/organizations.js +2 -1
  69. package/dist/lib/api/organizations.js.map +1 -1
  70. package/dist/lib/api/schemas/api-keys.d.ts +4 -4
  71. package/dist/lib/api/schemas/documents.d.ts +1 -0
  72. package/dist/lib/api/schemas/documents.d.ts.map +1 -1
  73. package/dist/lib/api/schemas/documents.js +1 -0
  74. package/dist/lib/api/schemas/documents.js.map +1 -1
  75. package/dist/lib/api/schemas/jobs.d.ts +15 -0
  76. package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
  77. package/dist/lib/api/schemas/jobs.js +18 -0
  78. package/dist/lib/api/schemas/jobs.js.map +1 -0
  79. package/dist/lib/api/schemas/user.d.ts.map +1 -1
  80. package/dist/lib/api/schemas/user.js +6 -2
  81. package/dist/lib/api/schemas/user.js.map +1 -1
  82. package/dist/lib/auth/account-deletion.d.ts +35 -0
  83. package/dist/lib/auth/account-deletion.d.ts.map +1 -0
  84. package/dist/lib/auth/account-deletion.js +66 -0
  85. package/dist/lib/auth/account-deletion.js.map +1 -0
  86. package/dist/lib/auth/bootstrap.d.ts +104 -0
  87. package/dist/lib/auth/bootstrap.d.ts.map +1 -0
  88. package/dist/lib/auth/bootstrap.js +165 -0
  89. package/dist/lib/auth/bootstrap.js.map +1 -0
  90. package/dist/lib/auth/instance-state.d.ts +13 -0
  91. package/dist/lib/auth/instance-state.d.ts.map +1 -0
  92. package/dist/lib/auth/instance-state.js +31 -0
  93. package/dist/lib/auth/instance-state.js.map +1 -0
  94. package/dist/lib/auth/invitation-status.d.ts +25 -0
  95. package/dist/lib/auth/invitation-status.d.ts.map +1 -0
  96. package/dist/lib/auth/invitation-status.js +42 -0
  97. package/dist/lib/auth/invitation-status.js.map +1 -0
  98. package/dist/lib/auth/sign-up-policy.d.ts +62 -0
  99. package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
  100. package/dist/lib/auth/sign-up-policy.js +121 -0
  101. package/dist/lib/auth/sign-up-policy.js.map +1 -0
  102. package/dist/lib/db/interfaces/document.d.ts +9 -0
  103. package/dist/lib/db/interfaces/document.d.ts.map +1 -1
  104. package/dist/lib/db/interfaces/events.d.ts +29 -2
  105. package/dist/lib/db/interfaces/events.d.ts.map +1 -1
  106. package/dist/lib/db/interfaces/index.d.ts +1 -0
  107. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  108. package/dist/lib/db/interfaces/index.js +1 -0
  109. package/dist/lib/db/interfaces/index.js.map +1 -1
  110. package/dist/lib/db/interfaces/instance.d.ts +29 -0
  111. package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
  112. package/dist/lib/db/interfaces/instance.js +8 -1
  113. package/dist/lib/db/interfaces/instance.js.map +1 -1
  114. package/dist/lib/events/built-in-consumers.d.ts +20 -0
  115. package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
  116. package/dist/lib/events/built-in-consumers.js +50 -0
  117. package/dist/lib/events/built-in-consumers.js.map +1 -0
  118. package/dist/lib/events/catalog.d.ts +16 -0
  119. package/dist/lib/events/catalog.d.ts.map +1 -1
  120. package/dist/lib/events/catalog.js +17 -0
  121. package/dist/lib/events/catalog.js.map +1 -1
  122. package/dist/lib/events/consumer.d.ts +14 -0
  123. package/dist/lib/events/consumer.d.ts.map +1 -1
  124. package/dist/lib/events/consumer.js +1 -0
  125. package/dist/lib/events/consumer.js.map +1 -1
  126. package/dist/lib/events/emit.d.ts +23 -0
  127. package/dist/lib/events/emit.d.ts.map +1 -1
  128. package/dist/lib/events/emit.js +32 -1
  129. package/dist/lib/events/emit.js.map +1 -1
  130. package/dist/lib/events/index.d.ts +2 -0
  131. package/dist/lib/events/index.d.ts.map +1 -1
  132. package/dist/lib/events/index.js +2 -0
  133. package/dist/lib/events/index.js.map +1 -1
  134. package/dist/lib/field-validation/utils.d.ts +27 -4
  135. package/dist/lib/field-validation/utils.d.ts.map +1 -1
  136. package/dist/lib/field-validation/utils.js +78 -10
  137. package/dist/lib/field-validation/utils.js.map +1 -1
  138. package/dist/lib/index.d.ts +1 -0
  139. package/dist/lib/index.d.ts.map +1 -1
  140. package/dist/lib/index.js +3 -0
  141. package/dist/lib/index.js.map +1 -1
  142. package/dist/lib/jobs/run-batch.d.ts +7 -1
  143. package/dist/lib/jobs/run-batch.d.ts.map +1 -1
  144. package/dist/lib/jobs/run-batch.js +3 -1
  145. package/dist/lib/jobs/run-batch.js.map +1 -1
  146. package/dist/lib/local-api/collection-api.d.ts +48 -1
  147. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  148. package/dist/lib/local-api/collection-api.js +110 -11
  149. package/dist/lib/local-api/collection-api.js.map +1 -1
  150. package/dist/lib/local-api/index.d.ts +1 -1
  151. package/dist/lib/local-api/index.d.ts.map +1 -1
  152. package/dist/lib/local-api/index.js +1 -1
  153. package/dist/lib/local-api/index.js.map +1 -1
  154. package/dist/lib/plugins/resolver.d.ts.map +1 -1
  155. package/dist/lib/plugins/resolver.js +7 -1
  156. package/dist/lib/plugins/resolver.js.map +1 -1
  157. package/dist/lib/schema-utils/utils.d.ts +31 -1
  158. package/dist/lib/schema-utils/utils.d.ts.map +1 -1
  159. package/dist/lib/schema-utils/utils.js +68 -0
  160. package/dist/lib/schema-utils/utils.js.map +1 -1
  161. package/dist/lib/server/api/rate-limit.d.ts +38 -0
  162. package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
  163. package/dist/lib/server/api/rate-limit.js +71 -0
  164. package/dist/lib/server/api/rate-limit.js.map +1 -0
  165. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  166. package/dist/lib/server/api/routes/documents-by-id.js +10 -1
  167. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  168. package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
  169. package/dist/lib/server/api/routes/documents-publish.js +10 -0
  170. package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
  171. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  172. package/dist/lib/server/api/routes/documents.js +11 -0
  173. package/dist/lib/server/api/routes/documents.js.map +1 -1
  174. package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
  175. package/dist/lib/server/api/routes/jobs.js +174 -20
  176. package/dist/lib/server/api/routes/jobs.js.map +1 -1
  177. package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
  178. package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
  179. package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
  180. package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
  181. package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
  182. package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
  183. package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
  184. package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  185. package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
  186. package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
  187. package/dist/lib/server/api/routes/user.d.ts +0 -8
  188. package/dist/lib/server/api/routes/user.d.ts.map +1 -1
  189. package/dist/lib/server/api/routes/user.js +144 -0
  190. package/dist/lib/server/api/routes/user.js.map +1 -1
  191. package/dist/lib/server/index.d.ts +6 -1
  192. package/dist/lib/server/index.d.ts.map +1 -1
  193. package/dist/lib/server/index.js +9 -1
  194. package/dist/lib/server/index.js.map +1 -1
  195. package/dist/lib/type-gen.d.ts.map +1 -1
  196. package/dist/lib/type-gen.js +11 -5
  197. package/dist/lib/type-gen.js.map +1 -1
  198. package/dist/lib/types/capabilities.d.ts +14 -0
  199. package/dist/lib/types/capabilities.d.ts.map +1 -1
  200. package/dist/lib/types/capabilities.js +37 -18
  201. package/dist/lib/types/capabilities.js.map +1 -1
  202. package/dist/lib/types/events.d.ts +27 -4
  203. package/dist/lib/types/events.d.ts.map +1 -1
  204. package/dist/lib/types/filters.d.ts +31 -0
  205. package/dist/lib/types/filters.d.ts.map +1 -1
  206. package/dist/lib/types/schemas.d.ts +14 -0
  207. package/dist/lib/types/schemas.d.ts.map +1 -1
  208. package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
  209. package/dist/lib/utils/fetch-remote-file.js +32 -3
  210. package/dist/lib/utils/fetch-remote-file.js.map +1 -1
  211. package/dist/local-api/collection-api.d.ts +48 -1
  212. package/dist/local-api/collection-api.d.ts.map +1 -1
  213. package/dist/local-api/collection-api.js +110 -11
  214. package/dist/local-api/index.d.ts +1 -1
  215. package/dist/local-api/index.d.ts.map +1 -1
  216. package/dist/local-api/index.js +1 -1
  217. package/dist/plugins/resolver.d.ts.map +1 -1
  218. package/dist/plugins/resolver.js +7 -1
  219. package/dist/schema-utils/utils.d.ts +31 -1
  220. package/dist/schema-utils/utils.d.ts.map +1 -1
  221. package/dist/schema-utils/utils.js +68 -0
  222. package/dist/server/api/rate-limit.d.ts +38 -0
  223. package/dist/server/api/rate-limit.d.ts.map +1 -0
  224. package/dist/server/api/rate-limit.js +70 -0
  225. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  226. package/dist/server/api/routes/documents-by-id.js +10 -1
  227. package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
  228. package/dist/server/api/routes/documents-publish.js +10 -0
  229. package/dist/server/api/routes/documents.d.ts.map +1 -1
  230. package/dist/server/api/routes/documents.js +11 -0
  231. package/dist/server/api/routes/jobs.d.ts.map +1 -1
  232. package/dist/server/api/routes/jobs.js +174 -20
  233. package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
  234. package/dist/server/api/routes/organizations-by-id.js +61 -3
  235. package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
  236. package/dist/server/api/routes/organizations-invitations.js +31 -2
  237. package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
  238. package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  239. package/dist/server/api/routes/resolve-organization-names.js +25 -0
  240. package/dist/server/api/routes/user.d.ts +0 -8
  241. package/dist/server/api/routes/user.d.ts.map +1 -1
  242. package/dist/server/api/routes/user.js +144 -0
  243. package/dist/server/index.d.ts +6 -1
  244. package/dist/server/index.d.ts.map +1 -1
  245. package/dist/server/index.js +9 -1
  246. package/dist/type-gen.d.ts.map +1 -1
  247. package/dist/type-gen.js +11 -5
  248. package/dist/types/capabilities.d.ts +14 -0
  249. package/dist/types/capabilities.d.ts.map +1 -1
  250. package/dist/types/capabilities.js +37 -18
  251. package/dist/types/events.d.ts +27 -4
  252. package/dist/types/events.d.ts.map +1 -1
  253. package/dist/types/filters.d.ts +31 -0
  254. package/dist/types/filters.d.ts.map +1 -1
  255. package/dist/types/schemas.d.ts +14 -0
  256. package/dist/types/schemas.d.ts.map +1 -1
  257. package/dist/utils/fetch-remote-file.d.ts.map +1 -1
  258. package/dist/utils/fetch-remote-file.js +32 -3
  259. package/package.json +6 -6
@@ -0,0 +1,62 @@
1
+ import type { BootstrapPolicy, InstanceRole } from './bootstrap.js';
2
+ import type { DatabaseAdapter } from '../db/interfaces/index.js';
3
+ import type { UserProfile } from '../types/index.js';
4
+ /**
5
+ * Thrown when sign-up is refused by policy.
6
+ *
7
+ * Providers translate this into whatever their transport expects — Better Auth
8
+ * raises an `APIError`, a REST handler might return 403. The message is written
9
+ * to be safe to show a user.
10
+ */
11
+ export declare class SignUpBlockedError extends Error {
12
+ readonly code = "SIGN_UP_BLOCKED";
13
+ constructor(message: string);
14
+ }
15
+ export interface SignUpGateOptions {
16
+ db: DatabaseAdapter;
17
+ /** Address attempting to register. Matched case-insensitively. */
18
+ email: string | undefined;
19
+ /** When false the gate is disabled entirely and this is a no-op. */
20
+ inviteOnly: boolean;
21
+ }
22
+ /**
23
+ * Throws `SignUpBlockedError` unless this address may create an account.
24
+ *
25
+ * Two ways through: the instance is provably empty (nobody exists yet to have
26
+ * sent an invitation, so gating the first sign-up would lock the door with the
27
+ * keys inside), or the address holds a pending invitation.
28
+ *
29
+ * Narrowing that first exception is the bootstrap policy's job — see
30
+ * `resolveBootstrapRole` — not this gate's.
31
+ */
32
+ export declare function assertSignUpAllowed({ db, email, inviteOnly }: SignUpGateOptions): Promise<void>;
33
+ export interface BootstrapRoleOptions {
34
+ db: DatabaseAdapter;
35
+ user: {
36
+ id: string;
37
+ email: string;
38
+ emailVerified: boolean;
39
+ };
40
+ /** The request that triggered profile creation, when there is one. */
41
+ request?: Request;
42
+ bootstrap: BootstrapPolicy;
43
+ }
44
+ /**
45
+ * Runs the bootstrap policy for a brand-new user, returning the instance role to
46
+ * grant or `null` for none.
47
+ *
48
+ * Deliberately not wrapped in a transaction. SQLite allows a single writer, and
49
+ * holding that lock across the policy's own round-trips collides with the auth
50
+ * provider's concurrent user/session inserts (SQLITE_BUSY). The atomicity was
51
+ * only partial anyway — see `claimCode`: a claim is single-use, not mutually
52
+ * exclusive.
53
+ */
54
+ export declare function resolveBootstrapRole({ db, user, request, bootstrap }: BootstrapRoleOptions): Promise<InstanceRole | null>;
55
+ /**
56
+ * The one place a CMS user profile is created for a newly-authenticated user.
57
+ *
58
+ * Every `AuthProvider` should route new profiles through here so bootstrap
59
+ * promotion happens identically regardless of who did the authenticating.
60
+ */
61
+ export declare function createUserProfileWithBootstrap(options: BootstrapRoleOptions): Promise<UserProfile>;
62
+ //# sourceMappingURL=sign-up-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-up-policy.d.ts","sourceRoot":"","sources":["../../src/lib/auth/sign-up-policy.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,qBAAqB;gBACtB,OAAO,EAAE,MAAM;CAI3B;AAED,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EACzC,EAAE,EACF,KAAK,EACL,UAAU,EACV,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBnC;AAED,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5D,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,EAC1C,EAAE,EACF,IAAI,EACJ,OAAO,EACP,SAAS,EACT,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAerD;AAuBD;;;;;GAKG;AACH,wBAAsB,8BAA8B,CACnD,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAAC,WAAW,CAAC,CA6BtB"}
@@ -0,0 +1,120 @@
1
+ // Who may sign up, and what they get when they do.
2
+ //
3
+ // These are **CMS policy decisions, not auth-provider concerns**. Whether an
4
+ // instance is invite-only, and who becomes its first administrator, should mean
5
+ // the same thing whether the app authenticates with Better Auth, Keycloak,
6
+ // Supabase, or something hand-rolled — so they live behind the `AuthProvider`
7
+ // port rather than inside any one implementation of it.
8
+ //
9
+ // Before this existed, both rules were enforced inside `@aphexcms/auth`'s
10
+ // Better Auth wiring, which meant a third-party provider silently inherited
11
+ // neither: `inviteOnly: true` would be configured and quietly do nothing. An
12
+ // implementation is free to call these wherever its lifecycle allows, but it is
13
+ // no longer free to *re-derive* them.
14
+ import { cmsLogger } from '../utils/logger.js';
15
+ import { isInstanceEmpty, canDetermineInstanceEmptiness } from './instance-state.js';
16
+ import { isPendingInvitation } from './invitation-status.js';
17
+ /**
18
+ * Thrown when sign-up is refused by policy.
19
+ *
20
+ * Providers translate this into whatever their transport expects — Better Auth
21
+ * raises an `APIError`, a REST handler might return 403. The message is written
22
+ * to be safe to show a user.
23
+ */
24
+ export class SignUpBlockedError extends Error {
25
+ code = 'SIGN_UP_BLOCKED';
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = 'SignUpBlockedError';
29
+ }
30
+ }
31
+ /**
32
+ * Throws `SignUpBlockedError` unless this address may create an account.
33
+ *
34
+ * Two ways through: the instance is provably empty (nobody exists yet to have
35
+ * sent an invitation, so gating the first sign-up would lock the door with the
36
+ * keys inside), or the address holds a pending invitation.
37
+ *
38
+ * Narrowing that first exception is the bootstrap policy's job — see
39
+ * `resolveBootstrapRole` — not this gate's.
40
+ */
41
+ export async function assertSignUpAllowed({ db, email, inviteOnly }) {
42
+ if (!inviteOnly)
43
+ return;
44
+ if (await isInstanceEmpty(db)) {
45
+ cmsLogger.info('[SignUp]', 'Allowing first sign-up on an empty instance');
46
+ return;
47
+ }
48
+ // Invitations are stored lower-cased and matched exactly, so a mixed-case
49
+ // sign-up would otherwise find nothing and be refused.
50
+ const address = email?.toLowerCase().trim();
51
+ const invitations = address ? await db.findInvitationsByEmail(address) : [];
52
+ if (!invitations.some((invitation) => isPendingInvitation(invitation))) {
53
+ cmsLogger.warn('[SignUp]', `Blocked sign-up for ${address} — no pending invitation`);
54
+ throw new SignUpBlockedError('Sign-up is by invitation only. Ask an administrator for an invite.');
55
+ }
56
+ }
57
+ /**
58
+ * Runs the bootstrap policy for a brand-new user, returning the instance role to
59
+ * grant or `null` for none.
60
+ *
61
+ * Deliberately not wrapped in a transaction. SQLite allows a single writer, and
62
+ * holding that lock across the policy's own round-trips collides with the auth
63
+ * provider's concurrent user/session inserts (SQLITE_BUSY). The atomicity was
64
+ * only partial anyway — see `claimCode`: a claim is single-use, not mutually
65
+ * exclusive.
66
+ */
67
+ export async function resolveBootstrapRole({ db, user, request, bootstrap }) {
68
+ // "Provably empty", not "couldn't prove otherwise". An adapter that can't
69
+ // answer this must not read as "no users exist" — that inversion once
70
+ // promoted every single sign-up to super admin.
71
+ const isFirstUser = await isInstanceEmpty(db);
72
+ if (!canDetermineInstanceEmptiness(db)) {
73
+ cmsLogger.warn('[SignUp]', 'Database adapter does not implement hasAnyUserProfiles() — skipping bootstrap. ' +
74
+ 'Provision the first administrator out of band.');
75
+ }
76
+ return bootstrap({ user, isFirstUser, request, db });
77
+ }
78
+ /**
79
+ * Take the one-time bootstrap claim, or report that somebody else has it.
80
+ *
81
+ * An adapter without `tryClaimBootstrap` keeps the old, racy behaviour rather
82
+ * than never promoting anyone — the opposite of the rule `isInstanceEmpty`
83
+ * follows, and on purpose. There, failing closed skips a promotion; here it
84
+ * would leave a fresh install with no administrator and no way to get one.
85
+ */
86
+ async function claimBootstrapPromotion(db) {
87
+ const claim = db.tryClaimBootstrap?.bind(db);
88
+ if (!claim) {
89
+ cmsLogger.warn('[SignUp]', 'Database adapter does not implement tryClaimBootstrap() — bootstrap promotion is not ' +
90
+ 'race-safe. Concurrent first sign-ups could both be promoted.');
91
+ return true;
92
+ }
93
+ return claim();
94
+ }
95
+ /**
96
+ * The one place a CMS user profile is created for a newly-authenticated user.
97
+ *
98
+ * Every `AuthProvider` should route new profiles through here so bootstrap
99
+ * promotion happens identically regardless of who did the authenticating.
100
+ */
101
+ export async function createUserProfileWithBootstrap(options) {
102
+ const proposed = await resolveBootstrapRole(options);
103
+ // The policy decided *whether* this user qualifies; the claim decides whether
104
+ // they got there first. Both are needed: the policy's own `isFirstUser` check
105
+ // reads a snapshot taken before the profile insert, so two sign-ups racing on
106
+ // a fresh instance can both pass it and both be promoted. Every promotion in
107
+ // the instance's lifetime funnels through this one call, so claiming here
108
+ // covers every policy — including a custom one — without each having to know
109
+ // about the race.
110
+ const granted = proposed && (await claimBootstrapPromotion(options.db)) ? proposed : null;
111
+ if (proposed && !granted) {
112
+ cmsLogger.warn('[SignUp]', `Bootstrap promotion for ${options.user.id} lost the claim — another sign-up got there first. ` +
113
+ 'Creating an ordinary profile instead.');
114
+ }
115
+ cmsLogger.info('[SignUp]', `Creating profile for ${options.user.id}${granted ? ` with role ${granted.toUpperCase()}` : ''}`);
116
+ return options.db.createUserProfile({
117
+ userId: options.user.id,
118
+ role: granted ?? 'editor'
119
+ });
120
+ }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { Alert, AlertDescription, AlertTitle } from '@aphexcms/ui/shadcn/alert';
7
7
  import { Button } from '@aphexcms/ui/shadcn/button';
8
+ import { Input } from '@aphexcms/ui/shadcn/input';
8
9
  import { useSidebar } from '@aphexcms/ui/shadcn/sidebar';
9
10
  import * as Tabs from '@aphexcms/ui/shadcn/tabs';
10
11
  import * as Popover from '@aphexcms/ui/shadcn/popover';
@@ -18,6 +19,7 @@
18
19
  import type { AdminArea } from '../admin/types';
19
20
  import { setAdminNav } from '../admin/nav.svelte';
20
21
  import type { Component } from 'svelte';
22
+ import { tick } from 'svelte';
21
23
  import { setFieldComponents } from '../admin/field-components.svelte';
22
24
  import { setBlockPreviews, type BlockPreviewProps } from '../admin/block-previews.svelte';
23
25
  import AdminSlot from './admin/AdminSlot.svelte';
@@ -39,7 +41,9 @@
39
41
  ArrowUp10,
40
42
  ArrowDownUp,
41
43
  ChevronLeft,
42
- ChevronRight
44
+ ChevronRight,
45
+ Search,
46
+ X
43
47
  } from '@lucide/svelte';
44
48
  import type { Organization } from '../types/organization';
45
49
  import { getOrderingsForSchema } from '../utils/default-orderings';
@@ -254,6 +258,20 @@
254
258
  let docTotalDocs = $state(0);
255
259
  let docPageSize = $state(20);
256
260
  const PAGE_SIZE_OPTIONS = [20, 50, 100] as const;
261
+ let docSearchQuery = $state('');
262
+ let docSearchOpen = $state(false);
263
+ let docSearchInputEl = $state<HTMLInputElement | null>(null);
264
+ let docSearchTimeout: ReturnType<typeof setTimeout>;
265
+
266
+ function toggleDocSearch() {
267
+ if (docSearchOpen) {
268
+ docSearchOpen = false;
269
+ if (docSearchQuery) handleDocSearchInput('');
270
+ } else {
271
+ docSearchOpen = true;
272
+ tick().then(() => docSearchInputEl?.focus());
273
+ }
274
+ }
257
275
 
258
276
  // Organizations lookup map for displaying org names
259
277
  let organizationsMap = $state<Map<string, Organization>>(new Map());
@@ -617,6 +635,7 @@
617
635
  editorStack = [];
618
636
  if (selectedDocumentType !== docType) {
619
637
  docCurrentPage = 1;
638
+ docSearchQuery = '';
620
639
  selectedDocumentType = docType;
621
640
  fetchDocuments(docType);
622
641
  }
@@ -703,6 +722,7 @@
703
722
  // Only fetch if docType changed (org changes are handled by separate effect)
704
723
  if (selectedDocumentType !== docType) {
705
724
  docCurrentPage = 1;
725
+ docSearchQuery = '';
706
726
  selectedDocumentType = docType;
707
727
  fetchDocuments(docType);
708
728
  } else {
@@ -973,6 +993,15 @@
973
993
  }
974
994
  }
975
995
 
996
+ function handleDocSearchInput(value: string) {
997
+ docSearchQuery = value;
998
+ clearTimeout(docSearchTimeout);
999
+ docSearchTimeout = setTimeout(() => {
1000
+ docCurrentPage = 1;
1001
+ if (selectedDocumentType) fetchDocuments(selectedDocumentType);
1002
+ }, 300);
1003
+ }
1004
+
976
1005
  async function fetchDocuments(docType: string) {
977
1006
  // No type = nothing to list (e.g. mid-navigation from a plugin tab before a
978
1007
  // type is selected). Bail quietly rather than hitting the API and toasting
@@ -988,7 +1017,8 @@
988
1017
  page: docCurrentPage,
989
1018
  pageSize: docPageSize,
990
1019
  includeChildOrganizations: userPreferences?.includeChildOrganizations ?? false,
991
- sort: sortString
1020
+ sort: sortString,
1021
+ search: docSearchQuery.trim() || undefined
992
1022
  });
993
1023
 
994
1024
  if (result.success && result.data) {
@@ -1330,6 +1360,15 @@
1330
1360
  </div>
1331
1361
  </div>
1332
1362
  <div class="flex items-center gap-1">
1363
+ <Button
1364
+ size="sm"
1365
+ variant="ghost"
1366
+ onclick={toggleDocSearch}
1367
+ class="h-8 w-8 p-0 {docSearchOpen || docSearchQuery ? 'bg-muted' : ''}"
1368
+ title="Search documents"
1369
+ >
1370
+ <Search class="h-4 w-4" />
1371
+ </Button>
1333
1372
  {#if perms.can('document.create') && !schemas.find((s) => s.name === selectedDocumentType)?.singleton}
1334
1373
  <Button
1335
1374
  size="sm"
@@ -1459,6 +1498,37 @@
1459
1498
  </Popover.Root>
1460
1499
  </div>
1461
1500
  </div>
1501
+
1502
+ {#if docSearchOpen}
1503
+ <div class="relative mt-2">
1504
+ <Search
1505
+ size={14}
1506
+ class="text-muted-foreground absolute top-1/2 left-2.5 -translate-y-1/2"
1507
+ />
1508
+ <Input
1509
+ bind:ref={docSearchInputEl}
1510
+ placeholder="Search {pluralize(
1511
+ currentDocType?.title || selectedDocumentType
1512
+ ).toLowerCase()}"
1513
+ class="h-8 pl-8 text-sm {docSearchQuery ? 'pr-8' : ''}"
1514
+ value={docSearchQuery}
1515
+ oninput={(e) =>
1516
+ handleDocSearchInput((e.target as HTMLInputElement).value)}
1517
+ onkeydown={(e) => {
1518
+ if (e.key === 'Escape') toggleDocSearch();
1519
+ }}
1520
+ />
1521
+ {#if docSearchQuery}
1522
+ <button
1523
+ onclick={() => handleDocSearchInput('')}
1524
+ class="text-muted-foreground hover:text-foreground absolute top-1/2 right-2.5 -translate-y-1/2"
1525
+ title="Clear search"
1526
+ >
1527
+ <X class="h-3.5 w-3.5" />
1528
+ </button>
1529
+ {/if}
1530
+ </div>
1531
+ {/if}
1462
1532
  </div>
1463
1533
 
1464
1534
  <div class="flex-1 overflow-y-auto">
@@ -1547,10 +1617,17 @@
1547
1617
  <FileText class="text-muted-foreground h-8 w-8" />
1548
1618
  {/if}
1549
1619
  </div>
1550
- <h3 class="mb-2 font-medium">No documents found</h3>
1551
- <p class="text-muted-foreground text-sm">
1552
- Create your first {selectedDocumentType} document using the + button above
1553
- </p>
1620
+ {#if docSearchQuery}
1621
+ <h3 class="mb-2 font-medium">No matching documents</h3>
1622
+ <p class="text-muted-foreground text-sm">
1623
+ No results for "{docSearchQuery}". Try a different search.
1624
+ </p>
1625
+ {:else}
1626
+ <h3 class="mb-2 font-medium">No documents found</h3>
1627
+ <p class="text-muted-foreground text-sm">
1628
+ Create your first {selectedDocumentType} document using the + button above
1629
+ </p>
1630
+ {/if}
1554
1631
  </div>
1555
1632
  {/if}
1556
1633
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAE1F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AA4BnE,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACjC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC;CAC7D;AA+mDF,QAAA,MAAM,QAAQ,0BAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGxC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAE1F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AA8BnE,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACjC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC;CAC7D;AAsqDF,QAAA,MAAM,QAAQ,0BAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}