@rebasepro/server-postgresql 0.3.0 → 0.4.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 (49) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +5 -8
  2. package/dist/common/src/data/query_builder.d.ts +6 -2
  3. package/dist/index.es.js +301 -500
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +297 -496
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -0
  8. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +7 -4
  9. package/dist/server-postgresql/src/auth/services.d.ts +6 -31
  10. package/dist/server-postgresql/src/schema/auth-schema.d.ts +87 -340
  11. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +2 -1
  12. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
  13. package/dist/server-postgresql/src/services/entityService.d.ts +4 -0
  14. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +5 -1
  15. package/dist/types/src/controllers/auth.d.ts +2 -2
  16. package/dist/types/src/controllers/client.d.ts +25 -40
  17. package/dist/types/src/controllers/data.d.ts +21 -3
  18. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  19. package/dist/types/src/controllers/email.d.ts +2 -0
  20. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  21. package/dist/types/src/types/backend.d.ts +2 -2
  22. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  23. package/dist/types/src/types/collections.d.ts +9 -5
  24. package/dist/types/src/types/entity_views.d.ts +19 -28
  25. package/dist/types/src/types/properties.d.ts +9 -7
  26. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  27. package/dist/types/src/users/index.d.ts +0 -1
  28. package/dist/types/src/users/user.d.ts +0 -1
  29. package/package.json +6 -6
  30. package/src/PostgresBackendDriver.ts +10 -0
  31. package/src/PostgresBootstrapper.ts +25 -21
  32. package/src/auth/ensure-tables.ts +82 -129
  33. package/src/auth/services.ts +71 -170
  34. package/src/schema/auth-schema.ts +13 -69
  35. package/src/schema/doctor.ts +44 -3
  36. package/src/schema/generate-drizzle-schema-logic.ts +33 -3
  37. package/src/schema/generate-drizzle-schema.ts +2 -6
  38. package/src/schema/introspect-db-logic.ts +7 -0
  39. package/src/services/EntityFetchService.ts +13 -1
  40. package/src/services/EntityPersistService.ts +9 -0
  41. package/src/services/entityService.ts +7 -0
  42. package/src/utils/drizzle-conditions.ts +40 -5
  43. package/src/websocket.ts +1 -3
  44. package/test/auth-services.test.ts +7 -150
  45. package/test/doctor.test.ts +6 -2
  46. package/test/relation-pipeline-gaps.test.ts +315 -0
  47. package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
  48. package/dist/types/src/users/roles.d.ts +0 -14
  49. package/src/schema/default-collections.ts +0 -69
package/dist/index.umd.js CHANGED
@@ -2719,114 +2719,6 @@
2719
2719
  };
2720
2720
  }
2721
2721
  }
2722
- const defaultUsersCollection = {
2723
- name: "Users",
2724
- singularName: "User",
2725
- slug: "users",
2726
- table: "users",
2727
- schema: "rebase",
2728
- icon: "Users",
2729
- group: "Settings",
2730
- properties: {
2731
- id: {
2732
- name: "ID",
2733
- type: "string",
2734
- isId: "uuid"
2735
- },
2736
- email: {
2737
- name: "Email",
2738
- type: "string",
2739
- validation: {
2740
- required: true,
2741
- unique: true
2742
- }
2743
- },
2744
- password_hash: {
2745
- name: "Password Hash",
2746
- type: "string",
2747
- ui: {
2748
- hideFromCollection: true
2749
- }
2750
- },
2751
- display_name: {
2752
- name: "Display Name",
2753
- type: "string"
2754
- },
2755
- photo_url: {
2756
- name: "Photo URL",
2757
- type: "string"
2758
- },
2759
- email_verified: {
2760
- name: "Email Verified",
2761
- type: "boolean",
2762
- defaultValue: false
2763
- },
2764
- email_verification_token: {
2765
- name: "Email Verification Token",
2766
- type: "string",
2767
- ui: {
2768
- hideFromCollection: true
2769
- }
2770
- },
2771
- email_verification_sent_at: {
2772
- name: "Email Verification Sent At",
2773
- type: "date",
2774
- ui: {
2775
- hideFromCollection: true
2776
- }
2777
- },
2778
- metadata: {
2779
- name: "Metadata",
2780
- type: "map",
2781
- defaultValue: {},
2782
- ui: {
2783
- hideFromCollection: true
2784
- }
2785
- },
2786
- created_at: {
2787
- name: "Created At",
2788
- type: "date",
2789
- autoValue: "on_create",
2790
- ui: {
2791
- readOnly: true,
2792
- hideFromCollection: true
2793
- }
2794
- },
2795
- updated_at: {
2796
- name: "Updated At",
2797
- type: "date",
2798
- autoValue: "on_update",
2799
- ui: {
2800
- readOnly: true,
2801
- hideFromCollection: true
2802
- }
2803
- }
2804
- }
2805
- };
2806
- function mapOperator(op) {
2807
- switch (op) {
2808
- case "==":
2809
- return "eq";
2810
- case "!=":
2811
- return "neq";
2812
- case ">":
2813
- return "gt";
2814
- case ">=":
2815
- return "gte";
2816
- case "<":
2817
- return "lt";
2818
- case "<=":
2819
- return "lte";
2820
- case "array-contains":
2821
- return "cs";
2822
- case "array-contains-any":
2823
- return "csa";
2824
- case "not-in":
2825
- return "nin";
2826
- default:
2827
- return op;
2828
- }
2829
- }
2830
2722
  class QueryBuilder {
2831
2723
  constructor(collection) {
2832
2724
  this.collection = collection;
@@ -2834,23 +2726,30 @@
2834
2726
  params = {
2835
2727
  where: {}
2836
2728
  };
2837
- /**
2838
- * Add a filter condition to your query.
2839
- * @example
2840
- * client.collection('users').where('age', '>=', 18).find()
2841
- */
2842
- where(column, operator, value) {
2729
+ where(columnOrCondition, operator, value) {
2730
+ if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
2731
+ this.params.logical = columnOrCondition;
2732
+ return this;
2733
+ }
2843
2734
  if (!this.params.where) {
2844
2735
  this.params.where = {};
2845
2736
  }
2846
- const mappedOp = mapOperator(operator);
2847
- let formattedValue = value;
2848
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2849
- formattedValue = `(${value.join(",")})`;
2850
- } else if (value === null) {
2851
- formattedValue = "null";
2737
+ const column = columnOrCondition;
2738
+ const condition = [operator, value];
2739
+ const existing = this.params.where[column];
2740
+ if (existing === void 0) {
2741
+ this.params.where[column] = condition;
2742
+ } else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
2743
+ this.params.where[column].push(condition);
2744
+ } else {
2745
+ let firstCondition;
2746
+ if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
2747
+ firstCondition = existing;
2748
+ } else {
2749
+ firstCondition = ["==", existing];
2750
+ }
2751
+ this.params.where[column] = [firstCondition, condition];
2852
2752
  }
2853
- this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2854
2753
  return this;
2855
2754
  }
2856
2755
  /**
@@ -2952,10 +2851,13 @@
2952
2851
  filter[field] = ["==", rawValue];
2953
2852
  continue;
2954
2853
  }
2955
- if (Array.isArray(rawValue) && rawValue.length === 2) {
2956
- const [rawOp, val] = rawValue;
2957
- const mappedOp = operatorMap[rawOp] ?? "==";
2958
- filter[field] = [mappedOp, val];
2854
+ if (Array.isArray(rawValue)) {
2855
+ const conditions = Array.isArray(rawValue[0]) ? rawValue : [rawValue];
2856
+ const mappedConditions = conditions.map(([rawOp, val]) => {
2857
+ const mappedOp = operatorMap[rawOp] ?? "==";
2858
+ return [mappedOp, val];
2859
+ });
2860
+ filter[field] = Array.isArray(rawValue[0]) ? mappedConditions : mappedConditions[0];
2959
2861
  continue;
2960
2862
  }
2961
2863
  if (typeof rawValue === "string") {
@@ -3049,6 +2951,9 @@
3049
2951
  }
3050
2952
  });
3051
2953
  },
2954
+ deleteAll: driver.deleteAll ? async () => {
2955
+ return driver.deleteAll(slug);
2956
+ } : void 0,
3052
2957
  count: driver.countEntities ? async (params) => {
3053
2958
  return driver.countEntities({
3054
2959
  path: slug,
@@ -3090,8 +2995,12 @@
3090
2995
  });
3091
2996
  } : void 0,
3092
2997
  // Fluent Query Builder
3093
- where(column, operator, value) {
3094
- return new QueryBuilder(accessor).where(column, operator, value);
2998
+ where(columnOrCondition, operator, value) {
2999
+ const builder = new QueryBuilder(accessor);
3000
+ if (typeof columnOrCondition === "object") {
3001
+ return builder.where(columnOrCondition);
3002
+ }
3003
+ return builder.where(columnOrCondition, operator, value);
3095
3004
  },
3096
3005
  orderBy(column, ascending) {
3097
3006
  return new QueryBuilder(accessor).orderBy(column, ascending);
@@ -3142,7 +3051,6 @@
3142
3051
  const conditions = [];
3143
3052
  for (const [field, filterParam] of Object.entries(filter)) {
3144
3053
  if (!filterParam) continue;
3145
- const [op, value] = filterParam;
3146
3054
  let fieldColumn = table[field];
3147
3055
  if (!fieldColumn) {
3148
3056
  const relationKey = `${field}_id`;
@@ -3154,13 +3062,39 @@
3154
3062
  console.warn(`Filtering by field '${field}', but it does not exist in table for collection '${collectionPath}'`);
3155
3063
  continue;
3156
3064
  }
3157
- const condition = this.buildSingleFilterCondition(fieldColumn, op, value);
3158
- if (condition) {
3159
- conditions.push(condition);
3065
+ const paramsList = Array.isArray(filterParam) && filterParam.length > 0 && Array.isArray(filterParam[0]) ? filterParam : [filterParam];
3066
+ for (const [op, value] of paramsList) {
3067
+ const condition = this.buildSingleFilterCondition(fieldColumn, op, value);
3068
+ if (condition) {
3069
+ conditions.push(condition);
3070
+ }
3160
3071
  }
3161
3072
  }
3162
3073
  return conditions;
3163
3074
  }
3075
+ /**
3076
+ * Build logical conditions recursively from LogicalCondition or FilterCondition
3077
+ */
3078
+ static buildLogicalConditions(cond, table, collectionPath) {
3079
+ if ("type" in cond) {
3080
+ const subSQLs = cond.conditions.map((c) => this.buildLogicalConditions(c, table, collectionPath)).filter((sql2) => sql2 !== null);
3081
+ if (subSQLs.length === 0) return null;
3082
+ return (cond.type === "or" ? drizzleOrm.or(...subSQLs) : drizzleOrm.and(...subSQLs)) ?? null;
3083
+ } else {
3084
+ let fieldColumn = table[cond.column];
3085
+ if (!fieldColumn) {
3086
+ const relationKey = `${cond.column}_id`;
3087
+ if (relationKey in table) {
3088
+ fieldColumn = table[relationKey];
3089
+ }
3090
+ }
3091
+ if (!fieldColumn) {
3092
+ console.warn(`Filtering by field '${cond.column}', but it does not exist in table for collection '${collectionPath}'`);
3093
+ return null;
3094
+ }
3095
+ return this.buildSingleFilterCondition(fieldColumn, cond.operator, cond.value);
3096
+ }
3097
+ }
3164
3098
  /**
3165
3099
  * Build a single filter condition for a specific operator and value
3166
3100
  */
@@ -5557,6 +5491,10 @@
5557
5491
  const filterConditions = this.buildFilterConditions(options.filter, table, collectionPath);
5558
5492
  if (filterConditions.length > 0) allConditions.push(...filterConditions);
5559
5493
  }
5494
+ if (options.logical) {
5495
+ const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath);
5496
+ if (logicalCondition) allConditions.push(logicalCondition);
5497
+ }
5560
5498
  if (options.startAfter) {
5561
5499
  const cursorConditions = this.buildCursorConditions(table, idField, idInfo, options, collectionPath);
5562
5500
  if (cursorConditions.length > 0) allConditions.push(...cursorConditions);
@@ -5728,6 +5666,10 @@
5728
5666
  const filterConditions = this.buildFilterConditions(options.filter, table, collectionPath);
5729
5667
  if (filterConditions.length > 0) allConditions.push(...filterConditions);
5730
5668
  }
5669
+ if (options.logical) {
5670
+ const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath);
5671
+ if (logicalCondition) allConditions.push(logicalCondition);
5672
+ }
5731
5673
  if (vectorMeta?.filter) {
5732
5674
  allConditions.push(vectorMeta.filter);
5733
5675
  }
@@ -6313,6 +6255,14 @@
6313
6255
  const parsedId = parsedIdObj[idInfo.fieldName];
6314
6256
  await this.db.delete(table).where(drizzleOrm.eq(idField, parsedId));
6315
6257
  }
6258
+ /**
6259
+ * Delete all entities from a collection
6260
+ */
6261
+ async deleteAll(collectionPath, _databaseId) {
6262
+ const collection = getCollectionByPath(collectionPath, this.registry);
6263
+ const table = getTableForCollection(collection, this.registry);
6264
+ await this.db.delete(table);
6265
+ }
6316
6266
  /**
6317
6267
  * Save an entity (create or update)
6318
6268
  */
@@ -6644,6 +6594,12 @@
6644
6594
  async deleteEntity(collectionPath, entityId, databaseId) {
6645
6595
  return this.persistService.deleteEntity(collectionPath, entityId, databaseId);
6646
6596
  }
6597
+ /**
6598
+ * Delete all entities from a collection
6599
+ */
6600
+ async deleteAll(collectionPath, databaseId) {
6601
+ return this.persistService.deleteAll(collectionPath, databaseId);
6602
+ }
6647
6603
  /**
6648
6604
  * Execute raw SQL
6649
6605
  */
@@ -7344,6 +7300,10 @@
7344
7300
  await this.realtimeService.notifyEntityUpdate(entity.path, entity.id.toString(), null, entity.databaseId || resolvedCollection?.databaseId);
7345
7301
  }
7346
7302
  }
7303
+ async deleteAll(path2) {
7304
+ await this.entityService.deleteAll(path2);
7305
+ await this.realtimeService.notifyEntityUpdate(path2, "*", null);
7306
+ }
7347
7307
  async checkUniqueField(path2, name, value, entityId, collection) {
7348
7308
  return this.entityService.checkUniqueField(path2, name, value, entityId, collection?.databaseId);
7349
7309
  }
@@ -7613,11 +7573,11 @@
7613
7573
  console.warn("[DataDriver] User ID (uid) is missing for authenticated delegate. Using 'anonymous'. User object:", this.user);
7614
7574
  userId = "anonymous";
7615
7575
  }
7616
- const userRoles2 = this.user?.roles ?? [];
7576
+ const userRoles = this.user?.roles ?? [];
7617
7577
  if (!this.user?.roles) {
7618
7578
  console.warn("[DataDriver] User roles are missing for authenticated delegate. Using empty array. User object:", this.user);
7619
7579
  }
7620
- const normalizedRoles = userRoles2.map((r) => typeof r === "string" ? r : r?.id ?? String(r));
7580
+ const normalizedRoles = userRoles.map((r) => typeof r === "string" ? r : r?.id ?? String(r));
7621
7581
  const rolesString = normalizedRoles.join(",");
7622
7582
  await tx.execute(drizzleOrm.sql`
7623
7583
  SELECT
@@ -7625,7 +7585,7 @@
7625
7585
  set_config('app.user_roles', ${rolesString}, true),
7626
7586
  set_config('app.jwt', ${JSON.stringify({
7627
7587
  sub: userId,
7628
- roles: userRoles2
7588
+ roles: userRoles
7629
7589
  })}, true)
7630
7590
  `);
7631
7591
  const txEntityService = new EntityService(tx, this.delegate.registry);
@@ -7680,6 +7640,9 @@
7680
7640
  async deleteEntity(props) {
7681
7641
  return this.withTransaction((delegate) => delegate.deleteEntity(props));
7682
7642
  }
7643
+ async deleteAll(path2) {
7644
+ return this.delegate.deleteAll(path2);
7645
+ }
7683
7646
  async checkUniqueField(path2, name, value, entityId, collection) {
7684
7647
  return this.withTransaction((delegate) => delegate.checkUniqueField(path2, name, value, entityId, collection));
7685
7648
  }
@@ -7759,11 +7722,10 @@
7759
7722
  this.pools.clear();
7760
7723
  }
7761
7724
  }
7762
- function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
7763
- const rolesSchema = rolesSchemaName === "public" ? null : pgCore.pgSchema(rolesSchemaName);
7725
+ function createAuthSchema(usersSchemaName = "rebase") {
7764
7726
  const usersSchema2 = usersSchemaName === "public" ? null : pgCore.pgSchema(usersSchemaName);
7765
- const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgCore.pgTable;
7766
- const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgCore.pgTable;
7727
+ const tableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgCore.pgTable;
7728
+ const usersTableCreator = tableCreator;
7767
7729
  const users2 = usersTableCreator("users", {
7768
7730
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7769
7731
  email: pgCore.varchar("email", {
@@ -7785,37 +7747,12 @@
7785
7747
  }),
7786
7748
  emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
7787
7749
  isAnonymous: pgCore.boolean("is_anonymous").default(false).notNull(),
7750
+ roles: pgCore.text("roles").array().default([]).notNull(),
7788
7751
  metadata: pgCore.jsonb("metadata").$type().default({}).notNull(),
7789
7752
  createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7790
7753
  updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7791
7754
  });
7792
- const roles2 = rolesTableCreator("roles", {
7793
- id: pgCore.varchar("id", {
7794
- length: 50
7795
- }).primaryKey(),
7796
- // 'admin', 'editor', 'viewer'
7797
- name: pgCore.varchar("name", {
7798
- length: 100
7799
- }).notNull(),
7800
- isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
7801
- defaultPermissions: pgCore.jsonb("default_permissions").$type(),
7802
- collectionPermissions: pgCore.jsonb("collection_permissions").$type()
7803
- });
7804
- const userRoles2 = rolesTableCreator("user_roles", {
7805
- userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7806
- onDelete: "cascade"
7807
- }),
7808
- roleId: pgCore.varchar("role_id", {
7809
- length: 50
7810
- }).notNull().references(() => roles2.id, {
7811
- onDelete: "cascade"
7812
- })
7813
- }, (table) => ({
7814
- pk: pgCore.primaryKey({
7815
- columns: [table.userId, table.roleId]
7816
- })
7817
- }));
7818
- const refreshTokens2 = rolesTableCreator("refresh_tokens", {
7755
+ const refreshTokens2 = tableCreator("refresh_tokens", {
7819
7756
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7820
7757
  userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7821
7758
  onDelete: "cascade"
@@ -7834,7 +7771,7 @@
7834
7771
  }, (table) => ({
7835
7772
  uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7836
7773
  }));
7837
- const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
7774
+ const passwordResetTokens2 = tableCreator("password_reset_tokens", {
7838
7775
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7839
7776
  userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7840
7777
  onDelete: "cascade"
@@ -7846,14 +7783,14 @@
7846
7783
  usedAt: pgCore.timestamp("used_at"),
7847
7784
  createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7848
7785
  });
7849
- const appConfig2 = rolesTableCreator("app_config", {
7786
+ const appConfig2 = tableCreator("app_config", {
7850
7787
  key: pgCore.varchar("key", {
7851
7788
  length: 100
7852
7789
  }).primaryKey(),
7853
7790
  value: pgCore.jsonb("value").notNull(),
7854
7791
  updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7855
7792
  });
7856
- const userIdentities2 = rolesTableCreator("user_identities", {
7793
+ const userIdentities2 = tableCreator("user_identities", {
7857
7794
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7858
7795
  userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7859
7796
  onDelete: "cascade"
@@ -7871,7 +7808,7 @@
7871
7808
  }, (table) => ({
7872
7809
  uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
7873
7810
  }));
7874
- const mfaFactors2 = rolesTableCreator("mfa_factors", {
7811
+ const mfaFactors2 = tableCreator("mfa_factors", {
7875
7812
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7876
7813
  userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7877
7814
  onDelete: "cascade"
@@ -7890,7 +7827,7 @@
7890
7827
  createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7891
7828
  updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7892
7829
  });
7893
- const mfaChallenges2 = rolesTableCreator("mfa_challenges", {
7830
+ const mfaChallenges2 = tableCreator("mfa_challenges", {
7894
7831
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7895
7832
  factorId: pgCore.uuid("factor_id").notNull().references(() => mfaFactors2.id, {
7896
7833
  onDelete: "cascade"
@@ -7902,7 +7839,7 @@
7902
7839
  }),
7903
7840
  expiresAt: pgCore.timestamp("expires_at").notNull()
7904
7841
  });
7905
- const recoveryCodes2 = rolesTableCreator("recovery_codes", {
7842
+ const recoveryCodes2 = tableCreator("recovery_codes", {
7906
7843
  id: pgCore.uuid("id").defaultRandom().primaryKey(),
7907
7844
  userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7908
7845
  onDelete: "cascade"
@@ -7914,11 +7851,8 @@
7914
7851
  createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7915
7852
  });
7916
7853
  return {
7917
- rolesSchema,
7918
7854
  usersSchema: usersSchema2,
7919
7855
  users: users2,
7920
- roles: roles2,
7921
- userRoles: userRoles2,
7922
7856
  refreshTokens: refreshTokens2,
7923
7857
  passwordResetTokens: passwordResetTokens2,
7924
7858
  appConfig: appConfig2,
@@ -7928,12 +7862,9 @@
7928
7862
  recoveryCodes: recoveryCodes2
7929
7863
  };
7930
7864
  }
7931
- const defaultAuthSchema = createAuthSchema("rebase", "rebase");
7932
- const rebaseSchema = defaultAuthSchema.rolesSchema;
7865
+ const defaultAuthSchema = createAuthSchema("rebase");
7933
7866
  const usersSchema = defaultAuthSchema.usersSchema;
7934
7867
  const users = defaultAuthSchema.users;
7935
- const roles = defaultAuthSchema.roles;
7936
- const userRoles = defaultAuthSchema.userRoles;
7937
7868
  const refreshTokens = defaultAuthSchema.refreshTokens;
7938
7869
  const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
7939
7870
  const appConfig = defaultAuthSchema.appConfig;
@@ -7944,30 +7875,12 @@
7944
7875
  const usersRelations = drizzleOrm.relations(users, ({
7945
7876
  many
7946
7877
  }) => ({
7947
- userRoles: many(userRoles),
7948
7878
  refreshTokens: many(refreshTokens),
7949
7879
  passwordResetTokens: many(passwordResetTokens),
7950
7880
  userIdentities: many(userIdentities),
7951
7881
  mfaFactors: many(mfaFactors),
7952
7882
  recoveryCodes: many(recoveryCodes)
7953
7883
  }));
7954
- const rolesRelations = drizzleOrm.relations(roles, ({
7955
- many
7956
- }) => ({
7957
- userRoles: many(userRoles)
7958
- }));
7959
- const userRolesRelations = drizzleOrm.relations(userRoles, ({
7960
- one
7961
- }) => ({
7962
- user: one(users, {
7963
- fields: [userRoles.userId],
7964
- references: [users.id]
7965
- }),
7966
- role: one(roles, {
7967
- fields: [userRoles.roleId],
7968
- references: [roles.id]
7969
- })
7970
- }));
7971
7884
  const refreshTokensRelations = drizzleOrm.relations(refreshTokens, ({
7972
7885
  one
7973
7886
  }) => ({
@@ -8074,6 +7987,8 @@
8074
7987
  columnDefinition = `${enumName}("${colName}")`;
8075
7988
  } else if ("isId" in stringProp && stringProp.isId === "uuid") {
8076
7989
  columnDefinition = `uuid("${colName}")`;
7990
+ } else if (stringProp.columnType === "uuid") {
7991
+ columnDefinition = `uuid("${colName}")`;
8077
7992
  } else if (stringProp.columnType === "text") {
8078
7993
  columnDefinition = `text("${colName}")`;
8079
7994
  } else if (stringProp.columnType === "char") {
@@ -8141,11 +8056,38 @@
8141
8056
  }
8142
8057
  break;
8143
8058
  }
8144
- case "map":
8059
+ case "map": {
8060
+ const mapProp = prop;
8061
+ if (mapProp.columnType === "json") {
8062
+ columnDefinition = `json("${colName}")`;
8063
+ } else {
8064
+ columnDefinition = `jsonb("${colName}")`;
8065
+ }
8066
+ break;
8067
+ }
8145
8068
  case "array": {
8146
- const arrayOrMapProp = prop;
8147
- if (arrayOrMapProp.columnType === "json") {
8069
+ const arrayProp = prop;
8070
+ let colType = arrayProp.columnType;
8071
+ if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {
8072
+ const ofProp = arrayProp.of;
8073
+ if (ofProp.type === "string") {
8074
+ colType = "text[]";
8075
+ } else if (ofProp.type === "number") {
8076
+ colType = ofProp.validation?.integer ? "integer[]" : "numeric[]";
8077
+ } else if (ofProp.type === "boolean") {
8078
+ colType = "boolean[]";
8079
+ }
8080
+ }
8081
+ if (colType === "json") {
8148
8082
  columnDefinition = `json("${colName}")`;
8083
+ } else if (colType === "text[]") {
8084
+ columnDefinition = `text("${colName}").array()`;
8085
+ } else if (colType === "integer[]") {
8086
+ columnDefinition = `integer("${colName}").array()`;
8087
+ } else if (colType === "boolean[]") {
8088
+ columnDefinition = `boolean("${colName}").array()`;
8089
+ } else if (colType === "numeric[]") {
8090
+ columnDefinition = `numeric("${colName}").array()`;
8149
8091
  } else {
8150
8092
  columnDefinition = `jsonb("${colName}")`;
8151
8093
  }
@@ -8229,8 +8171,8 @@
8229
8171
  const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
8230
8172
  return `sql\`${resolved}\``;
8231
8173
  };
8232
- const wrapWithRoleCheck = (clause, roles2) => {
8233
- const rolesArrayString = `ARRAY[${roles2.map((r) => `'${r}'`).join(",")}]`;
8174
+ const wrapWithRoleCheck = (clause, roles) => {
8175
+ const rolesArrayString = `ARRAY[${roles.map((r) => `'${r}'`).join(",")}]`;
8234
8176
  const roleCondition = `string_to_array(auth.roles(), ',') @> ${rolesArrayString}`;
8235
8177
  return `sql\`(${unwrapSql(clause)}) AND (${roleCondition})\``;
8236
8178
  };
@@ -8283,22 +8225,22 @@
8283
8225
  };
8284
8226
  const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
8285
8227
  const mode = rule.mode ?? "permissive";
8286
- const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
8228
+ const roles = rule.roles ? [...rule.roles].sort() : void 0;
8287
8229
  const needsUsing = operation !== "insert";
8288
8230
  const needsWithCheck = operation !== "select" && operation !== "delete";
8289
8231
  let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
8290
8232
  let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
8291
- if (roles2 && roles2.length > 0) {
8233
+ if (roles && roles.length > 0) {
8292
8234
  if (usingClause) {
8293
- usingClause = wrapWithRoleCheck(usingClause, roles2);
8235
+ usingClause = wrapWithRoleCheck(usingClause, roles);
8294
8236
  } else if (needsUsing) {
8295
- const rolesArrayString = `ARRAY[${roles2.map((r) => `'${r}'`).join(",")}]`;
8237
+ const rolesArrayString = `ARRAY[${roles.map((r) => `'${r}'`).join(",")}]`;
8296
8238
  usingClause = `sql\`string_to_array(auth.roles(), ',') @> ${rolesArrayString}\``;
8297
8239
  }
8298
8240
  if (withCheckClause) {
8299
- withCheckClause = wrapWithRoleCheck(withCheckClause, roles2);
8241
+ withCheckClause = wrapWithRoleCheck(withCheckClause, roles);
8300
8242
  } else if (needsWithCheck) {
8301
- const rolesArrayString = `ARRAY[${roles2.map((r) => `'${r}'`).join(",")}]`;
8243
+ const rolesArrayString = `ARRAY[${roles.map((r) => `'${r}'`).join(",")}]`;
8302
8244
  withCheckClause = `sql\`string_to_array(auth.roles(), ',') @> ${rolesArrayString}\``;
8303
8245
  }
8304
8246
  }
@@ -8686,7 +8628,6 @@ ${tableRelations.join(",\n")}
8686
8628
  if (!collections || !Array.isArray(collections)) {
8687
8629
  collections = [];
8688
8630
  }
8689
- collections = Array.from(new Map([defaultUsersCollection, ...collections].map((c) => [c.slug, c])).values());
8690
8631
  collections.sort((a, b) => a.slug.localeCompare(b.slug));
8691
8632
  const schemaContent = await generateSchema(collections);
8692
8633
  if (outputPath) {
@@ -10032,15 +9973,15 @@ ${tableRelations.join(",\n")}
10032
9973
  wsDebug("👤 [WebSocket Server] Processing FETCH_ROLES request");
10033
9974
  const delegate = await getScopedDelegate();
10034
9975
  const admin = delegate.admin;
10035
- let roles2 = [];
9976
+ let roles = [];
10036
9977
  if (isSQLAdmin(admin) && admin.fetchAvailableRoles) {
10037
- roles2 = await admin.fetchAvailableRoles();
9978
+ roles = await admin.fetchAvailableRoles();
10038
9979
  }
10039
- wsDebug(`👤 [WebSocket Server] Fetched ${roles2.length} roles.`);
9980
+ wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);
10040
9981
  const response = {
10041
9982
  type: "FETCH_ROLES_SUCCESS",
10042
9983
  payload: {
10043
- roles: roles2
9984
+ roles
10044
9985
  },
10045
9986
  requestId
10046
9987
  };
@@ -10294,85 +10235,35 @@ ${tableRelations.join(",\n")}
10294
10235
  return collection.relations.map((r) => r.relationName || r.localKey || "").filter(Boolean);
10295
10236
  }
10296
10237
  }
10297
- const DEFAULT_ROLES = [{
10298
- id: "admin",
10299
- name: "Admin",
10300
- is_admin: true,
10301
- default_permissions: {
10302
- read: true,
10303
- create: true,
10304
- edit: true,
10305
- delete: true
10306
- }
10307
- }, {
10308
- id: "editor",
10309
- name: "Editor",
10310
- is_admin: false,
10311
- default_permissions: {
10312
- read: true,
10313
- create: true,
10314
- edit: true,
10315
- delete: true
10316
- }
10317
- }, {
10318
- id: "viewer",
10319
- name: "Viewer",
10320
- is_admin: false,
10321
- default_permissions: {
10322
- read: true,
10323
- create: false,
10324
- edit: false,
10325
- delete: false
10326
- }
10327
- }];
10328
- async function ensureAuthTablesExist(db, registry) {
10238
+ async function ensureAuthTablesExist(db, collection) {
10329
10239
  serverCore.logger.info("🔍 Checking auth tables...");
10330
10240
  try {
10331
- let usersTableName = '"users"';
10241
+ let usersTableName = '"rebase"."users"';
10332
10242
  let userIdType = "TEXT";
10333
- let usersSchema2 = "public";
10334
- if (registry) {
10335
- const usersTable = registry.getTable("users");
10336
- if (usersTable) {
10337
- const {
10338
- getTableName: getTableName2
10339
- } = await import("drizzle-orm");
10340
- usersSchema2 = pgCore.getTableConfig(usersTable).schema || "public";
10341
- usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
10342
- if (usersTable.id) {
10343
- const col = usersTable.id;
10344
- const meta = getColumnMeta(col);
10345
- const columnType = meta.columnType;
10346
- if (columnType === "PgUUID") {
10347
- userIdType = "UUID";
10348
- } else if (columnType === "PgSerial" || columnType === "PgInteger") {
10349
- userIdType = "INTEGER";
10350
- } else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
10351
- userIdType = "BIGINT";
10352
- }
10243
+ let usersSchema2 = "rebase";
10244
+ if (collection) {
10245
+ const rawTable = "table" in collection && typeof collection.table === "string" ? collection.table : collection.slug;
10246
+ usersSchema2 = "schema" in collection && typeof collection.schema === "string" ? collection.schema : "public";
10247
+ usersTableName = usersSchema2 === "public" ? `"${rawTable}"` : `"${usersSchema2}"."${rawTable}"`;
10248
+ const idProp = collection.properties?.id;
10249
+ if (idProp) {
10250
+ const isId = "isId" in idProp ? idProp.isId : void 0;
10251
+ if (isId === "uuid") {
10252
+ userIdType = "UUID";
10253
+ } else if (isId === "autoincrement") {
10254
+ userIdType = "INTEGER";
10353
10255
  }
10354
10256
  }
10355
10257
  }
10356
- let rolesSchema = "rebase";
10357
- if (registry) {
10358
- const rolesTable = registry.getTable("roles");
10359
- if (rolesTable) {
10360
- rolesSchema = pgCore.getTableConfig(rolesTable).schema || "public";
10361
- }
10362
- }
10363
10258
  if (usersSchema2 !== "public") {
10364
10259
  await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(usersSchema2)}`);
10365
10260
  }
10366
- if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
10367
- await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(rolesSchema)}`);
10368
- }
10369
10261
  await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS rebase`);
10370
- const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
10371
- const rolesTableName = `"${rolesSchema}"."roles"`;
10372
- const userRolesTableName = `"${rolesSchema}"."user_roles"`;
10373
- const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
10374
- const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
10375
- const appConfigTableName = `"${rolesSchema}"."app_config"`;
10262
+ const authSchema = usersSchema2 === "public" ? "rebase" : usersSchema2;
10263
+ const userIdentitiesTable = `"${authSchema}"."user_identities"`;
10264
+ const refreshTokensTableName = `"${authSchema}"."refresh_tokens"`;
10265
+ const passwordResetTokensTableName = `"${authSchema}"."password_reset_tokens"`;
10266
+ const appConfigTableName = `"${authSchema}"."app_config"`;
10376
10267
  await db.execute(drizzleOrm.sql`
10377
10268
  CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userIdentitiesTable)} (
10378
10269
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
@@ -10389,27 +10280,6 @@ ${tableRelations.join(",\n")}
10389
10280
  CREATE INDEX IF NOT EXISTS idx_user_identities_user
10390
10281
  ON ${drizzleOrm.sql.raw(userIdentitiesTable)}(user_id)
10391
10282
  `);
10392
- await db.execute(drizzleOrm.sql`
10393
- CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(rolesTableName)} (
10394
- id TEXT PRIMARY KEY,
10395
- name TEXT NOT NULL,
10396
- is_admin BOOLEAN DEFAULT FALSE,
10397
- default_permissions JSONB,
10398
- collection_permissions JSONB,
10399
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
10400
- )
10401
- `);
10402
- await db.execute(drizzleOrm.sql`
10403
- CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userRolesTableName)} (
10404
- user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
10405
- role_id TEXT NOT NULL REFERENCES ${drizzleOrm.sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
10406
- PRIMARY KEY (user_id, role_id)
10407
- )
10408
- `);
10409
- await db.execute(drizzleOrm.sql`
10410
- CREATE INDEX IF NOT EXISTS idx_user_roles_user
10411
- ON ${drizzleOrm.sql.raw(userRolesTableName)}(user_id)
10412
- `);
10413
10283
  await db.execute(drizzleOrm.sql`
10414
10284
  CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(refreshTokensTableName)} (
10415
10285
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
@@ -10477,14 +10347,43 @@ ${tableRelations.join(",\n")}
10477
10347
  $$ LANGUAGE sql STABLE
10478
10348
  `);
10479
10349
  });
10480
- await seedDefaultRoles(db, rolesTableName);
10481
10350
  await db.execute(drizzleOrm.sql`
10482
10351
  ALTER TABLE ${drizzleOrm.sql.raw(usersTableName)}
10483
10352
  ADD COLUMN IF NOT EXISTS is_anonymous BOOLEAN DEFAULT FALSE
10484
10353
  `);
10485
- const mfaFactorsTableName = `"${rolesSchema}"."mfa_factors"`;
10486
- const mfaChallengesTableName = `"${rolesSchema}"."mfa_challenges"`;
10487
- const recoveryCodesTableName = `"${rolesSchema}"."recovery_codes"`;
10354
+ await db.execute(drizzleOrm.sql`
10355
+ ALTER TABLE ${drizzleOrm.sql.raw(usersTableName)}
10356
+ ADD COLUMN IF NOT EXISTS roles TEXT[] DEFAULT '{}' NOT NULL
10357
+ `);
10358
+ try {
10359
+ const legacyCheck = await db.execute(drizzleOrm.sql`
10360
+ SELECT EXISTS (
10361
+ SELECT 1 FROM information_schema.tables
10362
+ WHERE table_schema = 'rebase' AND table_name = 'user_roles'
10363
+ ) AS has_user_roles
10364
+ `);
10365
+ const hasLegacyTables = legacyCheck.rows[0].has_user_roles;
10366
+ if (hasLegacyTables) {
10367
+ serverCore.logger.info("🔄 Migrating roles from legacy user_roles table...");
10368
+ await db.execute(drizzleOrm.sql`
10369
+ UPDATE ${drizzleOrm.sql.raw(usersTableName)} u
10370
+ SET roles = COALESCE((
10371
+ SELECT array_agg(ur.role_id)
10372
+ FROM "rebase"."user_roles" ur
10373
+ WHERE ur.user_id = u.id
10374
+ ), '{}')
10375
+ WHERE u.roles = '{}' OR u.roles IS NULL
10376
+ `);
10377
+ await db.execute(drizzleOrm.sql`DROP TABLE IF EXISTS "rebase"."user_roles" CASCADE`);
10378
+ await db.execute(drizzleOrm.sql`DROP TABLE IF EXISTS "rebase"."roles" CASCADE`);
10379
+ serverCore.logger.info("✅ Legacy roles tables migrated and dropped");
10380
+ }
10381
+ } catch (migrationError) {
10382
+ serverCore.logger.warn(`⚠️ Legacy roles migration skipped: ${migrationError instanceof Error ? migrationError.message : String(migrationError)}`);
10383
+ }
10384
+ const mfaFactorsTableName = `"${authSchema}"."mfa_factors"`;
10385
+ const mfaChallengesTableName = `"${authSchema}"."mfa_challenges"`;
10386
+ const recoveryCodesTableName = `"${authSchema}"."recovery_codes"`;
10488
10387
  await db.execute(drizzleOrm.sql`
10489
10388
  CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(mfaFactorsTableName)} (
10490
10389
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
@@ -10536,28 +10435,6 @@ ${tableRelations.join(",\n")}
10536
10435
  serverCore.logger.warn("⚠️ Continuing without creating auth tables.");
10537
10436
  }
10538
10437
  }
10539
- async function seedDefaultRoles(db, rolesTableName) {
10540
- const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM ${drizzleOrm.sql.raw(rolesTableName)}`);
10541
- const count = parseInt(result.rows[0]?.count || "0", 10);
10542
- if (count > 0) {
10543
- serverCore.logger.info(`📋 Found ${count} existing roles`);
10544
- return;
10545
- }
10546
- serverCore.logger.info("🌱 Seeding default roles...");
10547
- for (const role of DEFAULT_ROLES) {
10548
- await db.execute(drizzleOrm.sql`
10549
- INSERT INTO ${drizzleOrm.sql.raw(rolesTableName)} (id, name, is_admin, default_permissions)
10550
- VALUES (
10551
- ${role.id},
10552
- ${role.name},
10553
- ${role.is_admin},
10554
- ${JSON.stringify(role.default_permissions)}::jsonb
10555
- )
10556
- ON CONFLICT (id) DO NOTHING
10557
- `);
10558
- }
10559
- serverCore.logger.info("✅ Default roles created: admin, editor, viewer");
10560
- }
10561
10438
  function getColumnKey(table, ...keys2) {
10562
10439
  if (!table) return void 0;
10563
10440
  for (const key of keys2) {
@@ -10577,24 +10454,18 @@ ${tableRelations.join(",\n")}
10577
10454
  class UserService {
10578
10455
  constructor(db, tableOrTables) {
10579
10456
  this.db = db;
10580
- if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
10457
+ if (tableOrTables && tableOrTables.users) {
10581
10458
  const tables = tableOrTables;
10582
10459
  this.usersTable = tables.users || users;
10583
10460
  this.userIdentitiesTable = tables.userIdentities || userIdentities;
10584
- this.userRolesTable = tables.userRoles || userRoles;
10585
- this.rolesTable = tables.roles || roles;
10586
10461
  } else {
10587
10462
  const table = tableOrTables;
10588
10463
  this.usersTable = table || users;
10589
10464
  this.userIdentitiesTable = userIdentities;
10590
- this.userRolesTable = userRoles;
10591
- this.rolesTable = roles;
10592
10465
  }
10593
10466
  }
10594
10467
  usersTable;
10595
10468
  userIdentitiesTable;
10596
- userRolesTable;
10597
- rolesTable;
10598
10469
  getQualifiedUsersTableName() {
10599
10470
  const name = drizzleOrm.getTableName(this.usersTable);
10600
10471
  const schema = pgCore.getTableConfig(this.usersTable).schema || "public";
@@ -10616,7 +10487,7 @@ ${tableRelations.join(",\n")}
10616
10487
  const metadata = {
10617
10488
  ...row.metadata || {}
10618
10489
  };
10619
- const knownKeys = /* @__PURE__ */ new Set(["id", "uid", "email", "password_hash", "passwordHash", "display_name", "displayName", "photo_url", "photoUrl", "photoURL", "email_verified", "emailVerified", "email_verification_token", "emailVerificationToken", "email_verification_sent_at", "emailVerificationSentAt", "is_anonymous", "isAnonymous", "created_at", "createdAt", "updated_at", "updatedAt", "metadata"]);
10490
+ const knownKeys = /* @__PURE__ */ new Set(["id", "uid", "email", "password_hash", "passwordHash", "display_name", "displayName", "photo_url", "photoUrl", "photoURL", "email_verified", "emailVerified", "email_verification_token", "emailVerificationToken", "email_verification_sent_at", "emailVerificationSentAt", "is_anonymous", "isAnonymous", "roles", "created_at", "createdAt", "updated_at", "updatedAt", "metadata"]);
10620
10491
  for (const [key, val] of Object.entries(row)) {
10621
10492
  if (!knownKeys.has(key)) {
10622
10493
  const camelKey = camelCase(key);
@@ -10767,19 +10638,18 @@ ${tableRelations.join(",\n")}
10767
10638
  const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
10768
10639
  const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
10769
10640
  const idCol = getColumn(this.usersTable, "id");
10770
- const idColumn = idCol ? idCol.name : "id";
10641
+ idCol ? idCol.name : "id";
10771
10642
  const usersTableName = this.getQualifiedUsersTableName();
10772
- const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
10773
10643
  const conditions = [];
10774
10644
  if (roleId) {
10775
- conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
10645
+ conditions.push(drizzleOrm.sql`${roleId} = ANY(${drizzleOrm.sql.raw(usersTableName)}.roles)`);
10776
10646
  }
10777
10647
  if (search) {
10778
10648
  const pattern = `%${search}%`;
10779
10649
  conditions.push(drizzleOrm.sql`(${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(emailColumn)} ILIKE ${pattern} OR ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(displayNameColumn)} ILIKE ${pattern})`);
10780
10650
  }
10781
10651
  const whereClause = conditions.length > 0 ? drizzleOrm.sql`WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}` : drizzleOrm.sql``;
10782
- const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)}) DESC, ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
10652
+ const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY array_length(${drizzleOrm.sql.raw(usersTableName)}.roles, 1) DESC NULLS LAST, ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
10783
10653
  const countResult = await this.db.execute(drizzleOrm.sql`
10784
10654
  SELECT count(*)::int as total FROM ${drizzleOrm.sql.raw(usersTableName)}
10785
10655
  ${whereClause}
@@ -10853,54 +10723,57 @@ ${tableRelations.join(",\n")}
10853
10723
  return row ? this.mapRowToUser(row) : null;
10854
10724
  }
10855
10725
  /**
10856
- * Get roles for a user from database
10726
+ * Get roles for a user from database (inline TEXT[] column)
10857
10727
  */
10858
10728
  async getUserRoles(userId) {
10859
- const rolesSchema = pgCore.getTableConfig(this.rolesTable).schema || "public";
10729
+ const usersTableName = this.getQualifiedUsersTableName();
10860
10730
  const result = await this.db.execute(drizzleOrm.sql`
10861
- SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions
10862
- FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."roles"`)} r
10863
- INNER JOIN ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
10864
- WHERE ur.user_id = ${userId}
10731
+ SELECT roles FROM ${drizzleOrm.sql.raw(usersTableName)} WHERE id = ${userId}
10865
10732
  `);
10866
- return result.rows.map((row) => ({
10867
- id: row.id,
10868
- name: row.name,
10869
- isAdmin: row.is_admin,
10870
- defaultPermissions: row.default_permissions,
10871
- collectionPermissions: row.collection_permissions
10733
+ if (result.rows.length === 0) return [];
10734
+ const row = result.rows[0];
10735
+ const roleIds = row.roles ?? [];
10736
+ return roleIds.map((id) => ({
10737
+ id,
10738
+ name: id,
10739
+ isAdmin: id === "admin",
10740
+ defaultPermissions: null,
10741
+ collectionPermissions: null
10872
10742
  }));
10873
10743
  }
10874
10744
  /**
10875
10745
  * Get role IDs for a user
10876
10746
  */
10877
10747
  async getUserRoleIds(userId) {
10878
- const roles2 = await this.getUserRoles(userId);
10879
- return roles2.map((r) => r.id);
10748
+ const usersTableName = this.getQualifiedUsersTableName();
10749
+ const result = await this.db.execute(drizzleOrm.sql`
10750
+ SELECT roles FROM ${drizzleOrm.sql.raw(usersTableName)} WHERE id = ${userId}
10751
+ `);
10752
+ if (result.rows.length === 0) return [];
10753
+ const row = result.rows[0];
10754
+ return row.roles ?? [];
10880
10755
  }
10881
10756
  /**
10882
- * Set roles for a user
10757
+ * Set roles for a user (replaces existing roles)
10883
10758
  */
10884
10759
  async setUserRoles(userId, roleIds) {
10885
- const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
10886
- await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
10887
- for (const roleId of roleIds) {
10888
- await this.db.execute(drizzleOrm.sql`
10889
- INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
10890
- VALUES (${userId}, ${roleId})
10891
- ON CONFLICT DO NOTHING
10892
- `);
10893
- }
10760
+ const usersTableName = this.getQualifiedUsersTableName();
10761
+ const rolesArray = `{${roleIds.join(",")}}`;
10762
+ await this.db.execute(drizzleOrm.sql`
10763
+ UPDATE ${drizzleOrm.sql.raw(usersTableName)}
10764
+ SET roles = ${rolesArray}::text[], updated_at = NOW()
10765
+ WHERE id = ${userId}
10766
+ `);
10894
10767
  }
10895
10768
  /**
10896
- * Assign a specific role to new user
10769
+ * Assign a specific role to new user (appends if not present)
10897
10770
  */
10898
10771
  async assignDefaultRole(userId, roleId) {
10899
- const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
10772
+ const usersTableName = this.getQualifiedUsersTableName();
10900
10773
  await this.db.execute(drizzleOrm.sql`
10901
- INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
10902
- VALUES (${userId}, ${roleId})
10903
- ON CONFLICT DO NOTHING
10774
+ UPDATE ${drizzleOrm.sql.raw(usersTableName)}
10775
+ SET roles = array_append(roles, ${roleId}), updated_at = NOW()
10776
+ WHERE id = ${userId} AND NOT (${roleId} = ANY(roles))
10904
10777
  `);
10905
10778
  }
10906
10779
  /**
@@ -10909,101 +10782,12 @@ ${tableRelations.join(",\n")}
10909
10782
  async getUserWithRoles(userId) {
10910
10783
  const user = await this.getUserById(userId);
10911
10784
  if (!user) return null;
10912
- const roles2 = await this.getUserRoles(userId);
10785
+ const roles = await this.getUserRoles(userId);
10913
10786
  return {
10914
10787
  user,
10915
- roles: roles2
10916
- };
10917
- }
10918
- }
10919
- class RoleService {
10920
- constructor(db, tableOrTables) {
10921
- this.db = db;
10922
- if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
10923
- this.rolesTable = tableOrTables.roles || roles;
10924
- } else {
10925
- this.rolesTable = tableOrTables || roles;
10926
- }
10927
- }
10928
- rolesTable;
10929
- getQualifiedRolesTableName() {
10930
- const name = drizzleOrm.getTableName(this.rolesTable);
10931
- const schema = pgCore.getTableConfig(this.rolesTable).schema || "public";
10932
- return `"${schema}"."${name}"`;
10933
- }
10934
- async getRoleById(id) {
10935
- const tableName = this.getQualifiedRolesTableName();
10936
- const result = await this.db.execute(drizzleOrm.sql`
10937
- SELECT id, name, is_admin, default_permissions, collection_permissions
10938
- FROM ${drizzleOrm.sql.raw(tableName)}
10939
- WHERE id = ${id}
10940
- `);
10941
- if (result.rows.length === 0) return null;
10942
- const row = result.rows[0];
10943
- return {
10944
- id: row.id,
10945
- name: row.name,
10946
- isAdmin: row.is_admin,
10947
- defaultPermissions: row.default_permissions,
10948
- collectionPermissions: row.collection_permissions
10949
- };
10950
- }
10951
- async listRoles() {
10952
- const tableName = this.getQualifiedRolesTableName();
10953
- const result = await this.db.execute(drizzleOrm.sql`
10954
- SELECT id, name, is_admin, default_permissions, collection_permissions
10955
- FROM ${drizzleOrm.sql.raw(tableName)}
10956
- ORDER BY name
10957
- `);
10958
- return result.rows.map((row) => ({
10959
- id: row.id,
10960
- name: row.name,
10961
- isAdmin: row.is_admin,
10962
- defaultPermissions: row.default_permissions,
10963
- collectionPermissions: row.collection_permissions
10964
- }));
10965
- }
10966
- async createRole(data) {
10967
- const tableName = this.getQualifiedRolesTableName();
10968
- const result = await this.db.execute(drizzleOrm.sql`
10969
- INSERT INTO ${drizzleOrm.sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions)
10970
- VALUES (
10971
- ${data.id},
10972
- ${data.name},
10973
- ${data.isAdmin ?? false},
10974
- ${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : null}::jsonb,
10975
- ${data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null}::jsonb
10976
- )
10977
- RETURNING id, name, is_admin, default_permissions, collection_permissions
10978
- `);
10979
- const row = result.rows[0];
10980
- return {
10981
- id: row.id,
10982
- name: row.name,
10983
- isAdmin: row.is_admin,
10984
- defaultPermissions: row.default_permissions,
10985
- collectionPermissions: row.collection_permissions
10788
+ roles
10986
10789
  };
10987
10790
  }
10988
- async updateRole(id, data) {
10989
- const existing = await this.getRoleById(id);
10990
- if (!existing) return null;
10991
- const tableName = this.getQualifiedRolesTableName();
10992
- await this.db.execute(drizzleOrm.sql`
10993
- UPDATE ${drizzleOrm.sql.raw(tableName)}
10994
- SET
10995
- name = ${data.name ?? existing.name},
10996
- is_admin = ${data.isAdmin ?? existing.isAdmin},
10997
- default_permissions = ${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : JSON.stringify(existing.defaultPermissions)}::jsonb,
10998
- collection_permissions = ${data.collectionPermissions !== void 0 ? data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null : existing.collectionPermissions ? JSON.stringify(existing.collectionPermissions) : null}::jsonb
10999
- WHERE id = ${id}
11000
- `);
11001
- return this.getRoleById(id);
11002
- }
11003
- async deleteRole(id) {
11004
- const tableName = this.getQualifiedRolesTableName();
11005
- await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(tableName)} WHERE id = ${id}`);
11006
- }
11007
10791
  }
11008
10792
  class RefreshTokenService {
11009
10793
  constructor(db, tableOrTables) {
@@ -11199,11 +10983,9 @@ ${tableRelations.join(",\n")}
11199
10983
  constructor(db, tableOrTables) {
11200
10984
  this.db = db;
11201
10985
  this.userService = new UserService(db, tableOrTables);
11202
- this.roleService = new RoleService(db, tableOrTables);
11203
10986
  this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
11204
10987
  }
11205
10988
  userService;
11206
- roleService;
11207
10989
  tokenRepository;
11208
10990
  // User operations (delegate to UserService)
11209
10991
  async createUser(data) {
@@ -11263,25 +11045,56 @@ ${tableRelations.join(",\n")}
11263
11045
  async getUserWithRoles(userId) {
11264
11046
  return this.userService.getUserWithRoles(userId);
11265
11047
  }
11266
- // Role operations (delegate to RoleService)
11048
+ // Role operations (roles are inline on users, synthesized from string IDs)
11267
11049
  async getRoleById(id) {
11268
- return this.roleService.getRoleById(id);
11050
+ return {
11051
+ id,
11052
+ name: id,
11053
+ isAdmin: id === "admin",
11054
+ defaultPermissions: null,
11055
+ collectionPermissions: null
11056
+ };
11269
11057
  }
11270
11058
  async listRoles() {
11271
- return this.roleService.listRoles();
11059
+ return [{
11060
+ id: "admin",
11061
+ name: "Admin",
11062
+ isAdmin: true,
11063
+ defaultPermissions: null,
11064
+ collectionPermissions: null
11065
+ }, {
11066
+ id: "editor",
11067
+ name: "Editor",
11068
+ isAdmin: false,
11069
+ defaultPermissions: null,
11070
+ collectionPermissions: null
11071
+ }, {
11072
+ id: "viewer",
11073
+ name: "Viewer",
11074
+ isAdmin: false,
11075
+ defaultPermissions: null,
11076
+ collectionPermissions: null
11077
+ }];
11078
+ }
11079
+ async createRole(_data) {
11080
+ return {
11081
+ id: _data.id,
11082
+ name: _data.name,
11083
+ isAdmin: _data.isAdmin ?? false,
11084
+ defaultPermissions: _data.defaultPermissions ?? null,
11085
+ collectionPermissions: _data.collectionPermissions ?? null
11086
+ };
11272
11087
  }
11273
- async createRole(data) {
11274
- return this.roleService.createRole({
11275
- ...data,
11088
+ async updateRole(id, data) {
11089
+ return {
11090
+ id,
11091
+ name: data.name ?? id,
11092
+ isAdmin: data.isAdmin ?? id === "admin",
11276
11093
  defaultPermissions: data.defaultPermissions ?? null,
11277
11094
  collectionPermissions: data.collectionPermissions ?? null
11278
- });
11279
- }
11280
- async updateRole(id, data) {
11281
- return this.roleService.updateRole(id, data);
11095
+ };
11282
11096
  }
11283
- async deleteRole(id) {
11284
- await this.roleService.deleteRole(id);
11097
+ async deleteRole(_id) {
11285
11098
  }
11286
11099
  // Token operations (delegate to PostgresTokenRepository)
11287
11100
  async createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
@@ -11831,34 +11644,27 @@ ${tableRelations.join(",\n")}
11831
11644
  const internals = driverResult.internals;
11832
11645
  const db = internals.db;
11833
11646
  const registry = internals.registry;
11834
- await ensureAuthTablesExist(db, registry);
11647
+ const authCollection = authConfig.collection;
11648
+ await ensureAuthTablesExist(db, authCollection);
11835
11649
  let emailService;
11836
11650
  if (authConfig.email) {
11837
11651
  emailService = serverCore.createEmailService(authConfig.email);
11838
11652
  }
11839
- const customUsersTable = registry?.getTable("users");
11840
- const customRolesTable = registry?.getTable("roles");
11653
+ const tableName = authCollection ? "table" in authCollection && typeof authCollection.table === "string" ? authCollection.table : authCollection.slug : void 0;
11654
+ const usersTable = tableName ? registry.getTable(tableName) : void 0;
11841
11655
  let usersSchemaName = "rebase";
11842
- let rolesSchemaName = "rebase";
11843
- if (customUsersTable) {
11844
- usersSchemaName = pgCore.getTableConfig(customUsersTable).schema || "public";
11656
+ if (authCollection && "schema" in authCollection && typeof authCollection.schema === "string") {
11657
+ usersSchemaName = authCollection.schema;
11845
11658
  }
11846
- if (customRolesTable) {
11847
- rolesSchemaName = pgCore.getTableConfig(customRolesTable).schema || "public";
11848
- }
11849
- const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
11850
- if (customUsersTable) {
11851
- authTables.users = customUsersTable;
11852
- }
11853
- if (customRolesTable) {
11854
- authTables.roles = customRolesTable;
11659
+ const authTables = createAuthSchema(usersSchemaName);
11660
+ if (usersTable) {
11661
+ authTables.users = usersTable;
11855
11662
  }
11856
11663
  const userService = new UserService(db, authTables);
11857
- const roleService = new RoleService(db, authTables);
11858
11664
  const authRepository = new PostgresAuthRepository(db, authTables);
11859
11665
  return {
11860
11666
  userService,
11861
- roleService,
11667
+ roleService: userService,
11862
11668
  emailService,
11863
11669
  authRepository
11864
11670
  };
@@ -11962,17 +11768,12 @@ ${tableRelations.join(",\n")}
11962
11768
  exports2.mfaFactorsRelations = mfaFactorsRelations;
11963
11769
  exports2.passwordResetTokens = passwordResetTokens;
11964
11770
  exports2.passwordResetTokensRelations = passwordResetTokensRelations;
11965
- exports2.rebaseSchema = rebaseSchema;
11966
11771
  exports2.recoveryCodes = recoveryCodes;
11967
11772
  exports2.recoveryCodesRelations = recoveryCodesRelations;
11968
11773
  exports2.refreshTokens = refreshTokens;
11969
11774
  exports2.refreshTokensRelations = refreshTokensRelations;
11970
- exports2.roles = roles;
11971
- exports2.rolesRelations = rolesRelations;
11972
11775
  exports2.userIdentities = userIdentities;
11973
11776
  exports2.userIdentitiesRelations = userIdentitiesRelations;
11974
- exports2.userRoles = userRoles;
11975
- exports2.userRolesRelations = userRolesRelations;
11976
11777
  exports2.users = users;
11977
11778
  exports2.usersRelations = usersRelations;
11978
11779
  exports2.usersSchema = usersSchema;