@rebasepro/server-postgresql 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +9 -0
  2. package/dist/common/src/collections/index.d.ts +1 -0
  3. package/dist/common/src/util/permissions.d.ts +1 -0
  4. package/dist/index.es.js +1075 -470
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +1071 -466
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +3 -1
  9. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +1 -0
  10. package/dist/server-postgresql/src/auth/services.d.ts +48 -31
  11. package/dist/server-postgresql/src/connection.d.ts +25 -0
  12. package/dist/server-postgresql/src/schema/auth-schema.d.ts +2135 -41
  13. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +4 -0
  14. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
  15. package/dist/server-postgresql/src/services/entityService.d.ts +6 -0
  16. package/dist/server-postgresql/src/services/realtimeService.d.ts +20 -0
  17. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +18 -0
  18. package/dist/types/src/controllers/auth.d.ts +4 -26
  19. package/dist/types/src/controllers/client.d.ts +25 -43
  20. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  21. package/dist/types/src/controllers/data.d.ts +4 -0
  22. package/dist/types/src/controllers/data_driver.d.ts +23 -0
  23. package/dist/types/src/controllers/registry.d.ts +5 -4
  24. package/dist/types/src/rebase_context.d.ts +1 -1
  25. package/dist/types/src/types/auth_adapter.d.ts +5 -60
  26. package/dist/types/src/types/backend.d.ts +2 -2
  27. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  28. package/dist/types/src/types/collections.d.ts +0 -4
  29. package/dist/types/src/types/component_ref.d.ts +1 -1
  30. package/dist/types/src/types/cron.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +1 -0
  32. package/dist/types/src/types/export_import.d.ts +1 -1
  33. package/dist/types/src/types/formex.d.ts +2 -2
  34. package/dist/types/src/types/properties.d.ts +9 -7
  35. package/dist/types/src/types/translations.d.ts +28 -12
  36. package/dist/types/src/types/user_management_delegate.d.ts +22 -57
  37. package/dist/types/src/users/index.d.ts +0 -1
  38. package/dist/types/src/users/user.d.ts +0 -1
  39. package/package.json +6 -6
  40. package/src/PostgresBackendDriver.ts +14 -2
  41. package/src/PostgresBootstrapper.ts +30 -20
  42. package/src/auth/ensure-tables.ts +116 -103
  43. package/src/auth/services.ts +347 -177
  44. package/src/connection.ts +77 -0
  45. package/src/data-transformer.ts +2 -2
  46. package/src/schema/auth-schema.ts +85 -75
  47. package/src/schema/doctor.ts +44 -3
  48. package/src/schema/generate-drizzle-schema-logic.ts +33 -3
  49. package/src/schema/generate-drizzle-schema.ts +6 -6
  50. package/src/schema/introspect-db-logic.ts +7 -0
  51. package/src/services/EntityFetchService.ts +69 -10
  52. package/src/services/EntityPersistService.ts +9 -0
  53. package/src/services/entityService.ts +9 -0
  54. package/src/services/realtimeService.ts +214 -2
  55. package/src/utils/drizzle-conditions.ts +74 -2
  56. package/src/websocket.ts +10 -2
  57. package/test/auth-services.test.ts +10 -166
  58. package/test/doctor.test.ts +6 -2
  59. package/test/drizzle-conditions.test.ts +168 -0
  60. package/vite.config.ts +1 -1
  61. package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
  62. package/dist/types/src/users/roles.d.ts +0 -22
  63. package/src/schema/default-collections.ts +0 -69
@@ -3,40 +3,16 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
  import { getTableConfig, AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
4
4
  import { getColumnMeta } from "../services/entity-helpers";
5
5
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
6
+ import { logger } from "@rebasepro/server-core";
7
+
6
8
 
7
- /**
8
- * Default roles to seed on first run
9
- */
10
- const DEFAULT_ROLES = [
11
- {
12
- id: "admin",
13
- name: "Admin",
14
- is_admin: true,
15
- default_permissions: { read: true, create: true, edit: true, delete: true },
16
- config: { createCollections: true, editCollections: "all", deleteCollections: "all" }
17
- },
18
- {
19
- id: "editor",
20
- name: "Editor",
21
- is_admin: false,
22
- default_permissions: { read: true, create: true, edit: true, delete: true },
23
- config: { createCollections: true, editCollections: "own", deleteCollections: "own" }
24
- },
25
- {
26
- id: "viewer",
27
- name: "Viewer",
28
- is_admin: false,
29
- default_permissions: { read: true, create: false, edit: false, delete: false },
30
- config: null
31
- }
32
- ];
33
9
 
34
10
  /**
35
11
  * Auto-create auth tables if they don't exist
36
12
  * This runs on startup to ensure the database is ready for auth
37
13
  */
38
14
  export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: PostgresCollectionRegistry): Promise<void> {
39
- console.log("🔍 Checking auth tables...");
15
+ logger.info("🔍 Checking auth tables...");
40
16
 
41
17
  try {
42
18
  // Resolve dynamic user table name and ID type
@@ -66,31 +42,19 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: Postg
66
42
  }
67
43
  }
68
44
 
69
- // Resolve dynamic roles schema name
70
- let rolesSchema = "rebase";
71
- if (registry) {
72
- const rolesTable = registry.getTable("roles");
73
- if (rolesTable) {
74
- rolesSchema = getTableConfig(rolesTable).schema || "public";
75
- }
76
- }
45
+
77
46
 
78
47
  // ── Create schemas (idempotent) ──────────────────────────────────
79
48
  if (usersSchema !== "public") {
80
49
  await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(usersSchema)}`);
81
50
  }
82
- if (rolesSchema !== "public" && rolesSchema !== usersSchema) {
83
- await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(rolesSchema)}`);
84
- }
85
51
  await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
86
52
 
87
- // Dynamic table names
88
- const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
89
- const rolesTableName = `"${rolesSchema}"."roles"`;
90
- const userRolesTableName = `"${rolesSchema}"."user_roles"`;
91
- const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
92
- const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
93
- const appConfigTableName = `"${rolesSchema}"."app_config"`;
53
+ const authSchema = usersSchema === "public" ? "rebase" : usersSchema;
54
+ const userIdentitiesTable = `"${authSchema}"."user_identities"`;
55
+ const refreshTokensTableName = `"${authSchema}"."refresh_tokens"`;
56
+ const passwordResetTokensTableName = `"${authSchema}"."password_reset_tokens"`;
57
+ const appConfigTableName = `"${authSchema}"."app_config"`;
94
58
 
95
59
  // ── Create tables (idempotent) ──────────────────────────────────
96
60
 
@@ -115,33 +79,6 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: Postg
115
79
  `);
116
80
 
117
81
 
118
- // Create roles table
119
- await db.execute(sql`
120
- CREATE TABLE IF NOT EXISTS ${sql.raw(rolesTableName)} (
121
- id TEXT PRIMARY KEY,
122
- name TEXT NOT NULL,
123
- is_admin BOOLEAN DEFAULT FALSE,
124
- default_permissions JSONB,
125
- collection_permissions JSONB,
126
- config JSONB,
127
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
128
- )
129
- `);
130
-
131
- // Create user_roles junction table
132
- await db.execute(sql`
133
- CREATE TABLE IF NOT EXISTS ${sql.raw(userRolesTableName)} (
134
- user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
135
- role_id TEXT NOT NULL REFERENCES ${sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
136
- PRIMARY KEY (user_id, role_id)
137
- )
138
- `);
139
-
140
- // Create index on user_id for faster lookups
141
- await db.execute(sql`
142
- CREATE INDEX IF NOT EXISTS idx_user_roles_user
143
- ON ${sql.raw(userRolesTableName)}(user_id)
144
- `);
145
82
 
146
83
  // Create refresh tokens table (includes user_agent, ip_address, and unique constraint)
147
84
  await db.execute(sql`
@@ -232,43 +169,119 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: Postg
232
169
  });
233
170
 
234
171
  // Seed default roles if none exist
235
- await seedDefaultRoles(db, rolesTableName);
172
+ // (no-op: roles are now stored inline on the users table)
236
173
 
237
- console.log("✅ Auth tables ready");
238
- } catch (error) {
239
- console.error("❌ Failed to create auth tables:", error);
240
- console.warn("⚠️ Continuing without creating auth tables.");
241
- }
242
- }
174
+ // ── Migration: Add is_anonymous column (safe for existing tables) ────
175
+ await db.execute(sql`
176
+ ALTER TABLE ${sql.raw(usersTableName)}
177
+ ADD COLUMN IF NOT EXISTS is_anonymous BOOLEAN DEFAULT FALSE
178
+ `);
243
179
 
244
- /**
245
- * Seed default roles if the roles table is empty
246
- */
247
- async function seedDefaultRoles(db: NodePgDatabase, rolesTableName: string): Promise<void> {
248
- // Check if any roles exist
249
- const result = await db.execute(sql`SELECT COUNT(*) as count FROM ${sql.raw(rolesTableName)}`);
250
- const count = parseInt((result.rows[0] as Record<string, string | number>)?.count as string || "0", 10);
251
-
252
- if (count > 0) {
253
- console.log(`📋 Found ${count} existing roles`);
254
- return;
255
- }
180
+ // ── Migration: Add inline roles column (safe for existing tables) ────
181
+ await db.execute(sql`
182
+ ALTER TABLE ${sql.raw(usersTableName)}
183
+ ADD COLUMN IF NOT EXISTS roles TEXT[] DEFAULT '{}' NOT NULL
184
+ `);
256
185
 
257
- console.log("🌱 Seeding default roles...");
186
+ // ── Migration: Copy roles from legacy junction table to inline column ──
187
+ // If the old rebase.user_roles and rebase.roles tables exist, migrate
188
+ // the data into the new TEXT[] column then drop the legacy tables.
189
+ try {
190
+ const legacyCheck = await db.execute(sql`
191
+ SELECT EXISTS (
192
+ SELECT 1 FROM information_schema.tables
193
+ WHERE table_schema = 'rebase' AND table_name = 'user_roles'
194
+ ) AS has_user_roles
195
+ `);
196
+ const hasLegacyTables = (legacyCheck.rows[0] as { has_user_roles: boolean }).has_user_roles;
197
+
198
+ if (hasLegacyTables) {
199
+ logger.info("🔄 Migrating roles from legacy user_roles table...");
200
+ // Update users' roles column from the junction table
201
+ await db.execute(sql`
202
+ UPDATE ${sql.raw(usersTableName)} u
203
+ SET roles = COALESCE((
204
+ SELECT array_agg(ur.role_id)
205
+ FROM "rebase"."user_roles" ur
206
+ WHERE ur.user_id = u.id
207
+ ), '{}')
208
+ WHERE u.roles = '{}' OR u.roles IS NULL
209
+ `);
210
+
211
+ // Drop legacy tables (junction first due to FK)
212
+ await db.execute(sql`DROP TABLE IF EXISTS "rebase"."user_roles" CASCADE`);
213
+ await db.execute(sql`DROP TABLE IF EXISTS "rebase"."roles" CASCADE`);
214
+ logger.info("✅ Legacy roles tables migrated and dropped");
215
+ }
216
+ } catch (migrationError: unknown) {
217
+ // Non-fatal: log and continue — the column exists and will work
218
+ logger.warn(`⚠️ Legacy roles migration skipped: ${migrationError instanceof Error ? migrationError.message : String(migrationError)}`);
219
+ }
258
220
 
259
- for (const role of DEFAULT_ROLES) {
221
+ // ── MFA tables ──────────────────────────────────────────────────────
222
+ const mfaFactorsTableName = `"${authSchema}"."mfa_factors"`;
223
+ const mfaChallengesTableName = `"${authSchema}"."mfa_challenges"`;
224
+ const recoveryCodesTableName = `"${authSchema}"."recovery_codes"`;
225
+
226
+ // Create mfa_factors table
260
227
  await db.execute(sql`
261
- INSERT INTO ${sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
262
- VALUES (
263
- ${role.id},
264
- ${role.name},
265
- ${role.is_admin},
266
- ${JSON.stringify(role.default_permissions)}::jsonb,
267
- ${role.config ? JSON.stringify(role.config) : null}::jsonb
228
+ CREATE TABLE IF NOT EXISTS ${sql.raw(mfaFactorsTableName)} (
229
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
230
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
231
+ factor_type TEXT NOT NULL DEFAULT 'totp',
232
+ secret_encrypted TEXT NOT NULL,
233
+ friendly_name TEXT,
234
+ verified BOOLEAN DEFAULT FALSE,
235
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
236
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
268
237
  )
269
- ON CONFLICT (id) DO NOTHING
270
238
  `);
271
- }
272
239
 
273
- console.log("✅ Default roles created: admin, editor, viewer");
240
+ // Create indexes on mfa_factors
241
+ await db.execute(sql`
242
+ CREATE INDEX IF NOT EXISTS idx_mfa_factors_user
243
+ ON ${sql.raw(mfaFactorsTableName)}(user_id)
244
+ `);
245
+
246
+ // Create mfa_challenges table
247
+ await db.execute(sql`
248
+ CREATE TABLE IF NOT EXISTS ${sql.raw(mfaChallengesTableName)} (
249
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
250
+ factor_id TEXT NOT NULL REFERENCES ${sql.raw(mfaFactorsTableName)}(id) ON DELETE CASCADE,
251
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
252
+ verified_at TIMESTAMP WITH TIME ZONE,
253
+ ip_address TEXT,
254
+ expires_at TIMESTAMP WITH TIME ZONE NOT NULL
255
+ )
256
+ `);
257
+
258
+ // Create indexes on mfa_challenges
259
+ await db.execute(sql`
260
+ CREATE INDEX IF NOT EXISTS idx_mfa_challenges_factor
261
+ ON ${sql.raw(mfaChallengesTableName)}(factor_id)
262
+ `);
263
+
264
+ // Create recovery_codes table
265
+ await db.execute(sql`
266
+ CREATE TABLE IF NOT EXISTS ${sql.raw(recoveryCodesTableName)} (
267
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
268
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
269
+ code_hash TEXT NOT NULL,
270
+ used_at TIMESTAMP WITH TIME ZONE,
271
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
272
+ )
273
+ `);
274
+
275
+ // Create indexes on recovery_codes
276
+ await db.execute(sql`
277
+ CREATE INDEX IF NOT EXISTS idx_recovery_codes_user
278
+ ON ${sql.raw(recoveryCodesTableName)}(user_id)
279
+ `);
280
+
281
+ logger.info("✅ Auth tables ready");
282
+ } catch (error) {
283
+ logger.error("❌ Failed to create auth tables", { error });
284
+ logger.warn("⚠️ Continuing without creating auth tables.");
285
+ }
274
286
  }
287
+