@nextsparkjs/core 0.1.0-beta.196 → 0.1.0-beta.197

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 (136) hide show
  1. package/README.md +2 -0
  2. package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
  3. package/dist/components/auth/forms/SignupForm.js +55 -45
  4. package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
  5. package/dist/components/dashboard/block-editor/config-panel.js +3 -2
  6. package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
  7. package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
  8. package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
  9. package/dist/components/users/tables/UsersTable.js +3 -4
  10. package/dist/hooks/useAuthReadiness.d.ts +1 -0
  11. package/dist/hooks/useAuthReadiness.d.ts.map +1 -1
  12. package/dist/hooks/useAuthReadiness.js +3 -1
  13. package/dist/lib/actions/entity.actions.js +2 -2
  14. package/dist/lib/actions/team.actions.js +2 -2
  15. package/dist/lib/actions/user.actions.js +4 -4
  16. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  17. package/dist/lib/api/auth/dual-auth.js +3 -2
  18. package/dist/lib/api/auth.d.ts.map +1 -1
  19. package/dist/lib/api/auth.js +7 -0
  20. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  21. package/dist/lib/api/entity/generic-handler.js +66 -12
  22. package/dist/lib/api/entity-handler.js +2 -2
  23. package/dist/lib/api/helpers.d.ts.map +1 -1
  24. package/dist/lib/api/helpers.js +4 -2
  25. package/dist/lib/api/post-categories.d.ts +29 -0
  26. package/dist/lib/api/post-categories.d.ts.map +1 -0
  27. package/dist/lib/api/post-categories.js +33 -0
  28. package/dist/lib/auth/auth-methods.d.ts +5 -2
  29. package/dist/lib/auth/auth-methods.d.ts.map +1 -1
  30. package/dist/lib/auth/auth-methods.js +4 -2
  31. package/dist/lib/auth/authorization-session.d.ts +14 -0
  32. package/dist/lib/auth/authorization-session.d.ts.map +1 -0
  33. package/dist/lib/auth/authorization-session.js +10 -0
  34. package/dist/lib/auth/request-session.d.ts +5 -0
  35. package/dist/lib/auth/request-session.d.ts.map +1 -1
  36. package/dist/lib/auth/request-session.js +3 -0
  37. package/dist/lib/auth/runtime-readiness.d.ts +7 -4
  38. package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
  39. package/dist/lib/auth/runtime-readiness.js +7 -6
  40. package/dist/lib/auth/sample-accounts.d.ts +2 -1
  41. package/dist/lib/auth/sample-accounts.d.ts.map +1 -1
  42. package/dist/lib/auth/sample-accounts.js +4 -1
  43. package/dist/lib/auth/suspension.d.ts +22 -0
  44. package/dist/lib/auth/suspension.d.ts.map +1 -0
  45. package/dist/lib/auth/suspension.js +43 -0
  46. package/dist/lib/auth.d.ts.map +1 -1
  47. package/dist/lib/auth.js +15 -15
  48. package/dist/lib/config/app.config.d.ts.map +1 -1
  49. package/dist/lib/config/app.config.js +6 -7
  50. package/dist/lib/config/types.d.ts +8 -6
  51. package/dist/lib/config/types.d.ts.map +1 -1
  52. package/dist/lib/db-pool.d.ts +31 -0
  53. package/dist/lib/db-pool.d.ts.map +1 -0
  54. package/dist/lib/db-pool.js +75 -0
  55. package/dist/lib/db.d.ts +1 -1
  56. package/dist/lib/db.d.ts.map +1 -1
  57. package/dist/lib/db.js +11 -14
  58. package/dist/lib/entities/api-generator.js +5 -5
  59. package/dist/lib/oauth/token-refresh.d.ts.map +1 -1
  60. package/dist/lib/oauth/token-refresh.js +3 -6
  61. package/dist/lib/permissions/check.d.ts +9 -0
  62. package/dist/lib/permissions/check.d.ts.map +1 -1
  63. package/dist/lib/permissions/check.js +11 -1
  64. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  65. package/dist/lib/services/generic-entity.service.js +39 -7
  66. package/dist/lib/services/transactional-meta.service.d.ts.map +1 -1
  67. package/dist/lib/services/transactional-meta.service.js +7 -8
  68. package/dist/lib/validation.d.ts +5 -0
  69. package/dist/lib/validation.d.ts.map +1 -1
  70. package/dist/lib/validation.js +2 -0
  71. package/dist/migrations/030_taxonomies_team_writes.sql +29 -0
  72. package/dist/migrations/031_users_role_suspended.sql +41 -0
  73. package/dist/routes/(auth)/signup/page.cc.d.ts +3 -1
  74. package/dist/routes/(auth)/signup/page.cc.d.ts.map +1 -1
  75. package/dist/routes/(auth)/signup/page.cc.js +2 -2
  76. package/dist/routes/(auth)/signup/page.d.ts +3 -1
  77. package/dist/routes/(auth)/signup/page.d.ts.map +1 -1
  78. package/dist/routes/(auth)/signup/page.js +4 -3
  79. package/dist/routes/_internal/area-access.js +2 -2
  80. package/dist/routes/api/devtools/config/entities/route.js +2 -2
  81. package/dist/routes/api/devtools/config/theme/route.js +2 -2
  82. package/dist/routes/api/devtools/tests/[...path]/route.js +2 -2
  83. package/dist/routes/api/devtools/tests/route.js +2 -2
  84. package/dist/routes/api/superadmin/subscriptions/route.js +2 -2
  85. package/dist/routes/api/superadmin/teams/[teamId]/route.js +2 -2
  86. package/dist/routes/api/superadmin/teams/route.js +2 -2
  87. package/dist/routes/api/superadmin/users/[userId]/route.d.ts +5 -3
  88. package/dist/routes/api/superadmin/users/[userId]/route.d.ts.map +1 -1
  89. package/dist/routes/api/superadmin/users/[userId]/route.js +46 -23
  90. package/dist/routes/api/superadmin/users/route.js +2 -2
  91. package/dist/routes/api/user/delete-account/route.d.ts.map +1 -1
  92. package/dist/routes/api/user/delete-account/route.js +2 -3
  93. package/dist/routes/api/user/plan-flags/route.d.ts.map +1 -1
  94. package/dist/routes/api/user/plan-flags/route.js +5 -5
  95. package/dist/routes/api/user/profile/route.js +3 -3
  96. package/dist/routes/api/v1/auth/signup-with-invite/route.d.ts.map +1 -1
  97. package/dist/routes/api/v1/auth/signup-with-invite/route.js +80 -44
  98. package/dist/routes/api/v1/post-categories/[id]/route.d.ts.map +1 -1
  99. package/dist/routes/api/v1/post-categories/[id]/route.js +39 -34
  100. package/dist/routes/api/v1/post-categories/route.d.ts.map +1 -1
  101. package/dist/routes/api/v1/post-categories/route.js +18 -14
  102. package/dist/routes/api/v1/team-invitations/route.d.ts.map +1 -1
  103. package/dist/routes/api/v1/team-invitations/route.js +12 -5
  104. package/dist/routes/superadmin/users/page.js +2 -2
  105. package/dist/templates/next.config.mjs +11 -31
  106. package/dist/templates/projects/blog/package.json +1 -1
  107. package/dist/templates/projects/crm/package.json +1 -1
  108. package/dist/templates/projects/productivity/package.json +1 -1
  109. package/dist/templates/projects/starter/config/app.config.ts +8 -0
  110. package/dist/templates/proxy.ts +6 -3
  111. package/migrations/030_taxonomies_team_writes.sql +29 -0
  112. package/migrations/031_users_role_suspended.sql +41 -0
  113. package/package.json +3 -3
  114. package/scripts/build/registry/__tests__/build-output.test.mjs +1 -1
  115. package/scripts/build/registry/__tests__/project-mode-root-first.test.mjs +6 -6
  116. package/scripts/build/registry/__tests__/write-places.test.mjs +1 -1
  117. package/scripts/build/registry/contracts/__tests__/contracts-wire.test.mjs +1 -1
  118. package/scripts/build/registry/contracts/__tests__/contracts.test.mjs +1 -1
  119. package/scripts/build/registry/host/README.md +2 -2
  120. package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +14 -13
  121. package/scripts/build/registry/host/__tests__/host-mode.test.mjs +1 -1
  122. package/scripts/build/registry/host/__tests__/host-project-composition.test.mjs +1 -1
  123. package/scripts/build/registry/host/__tests__/host-project.test.mjs +2 -2
  124. package/scripts/build/registry/host/__tests__/host-proxy-areas.test.mjs +29 -2
  125. package/scripts/build/registry/host/__tests__/host-robustness.test.mjs +1 -1
  126. package/scripts/build/registry/host/facade-emitter.mjs +1 -1
  127. package/scripts/build/registry/host/next-route-exports.json +2 -2
  128. package/scripts/build/registry/host/proxy-areas.mjs +29 -2
  129. package/scripts/db/verify-theme-migrations.mjs +1 -1
  130. package/templates/next.config.mjs +11 -31
  131. package/templates/projects/blog/package.json +1 -1
  132. package/templates/projects/crm/package.json +1 -1
  133. package/templates/projects/productivity/package.json +1 -1
  134. package/templates/projects/starter/config/app.config.ts +8 -0
  135. package/templates/proxy.ts +6 -3
  136. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +5 -4
@@ -0,0 +1,31 @@
1
+ import { Pool, type PoolConfig } from 'pg';
2
+ /**
3
+ * The one place core's runtime pg pools get their resilience options, so the app pool, the service pool and the
4
+ * Better Auth pool cannot drift. Idle connections on Neon, Supabase poolers and behind NAT/load balancers are dropped
5
+ * silently; without these options the next query waits for the OS TCP timeout (minutes) on a dead socket, and the
6
+ * socket error on an idle client surfaces as an `uncaughtException`.
7
+ */
8
+ /** Far below the idle cutoffs seen in practice (Azure LB 4 min, AWS NLB 350 s, NAT tables 60 s and up): an idle client is closed by us long before a provider drops it, at the cost of an occasional reconnect. */
9
+ export declare const POOL_IDLE_TIMEOUT_MS = 10000;
10
+ export declare const POOL_CONNECTION_TIMEOUT_MS = 10000;
11
+ /** First TCP keep-alive probe 10 s after the socket goes quiet; how soon a dead peer is detected after that depends on the OS. */
12
+ export declare const POOL_KEEP_ALIVE_DELAY_MS = 10000;
13
+ /** Default for DB_QUERY_TIMEOUT_MS: long enough for a heavy report, short of the minutes a dead socket takes to fail. */
14
+ export declare const DEFAULT_QUERY_TIMEOUT_MS = 60000;
15
+ /**
16
+ * A timeout in milliseconds from the environment. Unset or empty gives `fallback`; `0` gives `undefined` (no limit);
17
+ * anything that is not a non-negative integer warns once and gives `fallback`.
18
+ */
19
+ export declare function parseTimeoutMs(name: string, raw: string | undefined, fallback: number | undefined): number | undefined;
20
+ /**
21
+ * DB_QUERY_TIMEOUT_MS (default 60000): the client gives up on a query that has no answer after this long. It works on
22
+ * every provider, pooled or not, and is what fails a query on a dead socket. 0 disables it.
23
+ * DB_STATEMENT_TIMEOUT_MS (default unset): also asks the server to cancel a statement after this long. Opt-in because
24
+ * the limit travels as a connection startup parameter, which some transaction poolers (PgBouncer) reject.
25
+ */
26
+ export declare function poolTimeouts(env?: NodeJS.ProcessEnv): Pick<PoolConfig, 'query_timeout' | 'statement_timeout'>;
27
+ /** What is safe to log about a pool error: its code or class, never the message (it can carry hosts, users or databases). */
28
+ export declare function describePoolError(err: unknown): string;
29
+ /** A Pool with core's keep-alive, timeouts and error handling on top of `config`; `label` says which pool in the log. */
30
+ export declare function createPool(label: string, config: PoolConfig): Pool;
31
+ //# sourceMappingURL=db-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-pool.d.ts","sourceRoot":"","sources":["../../src/lib/db-pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,KAAK,UAAU,EAAE,MAAM,IAAI,CAAC;AAE5D;;;;;GAKG;AAEH,mNAAmN;AACnN,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,eAAO,MAAM,0BAA0B,QAAS,CAAC;AACjD,kIAAkI;AAClI,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,yHAAyH;AACzH,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAQtH;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,GAAG,mBAAmB,CAAC,CAK1H;AAED,6HAA6H;AAC7H,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAGtD;AA8BD,yHAAyH;AACzH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAmBlE"}
@@ -0,0 +1,75 @@
1
+ import { Pool } from "pg";
2
+ const POOL_IDLE_TIMEOUT_MS = 1e4;
3
+ const POOL_CONNECTION_TIMEOUT_MS = 1e4;
4
+ const POOL_KEEP_ALIVE_DELAY_MS = 1e4;
5
+ const DEFAULT_QUERY_TIMEOUT_MS = 6e4;
6
+ function parseTimeoutMs(name, raw, fallback) {
7
+ if (raw === void 0 || raw.trim() === "") return fallback;
8
+ const value = Number(raw);
9
+ if (!Number.isInteger(value) || value < 0) {
10
+ console.warn(`[DB] Ignoring ${name}="${raw}": expected a non-negative integer of milliseconds (0 disables).`);
11
+ return fallback;
12
+ }
13
+ return value === 0 ? void 0 : value;
14
+ }
15
+ function poolTimeouts(env = process.env) {
16
+ return {
17
+ query_timeout: parseTimeoutMs("DB_QUERY_TIMEOUT_MS", env.DB_QUERY_TIMEOUT_MS, DEFAULT_QUERY_TIMEOUT_MS),
18
+ statement_timeout: parseTimeoutMs("DB_STATEMENT_TIMEOUT_MS", env.DB_STATEMENT_TIMEOUT_MS, void 0)
19
+ };
20
+ }
21
+ function describePoolError(err) {
22
+ const e = err;
23
+ return String((e == null ? void 0 : e.code) ?? (e == null ? void 0 : e.name) ?? "unknown");
24
+ }
25
+ const QUERY_TIMEOUT_MESSAGE = "Query read timeout";
26
+ function dropOnQueryTimeout(client) {
27
+ const query = client.query.bind(client);
28
+ const drop = (err) => {
29
+ var _a, _b;
30
+ if ((err == null ? void 0 : err.message) === QUERY_TIMEOUT_MESSAGE) {
31
+ client._queryable = false;
32
+ (_b = (_a = client.connection) == null ? void 0 : _a.stream) == null ? void 0 : _b.destroy();
33
+ }
34
+ };
35
+ client.query = (...args) => {
36
+ const last = args[args.length - 1];
37
+ if (typeof last === "function") {
38
+ args[args.length - 1] = (err, ...rest) => {
39
+ drop(err);
40
+ last(err, ...rest);
41
+ };
42
+ }
43
+ const result = query(...args);
44
+ if (typeof (result == null ? void 0 : result.catch) === "function") result.catch(drop);
45
+ return result;
46
+ };
47
+ }
48
+ function createPool(label, config) {
49
+ const pool = new Pool({
50
+ max: 20,
51
+ idleTimeoutMillis: POOL_IDLE_TIMEOUT_MS,
52
+ connectionTimeoutMillis: POOL_CONNECTION_TIMEOUT_MS,
53
+ keepAlive: true,
54
+ keepAliveInitialDelayMillis: POOL_KEEP_ALIVE_DELAY_MS,
55
+ ...poolTimeouts(),
56
+ ...config
57
+ });
58
+ pool.on("connect", (client) => {
59
+ dropOnQueryTimeout(client);
60
+ client.on("error", (err) => console.warn(`[DB] ${label} pool: client error (${describePoolError(err)}); connection dropped.`));
61
+ });
62
+ pool.on("error", () => {
63
+ });
64
+ return pool;
65
+ }
66
+ export {
67
+ DEFAULT_QUERY_TIMEOUT_MS,
68
+ POOL_CONNECTION_TIMEOUT_MS,
69
+ POOL_IDLE_TIMEOUT_MS,
70
+ POOL_KEEP_ALIVE_DELAY_MS,
71
+ createPool,
72
+ describePoolError,
73
+ parseTimeoutMs,
74
+ poolTimeouts
75
+ };
package/dist/lib/db.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Pool } from 'pg';
1
+ import type { Pool } from 'pg';
2
2
  /**
3
3
  * Strip SSL-related parameters from DATABASE_URL to prevent pg-connection-string
4
4
  * from overriding our explicit ssl config.
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"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"}
package/dist/lib/db.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Pool } from "pg";
1
+ import { createPool } from "./db-pool.js";
2
2
  import { isValidUUID } from "./utils/uuid.js";
3
3
  let activeConnections = 0;
4
4
  let isShuttingDown = false;
@@ -57,20 +57,14 @@ function parseSSLConfig(databaseUrl2) {
57
57
  }
58
58
  const databaseUrl = process.env.DATABASE_URL || "";
59
59
  const sslConfig = parseSSLConfig(databaseUrl);
60
- const pool = new Pool({
60
+ const pool = createPool("app", {
61
61
  connectionString: stripSSLParams(databaseUrl),
62
- ssl: sslConfig,
63
- max: 20,
64
- idleTimeoutMillis: 3e4,
65
- connectionTimeoutMillis: 1e4
62
+ ssl: sslConfig
66
63
  });
67
64
  const serviceDatabaseUrl = process.env.DATABASE_SERVICE_URL || databaseUrl;
68
- const servicePool = serviceDatabaseUrl === databaseUrl ? pool : new Pool({
65
+ const servicePool = serviceDatabaseUrl === databaseUrl ? pool : createPool("service", {
69
66
  connectionString: stripSSLParams(serviceDatabaseUrl),
70
- ssl: parseSSLConfig(serviceDatabaseUrl),
71
- max: 20,
72
- idleTimeoutMillis: 3e4,
73
- connectionTimeoutMillis: 1e4
67
+ ssl: parseSSLConfig(serviceDatabaseUrl)
74
68
  });
75
69
  function validateUserId(userId) {
76
70
  const isProduction = process.env.NODE_ENV === "production";
@@ -115,7 +109,8 @@ async function queryWithRLS(query2, params = [], userId, options) {
115
109
  await client.query("COMMIT");
116
110
  return result.rows;
117
111
  } catch (error) {
118
- await client.query("ROLLBACK");
112
+ await client.query("ROLLBACK").catch(() => {
113
+ });
119
114
  throw error;
120
115
  } finally {
121
116
  releaseClient(client);
@@ -141,7 +136,8 @@ async function mutateWithRLS(query2, params = [], userId, options) {
141
136
  rowCount: result.rowCount || 0
142
137
  };
143
138
  } catch (error) {
144
- await client.query("ROLLBACK");
139
+ await client.query("ROLLBACK").catch(() => {
140
+ });
145
141
  throw error;
146
142
  } finally {
147
143
  releaseClient(client);
@@ -169,7 +165,8 @@ async function getTransactionClient(userId, options) {
169
165
  releaseClient(client);
170
166
  },
171
167
  rollback: async () => {
172
- await client.query("ROLLBACK");
168
+ await client.query("ROLLBACK").catch(() => {
169
+ });
173
170
  releaseClient(client);
174
171
  }
175
172
  };
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
2
2
  import * as z from "zod";
3
3
  import { getEntityConfig } from "./registry.js";
4
4
  import { executeEntityHooks } from "./hooks.js";
5
- import { getTypedSession } from "../auth.js";
5
+ import { getAuthorizationSession } from "../auth/authorization-session.js";
6
6
  function generateEntityAPI(entityName, options = {}) {
7
7
  const entityConfig = getEntityConfig(entityName);
8
8
  if (!entityConfig) {
@@ -27,7 +27,7 @@ function createGETHandler(entityConfig, options) {
27
27
  try {
28
28
  let user = null;
29
29
  if (options.enableAuth) {
30
- const session = await getTypedSession(request.headers);
30
+ const session = await getAuthorizationSession(request.headers);
31
31
  if (!session) {
32
32
  return NextResponse.json({ error: "Authentication required" }, { status: 401 });
33
33
  }
@@ -78,7 +78,7 @@ function createPOSTHandler(entityConfig, options) {
78
78
  try {
79
79
  let user = null;
80
80
  if (options.enableAuth) {
81
- const session = await getTypedSession(request.headers);
81
+ const session = await getAuthorizationSession(request.headers);
82
82
  if (!session) {
83
83
  return NextResponse.json({ error: "Authentication required" }, { status: 401 });
84
84
  }
@@ -126,7 +126,7 @@ function createPATCHHandler(entityConfig, options) {
126
126
  try {
127
127
  let user = null;
128
128
  if (options.enableAuth) {
129
- const session = await getTypedSession(request.headers);
129
+ const session = await getAuthorizationSession(request.headers);
130
130
  if (!session) {
131
131
  return NextResponse.json({ error: "Authentication required" }, { status: 401 });
132
132
  }
@@ -205,7 +205,7 @@ function createDELETEHandler(entityConfig, options) {
205
205
  try {
206
206
  let user = null;
207
207
  if (options.enableAuth) {
208
- const session = await getTypedSession(request.headers);
208
+ const session = await getAuthorizationSession(request.headers);
209
209
  if (!session) {
210
210
  return NextResponse.json({ error: "Authentication required" }, { status: 401 });
211
211
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token-refresh.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/token-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EACL,aAAa,EAEb,kBAAkB,EAKnB,MAAM,SAAS,CAAC;AAuCjB;;GAEG;AACH,qBAAa,mBAAmB;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAahE;;;;;;;;OAQG;WACU,YAAY,CACvB,QAAQ,EAAE,aAAa,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,kBAAkB,CAAC;IAoC9B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IA2BjC;;;;;OAKG;WACU,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkHtE;;;;;OAKG;WACU,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA+B/D;;;;OAIG;WACU,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAqCvD;;OAEG;mBACkB,iBAAiB;IAetC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAa1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAa9B;;;;;OAKG;WACU,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC/D;;OAEG;mBACkB,uBAAuB;CAuB7C"}
1
+ {"version":3,"file":"token-refresh.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/token-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EACL,aAAa,EAEb,kBAAkB,EAKnB,MAAM,SAAS,CAAC;AAoCjB;;GAEG;AACH,qBAAa,mBAAmB;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAahE;;;;;;;;OAQG;WACU,YAAY,CACvB,QAAQ,EAAE,aAAa,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,kBAAkB,CAAC;IAoC9B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IA2BjC;;;;;OAKG;WACU,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkHtE;;;;;OAKG;WACU,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA+B/D;;;;OAIG;WACU,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAqCvD;;OAEG;mBACkB,iBAAiB;IAetC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAa1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAa9B;;;;;OAKG;WACU,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC/D;;OAEG;mBACkB,uBAAuB;CAuB7C"}
@@ -1,4 +1,4 @@
1
- import { Pool } from "pg";
1
+ import { createPool } from "../db-pool.js";
2
2
  import { parseSSLConfig } from "../db.js";
3
3
  import { TokenEncryption } from "./encryption.js";
4
4
  import {
@@ -20,12 +20,9 @@ const REFRESH_CONFIG = {
20
20
  };
21
21
  const databaseUrl = process.env.DATABASE_URL;
22
22
  const connectionString = databaseUrl.includes("?") ? `${databaseUrl}&pgbouncer=true` : `${databaseUrl}?pgbouncer=true`;
23
- const pool = new Pool({
23
+ const pool = createPool("oauth-token-refresh", {
24
24
  connectionString,
25
- ssl: parseSSLConfig(databaseUrl),
26
- connectionTimeoutMillis: 1e4,
27
- idleTimeoutMillis: 3e4,
28
- max: 20
25
+ ssl: parseSSLConfig(databaseUrl)
29
26
  });
30
27
  class TokenRefreshService {
31
28
  /**
@@ -79,4 +79,13 @@ export declare function getUserPermissions(userId: string, teamId: string): Prom
79
79
  * ```
80
80
  */
81
81
  export declare function hasPermissionSync(teamRole: TeamRole, permission: Permission): boolean;
82
+ /**
83
+ * The extra permission a write needs when it moves a record into or out of `published`: `<slug>.publish`, if the
84
+ * entity declares that action, else null. `next` is the status the write sets (undefined: not changed) and
85
+ * `current` the stored one (undefined on create). Every writer asks this: the REST handler (and the page builder
86
+ * through it), GenericEntityService and the server actions through it.
87
+ */
88
+ export declare function publishPermissionFor(entitySlug: string, next: unknown, current?: unknown): Permission | null;
89
+ /** `<slug>.publish` when the entity declares that action, else null. */
90
+ export declare function declaredPublishPermission(entitySlug: string): Permission | null;
82
91
  //# sourceMappingURL=check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/lib/permissions/check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAuBnD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,UAAU,EAAE,GACxB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,UAAU,EAAE,GACxB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,UAAU,GACrB,OAAO,CAET"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/lib/permissions/check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAuBnD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,UAAU,EAAE,GACxB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,UAAU,EAAE,GACxB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,UAAU,GACrB,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAG5G;AAED,wEAAwE;AACxE,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAG/E"}
@@ -52,10 +52,20 @@ async function getUserPermissions(userId, teamId) {
52
52
  function hasPermissionSync(teamRole, permission) {
53
53
  return permissionRegistry.hasPermission(teamRole, permission);
54
54
  }
55
+ function publishPermissionFor(entitySlug, next, current) {
56
+ if (next === void 0 || next === "published" === (current === "published")) return null;
57
+ return declaredPublishPermission(entitySlug);
58
+ }
59
+ function declaredPublishPermission(entitySlug) {
60
+ const permission = `${entitySlug}.publish`;
61
+ return permissionRegistry.isValidPermission(permission) ? permission : null;
62
+ }
55
63
  export {
56
64
  checkAnyPermission,
57
65
  checkPermission,
58
66
  checkPermissions,
67
+ declaredPublishPermission,
59
68
  getUserPermissions,
60
- hasPermissionSync
69
+ hasPermissionSync,
70
+ publishPermissionFor
61
71
  };
@@ -1 +1 @@
1
- {"version":3,"file":"generic-entity.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/generic-entity.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generic-entity.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/generic-entity.service.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { queryWithRLS, queryOneWithRLS, mutateWithRLS } from "../db.js";
1
+ import { queryWithRLS, queryOneWithRLS, mutateWithRLS, getTransactionClient } from "../db.js";
2
2
  import { entityRegistry } from "../entities/registry.js";
3
3
  import { expirePublicEntity } from "../cache/public-entity-cache.js";
4
4
  import {
@@ -9,6 +9,7 @@ import {
9
9
  beforeEntityDelete,
10
10
  afterEntityDelete
11
11
  } from "../entities/entity-hooks.js";
12
+ import { checkPermission, declaredPublishPermission, publishPermissionFor } from "../permissions/check.js";
12
13
  const SYSTEM_FIELDS = ["id", "userId", "teamId", "createdAt", "updatedAt"];
13
14
  const DEFAULT_LIMIT = 20;
14
15
  const DEFAULT_ORDER_BY = "createdAt";
@@ -296,6 +297,7 @@ class GenericEntityService {
296
297
  * @returns Created entity
297
298
  */
298
299
  static async create(entitySlug, userId, teamId, data) {
300
+ var _a;
299
301
  if (!(userId == null ? void 0 : userId.trim())) {
300
302
  throw new Error("User ID is required");
301
303
  }
@@ -311,6 +313,13 @@ class GenericEntityService {
311
313
  if (!validation.valid) {
312
314
  throw new Error(`Validation failed: ${validation.errors.join(", ")}`);
313
315
  }
316
+ const statusField = entityConfig.fields.find((f) => f.name === "status");
317
+ const createStatus = finalData.status ?? (statusField == null ? void 0 : statusField.defaultValue);
318
+ const publish = publishPermissionFor(entitySlug, createStatus);
319
+ if (publish && !await checkPermission(userId, teamId, publish)) {
320
+ throw new Error("Permission denied");
321
+ }
322
+ const storedPublish = createStatus === void 0 && statusField ? declaredPublishPermission(entitySlug) : null;
314
323
  const tableName = getTableName(entityConfig);
315
324
  const fieldNames = getFieldNames(entityConfig);
316
325
  const selectClause = buildSelectClause(fieldNames);
@@ -327,13 +336,27 @@ class GenericEntityService {
327
336
  }
328
337
  }
329
338
  const quotedFields = fields.map((f) => quoteField(f)).join(", ");
330
- const result = await mutateWithRLS(
331
- `INSERT INTO ${tableName} (${quotedFields})
339
+ const insertQuery = `INSERT INTO ${tableName} (${quotedFields})
332
340
  VALUES (${placeholders.join(", ")})
333
- RETURNING ${selectClause}`,
334
- values,
335
- userId
336
- );
341
+ RETURNING ${selectClause}`;
342
+ let result;
343
+ if (storedPublish) {
344
+ const tx = await getTransactionClient(userId);
345
+ let denied = false;
346
+ try {
347
+ result = await tx.mutate(insertQuery, values);
348
+ denied = ((_a = result.rows[0]) == null ? void 0 : _a.status) === "published" && !await checkPermission(userId, teamId, storedPublish);
349
+ if (denied) await tx.rollback();
350
+ else await tx.commit();
351
+ } catch (error) {
352
+ await tx.rollback().catch(() => {
353
+ });
354
+ throw error;
355
+ }
356
+ if (denied) throw new Error("Permission denied");
357
+ } else {
358
+ result = await mutateWithRLS(insertQuery, values, userId);
359
+ }
337
360
  if (!result.rows[0]) {
338
361
  throw new Error("Failed to create entity");
339
362
  }
@@ -382,6 +405,10 @@ class GenericEntityService {
382
405
  if (!validation.valid) {
383
406
  throw new Error(`Validation failed: ${validation.errors.join(", ")}`);
384
407
  }
408
+ const publish = "status" in finalData ? publishPermissionFor(entitySlug, finalData.status, currentEntity.status) : null;
409
+ if (publish && !await checkPermission(userId, teamId ?? currentEntity.teamId, publish)) {
410
+ throw new Error("Permission denied");
411
+ }
385
412
  const setClauses = [];
386
413
  const values = [];
387
414
  let paramIndex = 1;
@@ -403,6 +430,11 @@ class GenericEntityService {
403
430
  if (teamId) {
404
431
  values.push(teamId);
405
432
  updateWhereClause += ` AND "teamId" = $${paramIndex}`;
433
+ paramIndex++;
434
+ }
435
+ if ("status" in finalData && declaredPublishPermission(entitySlug)) {
436
+ values.push(currentEntity.status);
437
+ updateWhereClause += ` AND "status" IS NOT DISTINCT FROM $${paramIndex}`;
406
438
  }
407
439
  const result = await mutateWithRLS(
408
440
  `UPDATE ${tableName}
@@ -1 +1 @@
1
- {"version":3,"file":"transactional-meta.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/transactional-meta.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAuB,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAaxE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGlC,IAAI,CAAC,EAAE,MAAM;IACb,aAAa,CAAC,EAAE,KAAK;gBAF5B,OAAO,EAAE,MAAM,EACR,IAAI,CAAC,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,KAAK;CAK/B;AAED;;GAEG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAKtC;IAED;;;;;;;;;;;;;;;OAeG;WACU,cAAc,CAAC,CAAC,GAAG,OAAO,EACrC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,EAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,CAAC,CAAC;IAoIb;;;;;;;;;OASG;WACU,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,EACtD,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,CAAC,CAAC;IAkBb;;;;;;;;OAQG;WACU,cAAc,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;CA0CpB"}
1
+ {"version":3,"file":"transactional-meta.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/transactional-meta.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAuB,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAUxE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGlC,IAAI,CAAC,EAAE,MAAM;IACb,aAAa,CAAC,EAAE,KAAK;gBAF5B,OAAO,EAAE,MAAM,EACR,IAAI,CAAC,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,KAAK;CAK/B;AAED;;GAEG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAKtC;IAED;;;;;;;;;;;;;;;OAeG;WACU,cAAc,CAAC,CAAC,GAAG,OAAO,EACrC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,EAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,CAAC,CAAC;IAoIb;;;;;;;;;OASG;WACU,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,EACtD,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,CAAC,CAAC;IAkBb;;;;;;;;OAQG;WACU,cAAc,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;CA0CpB"}
@@ -1,13 +1,10 @@
1
- import { Pool } from "pg";
1
+ import { createPool } from "../db-pool.js";
2
2
  import { getEntityMetaConfig } from "../../types/meta.types.js";
3
3
  import { parseSSLConfig } from "../db.js";
4
4
  const databaseUrl = process.env.DATABASE_URL;
5
- const pool = new Pool({
5
+ const pool = createPool("transactional-meta", {
6
6
  connectionString: databaseUrl,
7
- ssl: parseSSLConfig(databaseUrl),
8
- max: 20,
9
- idleTimeoutMillis: 3e4,
10
- connectionTimeoutMillis: 1e4
7
+ ssl: parseSSLConfig(databaseUrl)
11
8
  });
12
9
  class TransactionalError extends Error {
13
10
  constructor(message, code, originalError) {
@@ -99,7 +96,8 @@ class TransactionalMetaService {
99
96
  client.release();
100
97
  return upsertResult.rows[0].metaValue;
101
98
  } catch (error) {
102
- await client.query("ROLLBACK");
99
+ await client.query("ROLLBACK").catch(() => {
100
+ });
103
101
  client.release();
104
102
  const pgError = error;
105
103
  lastError = pgError;
@@ -185,7 +183,8 @@ class TransactionalMetaService {
185
183
  client.release();
186
184
  return (result.rowCount || 0) > 0;
187
185
  } catch (error) {
188
- await client.query("ROLLBACK");
186
+ await client.query("ROLLBACK").catch(() => {
187
+ });
189
188
  client.release();
190
189
  const pgError = error;
191
190
  throw new TransactionalError(
@@ -9,6 +9,11 @@ export declare const baseSignupSchema: z.ZodObject<{
9
9
  email: z.ZodString;
10
10
  password: z.ZodString;
11
11
  }, z.core.$strip>;
12
+ export declare const inviteSignupSchema: z.ZodObject<{
13
+ email: z.ZodString;
14
+ firstName: z.ZodString;
15
+ lastName: z.ZodString;
16
+ }, z.core.$strip>;
12
17
  export declare const signupSchema: z.ZodObject<{
13
18
  firstName: z.ZodString;
14
19
  lastName: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/lib/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,eAAO,MAAM,WAAW;;;iBAGtB,CAAC;AAGH,eAAO,MAAM,gBAAgB;;;;;iBAU3B,CAAC;AAGH,eAAO,MAAM,YAAY;;;;;;iBAKvB,CAAC;AAEH,eAAO,MAAM,mBAAmB;;iBAE9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAW5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;iBAgB/B,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;iBAMxB,CAAC;AAGH,eAAO,MAAM,oBAAoB;;;;;;iBAQ/B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACpE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/lib/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,eAAO,MAAM,WAAW;;;iBAGtB,CAAC;AAGH,eAAO,MAAM,gBAAgB;;;;;iBAU3B,CAAC;AAGH,eAAO,MAAM,kBAAkB;;;;iBAA4C,CAAC;AAG5E,eAAO,MAAM,YAAY;;;;;;iBAKvB,CAAC;AAEH,eAAO,MAAM,mBAAmB;;iBAE9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAW5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;iBAgB/B,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;iBAMxB,CAAC;AAGH,eAAO,MAAM,oBAAoB;;;;;;iBAQ/B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACpE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
@@ -9,6 +9,7 @@ const baseSignupSchema = z.object({
9
9
  email: z.string().email("Invalid email"),
10
10
  password: z.string().min(8, "Password must be at least 8 characters").regex(/[A-Z]/, "Must contain at least one uppercase letter").regex(/[a-z]/, "Must contain at least one lowercase letter").regex(/[0-9]/, "Must contain at least one number")
11
11
  });
12
+ const inviteSignupSchema = baseSignupSchema.omit({ password: true });
12
13
  const signupSchema = baseSignupSchema.extend({
13
14
  confirmPassword: z.string()
14
15
  }).refine((data) => data.password === data.confirmPassword, {
@@ -56,6 +57,7 @@ const partialProfileSchema = z.object({
56
57
  export {
57
58
  baseSignupSchema,
58
59
  changePasswordSchema,
60
+ inviteSignupSchema,
59
61
  loginSchema,
60
62
  newPasswordSchema,
61
63
  partialProfileSchema,
@@ -0,0 +1,29 @@
1
+ -- Migration: 030_taxonomies_team_writes.sql
2
+ -- Description: Taxonomy rows are written by members of their team; rows without a team only by a superadmin.
3
+ --
4
+ -- 021 let any authenticated user write a taxonomy row with "teamId" IS NULL. Post categories are now created in
5
+ -- the caller's team (lib/api/post-categories.ts), and the rows without a team that earlier releases created stay
6
+ -- readable by everyone but are changed only by a superadmin. This policy says the same on the RLS-enforced
7
+ -- connection: reads are as before, writes need the row's team (or is_superadmin()).
8
+ -- is_superadmin() also accepts a developer enrolled in the System Admin Team; the API (lib/api/post-categories.ts) is
9
+ -- stricter and lets only users.role = 'superadmin' change a row without a team.
10
+ -- Idempotent; the anon read policy from 006 is unchanged.
11
+
12
+ DROP POLICY IF EXISTS "taxonomies auth can do all" ON public.taxonomies;
13
+ DROP POLICY IF EXISTS "taxonomies auth can select" ON public.taxonomies;
14
+ DROP POLICY IF EXISTS "taxonomies team can write" ON public.taxonomies;
15
+
16
+ CREATE POLICY "taxonomies auth can select"
17
+ ON public.taxonomies
18
+ FOR SELECT TO authenticated
19
+ USING (
20
+ "teamId" IS NULL
21
+ OR "teamId" = ANY(public.get_user_team_ids())
22
+ OR public.is_superadmin()
23
+ );
24
+
25
+ CREATE POLICY "taxonomies team can write"
26
+ ON public.taxonomies
27
+ FOR ALL TO authenticated
28
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin())
29
+ WITH CHECK ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
@@ -0,0 +1,41 @@
1
+ -- Migration: 031_users_role_suspended.sql
2
+ -- Description: users.role accepts 'suspended' (lib/auth/suspension.ts).
3
+ --
4
+ -- The superadmin "suspend" action sets users.role = 'suspended', which check_users_role (002: member, superadmin,
5
+ -- developer) refused. This adds 'suspended' to the roles the constraint already accepts and keeps every other one,
6
+ -- including roles a project added to it. Idempotent: a constraint that already accepts 'suspended' is left alone. A
7
+ -- constraint that is not a plain list of roles is left alone too, with a notice (add 'suspended' to it yourself).
8
+
9
+ DO $$
10
+ DECLARE
11
+ def TEXT;
12
+ roles TEXT[];
13
+ BEGIN
14
+ SELECT pg_get_constraintdef(c.oid) INTO def
15
+ FROM pg_constraint c
16
+ WHERE c.conrelid = 'public.users'::regclass AND c.conname = 'check_users_role';
17
+
18
+ IF def IS NULL THEN
19
+ ALTER TABLE public."users" ADD CONSTRAINT check_users_role
20
+ CHECK ("role" IN ('member', 'superadmin', 'developer', 'suspended'));
21
+ RETURN;
22
+ END IF;
23
+
24
+ IF def ~ '\msuspended\M' THEN
25
+ RETURN;
26
+ END IF;
27
+
28
+ -- "role" IN (...) is stored as: CHECK ((role = ANY (ARRAY['member'::text, ...])))
29
+ IF def !~ '^CHECK \(\(+"?role"?\)?(::text)? = ANY \(\(?ARRAY\[' THEN
30
+ RAISE NOTICE 'check_users_role is not a plain list of roles (%); add ''suspended'' to it to use the suspend action', def;
31
+ RETURN;
32
+ END IF;
33
+
34
+ SELECT array_agg(m[1]) INTO roles FROM regexp_matches(def, '''([^'']+)''', 'g') AS m;
35
+
36
+ ALTER TABLE public."users" DROP CONSTRAINT check_users_role;
37
+ EXECUTE format(
38
+ 'ALTER TABLE public."users" ADD CONSTRAINT check_users_role CHECK ("role" IN (%s))',
39
+ (SELECT string_agg(quote_literal(r), ', ') FROM unnest(array_append(roles, 'suspended')) AS r)
40
+ );
41
+ END $$;
@@ -1,4 +1,6 @@
1
1
  import { metadata } from './page';
2
2
  export { metadata };
3
- export default function SignupPageCc(): import("react/jsx-runtime").JSX.Element;
3
+ export default function SignupPageCc({ searchParams }: {
4
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
5
+ }): import("react/jsx-runtime").JSX.Element;
4
6
  //# sourceMappingURL=page.cc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.cc.d.ts","sourceRoot":"","sources":["../../../../src/routes/(auth)/signup/page.cc.tsx"],"names":[],"mappings":"AAUA,OAAmB,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,CAAA;AAEnB,MAAM,CAAC,OAAO,UAAU,YAAY,4CAMnC"}
1
+ {"version":3,"file":"page.cc.d.ts","sourceRoot":"","sources":["../../../../src/routes/(auth)/signup/page.cc.tsx"],"names":[],"mappings":"AAUA,OAAmB,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,CAAA;AAEnB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAA;CAAE,2CAM9H"}
@@ -2,6 +2,6 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { Suspense } from "react";
3
3
  import SignupPage, { metadata } from "./page.js";
4
4
  export { metadata };
5
- export default function SignupPageCc() {
6
- return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SignupPage, {}) });
5
+ export default function SignupPageCc({ searchParams }) {
6
+ return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SignupPage, { searchParams }) });
7
7
  }
@@ -1,6 +1,8 @@
1
1
  import type { Metadata } from 'next';
2
2
  export declare const dynamic = "force-dynamic";
3
3
  export declare const metadata: Metadata;
4
- declare function SignupPage(): Promise<import("react/jsx-runtime").JSX.Element>;
4
+ declare function SignupPage({ searchParams }: {
5
+ searchParams?: Promise<Record<string, string | string[] | undefined>>;
6
+ }): Promise<import("react/jsx-runtime").JSX.Element>;
5
7
  export default SignupPage;
6
8
  //# sourceMappingURL=page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../src/routes/(auth)/signup/page.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AASpC,eAAO,MAAM,OAAO,kBAAkB,CAAA;AAOtC,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAA;AAKjD,iBAAe,UAAU,qDA4BxB;AAED,eAAe,UAAU,CAAA"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../src/routes/(auth)/signup/page.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AASpC,eAAO,MAAM,OAAO,kBAAkB,CAAA;AAOtC,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAA;AAKjD,iBAAe,UAAU,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAA;CAAE,oDA8BpH;AAED,eAAe,UAAU,CAAA"}
@@ -10,10 +10,11 @@ const defaultMetadata = {
10
10
  description: "Create your account to start using our platform"
11
11
  };
12
12
  export const metadata = defaultMetadata;
13
- async function SignupPage() {
14
- var _a, _b;
13
+ async function SignupPage({ searchParams }) {
14
+ var _a, _b, _c;
15
15
  const registrationMode = ((_b = (_a = AUTH_CONFIG) == null ? void 0 : _a.registration) == null ? void 0 : _b.mode) ?? "open";
16
- if (!resolveAuthMethods(AUTH_CONFIG).includes("email-password")) {
16
+ const invited = typeof ((_c = await searchParams) == null ? void 0 : _c.inviteToken) === "string";
17
+ if (!invited && !resolveAuthMethods(AUTH_CONFIG).includes("email-password")) {
17
18
  redirect("/login");
18
19
  }
19
20
  if (registrationMode === "invitation-only") {