@rebasepro/server-postgresql 0.2.4 → 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 (37) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +5 -8
  2. package/dist/index.es.js +286 -365
  3. package/dist/index.es.js.map +1 -1
  4. package/dist/index.umd.js +283 -362
  5. package/dist/index.umd.js.map +1 -1
  6. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -0
  7. package/dist/server-postgresql/src/auth/services.d.ts +6 -31
  8. package/dist/server-postgresql/src/schema/auth-schema.d.ts +87 -340
  9. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
  10. package/dist/server-postgresql/src/services/entityService.d.ts +4 -0
  11. package/dist/types/src/controllers/auth.d.ts +2 -2
  12. package/dist/types/src/controllers/client.d.ts +25 -40
  13. package/dist/types/src/controllers/data.d.ts +4 -0
  14. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  15. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  16. package/dist/types/src/types/backend.d.ts +2 -2
  17. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  18. package/dist/types/src/types/properties.d.ts +7 -5
  19. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  20. package/dist/types/src/users/index.d.ts +0 -1
  21. package/dist/types/src/users/user.d.ts +0 -1
  22. package/package.json +6 -6
  23. package/src/PostgresBackendDriver.ts +10 -0
  24. package/src/PostgresBootstrapper.ts +3 -12
  25. package/src/auth/ensure-tables.ts +52 -101
  26. package/src/auth/services.ts +71 -170
  27. package/src/schema/auth-schema.ts +13 -69
  28. package/src/schema/doctor.ts +44 -3
  29. package/src/schema/generate-drizzle-schema-logic.ts +33 -3
  30. package/src/schema/introspect-db-logic.ts +7 -0
  31. package/src/services/EntityPersistService.ts +9 -0
  32. package/src/services/entityService.ts +7 -0
  33. package/test/auth-services.test.ts +7 -150
  34. package/test/doctor.test.ts +6 -2
  35. package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
  36. package/dist/types/src/users/roles.d.ts +0 -14
  37. package/src/schema/default-collections.ts +0 -69
@@ -1,15 +1,14 @@
1
- import { pgSchema, pgTable, varchar, uuid, timestamp, boolean, jsonb, primaryKey, unique } from "drizzle-orm/pg-core";
1
+ import { pgSchema, pgTable, varchar, uuid, timestamp, boolean, jsonb, text, unique } from "drizzle-orm/pg-core";
2
2
  import { relations } from "drizzle-orm";
3
3
 
4
4
  /**
5
5
  * Factory function to dynamically create the auth tables bound to the specified schema names.
6
6
  */
7
- export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchemaName: string = "rebase") {
8
- const rolesSchema = rolesSchemaName === "public" ? null : pgSchema(rolesSchemaName);
7
+ export function createAuthSchema(usersSchemaName: string = "rebase") {
9
8
  const usersSchema = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
10
9
 
11
- const rolesTableCreator = (rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgTable) as typeof pgTable;
12
- const usersTableCreator = (usersSchema ? usersSchema.table.bind(usersSchema) : pgTable) as typeof pgTable;
10
+ const tableCreator = (usersSchema ? usersSchema.table.bind(usersSchema) : pgTable) as typeof pgTable;
11
+ const usersTableCreator = tableCreator;
13
12
 
14
13
  /**
15
14
  * Users table - stores both email/password and OAuth users
@@ -24,48 +23,18 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
24
23
  emailVerificationToken: varchar("email_verification_token", { length: 255 }),
25
24
  emailVerificationSentAt: timestamp("email_verification_sent_at"),
26
25
  isAnonymous: boolean("is_anonymous").default(false).notNull(),
26
+ roles: text("roles").array().default([]).notNull(),
27
27
  metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}).notNull(),
28
28
  createdAt: timestamp("created_at").defaultNow().notNull(),
29
29
  updatedAt: timestamp("updated_at").defaultNow().notNull()
30
30
  });
31
31
 
32
- /**
33
- * Roles table - defines permission sets
34
- */
35
- const roles = rolesTableCreator("roles", {
36
- id: varchar("id", { length: 50 }).primaryKey(), // 'admin', 'editor', 'viewer'
37
- name: varchar("name", { length: 100 }).notNull(),
38
- isAdmin: boolean("is_admin").default(false).notNull(),
39
- defaultPermissions: jsonb("default_permissions").$type<{
40
- read?: boolean;
41
- create?: boolean;
42
- edit?: boolean;
43
- delete?: boolean;
44
- }>(),
45
- collectionPermissions: jsonb("collection_permissions").$type<
46
- Record<string, {
47
- read?: boolean;
48
- create?: boolean;
49
- edit?: boolean;
50
- delete?: boolean;
51
- }>
52
- >()
53
- });
54
32
 
55
- /**
56
- * User-Role junction table
57
- */
58
- const userRoles = rolesTableCreator("user_roles", {
59
- userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
60
- roleId: varchar("role_id", { length: 50 }).notNull().references(() => roles.id, { onDelete: "cascade" })
61
- }, (table) => ({
62
- pk: primaryKey({ columns: [table.userId, table.roleId] })
63
- }));
64
33
 
65
34
  /**
66
35
  * Refresh tokens for long-lived sessions
67
36
  */
68
- const refreshTokens = rolesTableCreator("refresh_tokens", {
37
+ const refreshTokens = tableCreator("refresh_tokens", {
69
38
  id: uuid("id").defaultRandom().primaryKey(),
70
39
  userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
71
40
  tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
@@ -80,7 +49,7 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
80
49
  /**
81
50
  * Password reset tokens for forgot password flow
82
51
  */
83
- const passwordResetTokens = rolesTableCreator("password_reset_tokens", {
52
+ const passwordResetTokens = tableCreator("password_reset_tokens", {
84
53
  id: uuid("id").defaultRandom().primaryKey(),
85
54
  userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
86
55
  tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
@@ -92,7 +61,7 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
92
61
  /**
93
62
  * App config - key/value store for custom settings
94
63
  */
95
- const appConfig = rolesTableCreator("app_config", {
64
+ const appConfig = tableCreator("app_config", {
96
65
  key: varchar("key", { length: 100 }).primaryKey(),
97
66
  value: jsonb("value").notNull(),
98
67
  updatedAt: timestamp("updated_at").defaultNow().notNull()
@@ -101,7 +70,7 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
101
70
  /**
102
71
  * User identities - maps external OAuth profiles back to local users
103
72
  */
104
- const userIdentities = rolesTableCreator("user_identities", {
73
+ const userIdentities = tableCreator("user_identities", {
105
74
  id: uuid("id").defaultRandom().primaryKey(),
106
75
  userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
107
76
  provider: varchar("provider", { length: 50 }).notNull(), // e.g. 'google', 'linkedin'
@@ -116,7 +85,7 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
116
85
  /**
117
86
  * MFA factors table - stores enrolled MFA methods
118
87
  */
119
- const mfaFactors = rolesTableCreator("mfa_factors", {
88
+ const mfaFactors = tableCreator("mfa_factors", {
120
89
  id: uuid("id").defaultRandom().primaryKey(),
121
90
  userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
122
91
  factorType: varchar("factor_type", { length: 20 }).notNull(), // 'totp'
@@ -130,7 +99,7 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
130
99
  /**
131
100
  * MFA challenges table - tracks active MFA verification attempts
132
101
  */
133
- const mfaChallenges = rolesTableCreator("mfa_challenges", {
102
+ const mfaChallenges = tableCreator("mfa_challenges", {
134
103
  id: uuid("id").defaultRandom().primaryKey(),
135
104
  factorId: uuid("factor_id").notNull().references(() => mfaFactors.id, { onDelete: "cascade" }),
136
105
  createdAt: timestamp("created_at").defaultNow().notNull(),
@@ -142,7 +111,7 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
142
111
  /**
143
112
  * Recovery codes table - backup codes for MFA
144
113
  */
145
- const recoveryCodes = rolesTableCreator("recovery_codes", {
114
+ const recoveryCodes = tableCreator("recovery_codes", {
146
115
  id: uuid("id").defaultRandom().primaryKey(),
147
116
  userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
148
117
  codeHash: varchar("code_hash", { length: 255 }).notNull(),
@@ -151,11 +120,8 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
151
120
  });
152
121
 
153
122
  return {
154
- rolesSchema,
155
123
  usersSchema,
156
124
  users,
157
- roles,
158
- userRoles,
159
125
  refreshTokens,
160
126
  passwordResetTokens,
161
127
  appConfig,
@@ -167,14 +133,11 @@ export function createAuthSchema(rolesSchemaName: string = "rebase", usersSchema
167
133
  }
168
134
 
169
135
  // Instantiate default schema and tables using the default "rebase" schema
170
- const defaultAuthSchema = createAuthSchema("rebase", "rebase");
136
+ const defaultAuthSchema = createAuthSchema("rebase");
171
137
 
172
- export const rebaseSchema = defaultAuthSchema.rolesSchema;
173
138
  export const usersSchema = defaultAuthSchema.usersSchema;
174
139
 
175
140
  export const users = defaultAuthSchema.users;
176
- export const roles = defaultAuthSchema.roles;
177
- export const userRoles = defaultAuthSchema.userRoles;
178
141
  export const refreshTokens = defaultAuthSchema.refreshTokens;
179
142
  export const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
180
143
  export const appConfig = defaultAuthSchema.appConfig;
@@ -185,7 +148,6 @@ export const recoveryCodes = defaultAuthSchema.recoveryCodes;
185
148
 
186
149
  // Relations
187
150
  export const usersRelations = relations(users, ({ many }) => ({
188
- userRoles: many(userRoles),
189
151
  refreshTokens: many(refreshTokens),
190
152
  passwordResetTokens: many(passwordResetTokens),
191
153
  userIdentities: many(userIdentities),
@@ -193,21 +155,6 @@ export const usersRelations = relations(users, ({ many }) => ({
193
155
  recoveryCodes: many(recoveryCodes)
194
156
  }));
195
157
 
196
- export const rolesRelations = relations(roles, ({ many }) => ({
197
- userRoles: many(userRoles)
198
- }));
199
-
200
- export const userRolesRelations = relations(userRoles, ({ one }) => ({
201
- user: one(users, {
202
- fields: [userRoles.userId],
203
- references: [users.id]
204
- }),
205
- role: one(roles, {
206
- fields: [userRoles.roleId],
207
- references: [roles.id]
208
- })
209
- }));
210
-
211
158
  export const refreshTokensRelations = relations(refreshTokens, ({ one }) => ({
212
159
  user: one(users, {
213
160
  fields: [refreshTokens.userId],
@@ -254,9 +201,6 @@ export const recoveryCodesRelations = relations(recoveryCodes, ({ one }) => ({
254
201
  // Type exports
255
202
  export type User = typeof users.$inferSelect;
256
203
  export type NewUser = typeof users.$inferInsert;
257
- export type Role = typeof roles.$inferSelect;
258
- export type NewRole = typeof roles.$inferInsert;
259
- export type UserRole = typeof userRoles.$inferSelect;
260
204
  export type RefreshToken = typeof refreshTokens.$inferSelect;
261
205
  export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
262
206
  export type AppConfig = typeof appConfig.$inferSelect;
@@ -84,10 +84,28 @@ export function getExpectedColumnType(prop: Property): string | null {
84
84
  if (dp.columnType === "time") return "time without time zone";
85
85
  return "timestamp with time zone";
86
86
  }
87
- case "map":
88
87
  case "array": {
89
- const ap = prop as ArrayProperty | MapProperty;
90
- if (ap.columnType === "json") return "json";
88
+ const ap = prop as ArrayProperty;
89
+ let colType = ap.columnType;
90
+ if (!colType && ap.of && !Array.isArray(ap.of)) {
91
+ const ofProp = ap.of as Property;
92
+ if (ofProp.type === "string") {
93
+ colType = "text[]";
94
+ } else if (ofProp.type === "number") {
95
+ colType = ofProp.validation?.integer ? "integer[]" : "numeric[]";
96
+ } else if (ofProp.type === "boolean") {
97
+ colType = "boolean[]";
98
+ }
99
+ }
100
+
101
+ if (colType === "json") return "json";
102
+ if (colType === "jsonb") return "jsonb";
103
+ if (colType && colType.endsWith("[]")) return "ARRAY";
104
+ return "jsonb";
105
+ }
106
+ case "map": {
107
+ const mp = prop as MapProperty;
108
+ if (mp.columnType === "json") return "json";
91
109
  return "jsonb";
92
110
  }
93
111
  case "relation":
@@ -485,6 +503,29 @@ export async function checkCollectionsVsDatabase(
485
503
  if (prop.type === "vector" && dbCol.udt_name !== "vector") {
486
504
  isMismatch = true;
487
505
  }
506
+ if (prop.type === "array") {
507
+ const ap = prop as ArrayProperty;
508
+ let expectedColType = ap.columnType;
509
+ if (!expectedColType && ap.of && !Array.isArray(ap.of)) {
510
+ const ofProp = ap.of as Property;
511
+ if (ofProp.type === "string") expectedColType = "text[]";
512
+ else if (ofProp.type === "number") expectedColType = ofProp.validation?.integer ? "integer[]" : "numeric[]";
513
+ else if (ofProp.type === "boolean") expectedColType = "boolean[]";
514
+ }
515
+ if (expectedColType && expectedColType.endsWith("[]")) {
516
+ if (actualType !== "ARRAY") {
517
+ isMismatch = true;
518
+ } else {
519
+ const expectedUdt = expectedColType === "text[]" ? "_text" :
520
+ expectedColType === "integer[]" ? "_int4" :
521
+ expectedColType === "boolean[]" ? "_bool" :
522
+ expectedColType === "numeric[]" ? "_numeric" : "";
523
+ if (expectedUdt && dbCol.udt_name !== expectedUdt) {
524
+ isMismatch = true;
525
+ }
526
+ }
527
+ }
528
+ }
488
529
  if (isMismatch) {
489
530
  issues.push({
490
531
  severity: "warning",
@@ -70,6 +70,8 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
70
70
  columnDefinition = `${enumName}("${colName}")`;
71
71
  } else if ("isId" in stringProp && stringProp.isId === "uuid") {
72
72
  columnDefinition = `uuid("${colName}")`;
73
+ } else if (stringProp.columnType === "uuid") {
74
+ columnDefinition = `uuid("${colName}")`;
73
75
  } else if (stringProp.columnType === "text") {
74
76
  columnDefinition = `text("${colName}")`;
75
77
  } else if (stringProp.columnType === "char") {
@@ -145,11 +147,39 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: EntityCo
145
147
  }
146
148
  break;
147
149
  }
148
- case "map":
150
+ case "map": {
151
+ const mapProp = prop as MapProperty;
152
+ if (mapProp.columnType === "json") {
153
+ columnDefinition = `json("${colName}")`;
154
+ } else {
155
+ columnDefinition = `jsonb("${colName}")`;
156
+ }
157
+ break;
158
+ }
149
159
  case "array": {
150
- const arrayOrMapProp = prop as ArrayProperty | MapProperty;
151
- if (arrayOrMapProp.columnType === "json") {
160
+ const arrayProp = prop as ArrayProperty;
161
+ let colType = arrayProp.columnType;
162
+ if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {
163
+ const ofProp = arrayProp.of as Property;
164
+ if (ofProp.type === "string") {
165
+ colType = "text[]";
166
+ } else if (ofProp.type === "number") {
167
+ colType = ofProp.validation?.integer ? "integer[]" : "numeric[]";
168
+ } else if (ofProp.type === "boolean") {
169
+ colType = "boolean[]";
170
+ }
171
+ }
172
+
173
+ if (colType === "json") {
152
174
  columnDefinition = `json("${colName}")`;
175
+ } else if (colType === "text[]") {
176
+ columnDefinition = `text("${colName}").array()`;
177
+ } else if (colType === "integer[]") {
178
+ columnDefinition = `integer("${colName}").array()`;
179
+ } else if (colType === "boolean[]") {
180
+ columnDefinition = `boolean("${colName}").array()`;
181
+ } else if (colType === "numeric[]") {
182
+ columnDefinition = `numeric("${colName}").array()`;
153
183
  } else {
154
184
  columnDefinition = `jsonb("${colName}")`;
155
185
  }
@@ -589,9 +589,16 @@ export function generateCollectionFile(
589
589
  // Array/Map heuristics (Fallback if not inferred)
590
590
  if (finalPropType === "array" && !inferenceExtra.includes("of: {")) {
591
591
  let innerType = "string";
592
+ let colType = "";
592
593
  if (col.udt_name.startsWith("_")) {
593
594
  const baseType = col.udt_name.substring(1);
594
595
  innerType = mapPgType(baseType);
596
+ if (innerType === "string") colType = "text[]";
597
+ else if (innerType === "number") colType = col.udt_name === "_numeric" ? "numeric[]" : "integer[]";
598
+ else if (innerType === "boolean") colType = "boolean[]";
599
+ }
600
+ if (colType) {
601
+ extra += `\n columnType: "${colType}",`;
595
602
  }
596
603
  extra += `\n of: { name: "${humanize(col.column_name)} Item", type: "${innerType}" },`;
597
604
  } else if (finalPropType === "map" && !inferenceExtra.includes("keyValue: true") && !inferenceExtra.includes("properties: {")) {
@@ -53,6 +53,15 @@ export class EntityPersistService {
53
53
  .where(eq(idField, parsedId));
54
54
  }
55
55
 
56
+ /**
57
+ * Delete all entities from a collection
58
+ */
59
+ async deleteAll(collectionPath: string, _databaseId?: string): Promise<void> {
60
+ const collection = getCollectionByPath(collectionPath, this.registry);
61
+ const table = getTableForCollection(collection, this.registry);
62
+ await this.db.delete(table);
63
+ }
64
+
56
65
  /**
57
66
  * Save an entity (create or update)
58
67
  */
@@ -169,6 +169,13 @@ export class EntityService implements EntityRepository {
169
169
  return this.persistService.deleteEntity(collectionPath, entityId, databaseId);
170
170
  }
171
171
 
172
+ /**
173
+ * Delete all entities from a collection
174
+ */
175
+ async deleteAll(collectionPath: string, databaseId?: string): Promise<void> {
176
+ return this.persistService.deleteAll(collectionPath, databaseId);
177
+ }
178
+
172
179
 
173
180
  /**
174
181
  * Execute raw SQL
@@ -1,5 +1,5 @@
1
1
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
- import { UserService, RoleService, RefreshTokenService, PasswordResetTokenService, Role } from "../src/auth/services";
2
+ import { UserService, RefreshTokenService, PasswordResetTokenService, Role } from "../src/auth/services";
3
3
  import { users, refreshTokens, passwordResetTokens } from "../src/schema/auth-schema";
4
4
  import { UserData } from "@rebasepro/server-core";
5
5
 
@@ -344,18 +344,7 @@ describe("Auth Services", () => {
344
344
  describe("getUserRoles", () => {
345
345
  it("should return roles for user", async () => {
346
346
  mockExecute.mockResolvedValueOnce({
347
- rows: [
348
- { id: "admin",
349
- name: "Admin",
350
- is_admin: true,
351
- default_permissions: null,
352
- collection_permissions: null },
353
- { id: "editor",
354
- name: "Editor",
355
- is_admin: false,
356
- default_permissions: { edit: true },
357
- collection_permissions: null }
358
- ]
347
+ rows: [{ roles: ["admin", "editor"] }]
359
348
  });
360
349
 
361
350
  const roles = await userService.getUserRoles("user-123");
@@ -363,7 +352,7 @@ describe("Auth Services", () => {
363
352
  expect(roles).toHaveLength(2);
364
353
  expect(roles[0]).toEqual({
365
354
  id: "admin",
366
- name: "Admin",
355
+ name: "admin",
367
356
  isAdmin: true,
368
357
  defaultPermissions: null,
369
358
  collectionPermissions: null
@@ -374,13 +363,7 @@ describe("Auth Services", () => {
374
363
  describe("getUserRoleIds", () => {
375
364
  it("should return role IDs for user", async () => {
376
365
  mockExecute.mockResolvedValueOnce({
377
- rows: [
378
- { id: "admin",
379
- name: "Admin",
380
- is_admin: true,
381
- default_permissions: null,
382
- collection_permissions: null }
383
- ]
366
+ rows: [{ roles: ["admin"] }]
384
367
  });
385
368
 
386
369
  const roleIds = await userService.getUserRoleIds("user-123");
@@ -393,10 +376,7 @@ describe("Auth Services", () => {
393
376
  it("should delete existing and insert new roles", async () => {
394
377
  await userService.setUserRoles("user-123", ["admin", "editor"]);
395
378
 
396
- // First call deletes existing roles
397
379
  expect(mockExecute).toHaveBeenCalled();
398
- // Subsequent calls insert new roles
399
- expect(mockExecute.mock.calls.length).toBeGreaterThanOrEqual(1);
400
380
  });
401
381
  });
402
382
 
@@ -408,7 +388,7 @@ describe("Auth Services", () => {
408
388
  });
409
389
 
410
390
  it("should use editor as default role", async () => {
411
- await userService.assignDefaultRole("user-123");
391
+ await userService.assignDefaultRole("user-123", "editor");
412
392
 
413
393
  expect(mockExecute).toHaveBeenCalled();
414
394
  });
@@ -419,11 +399,7 @@ describe("Auth Services", () => {
419
399
  const mockUser = { id: "user-123", email: "test@example.com" };
420
400
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
421
401
  mockExecute.mockResolvedValueOnce({
422
- rows: [{ id: "admin",
423
- name: "Admin",
424
- is_admin: true,
425
- default_permissions: null,
426
- collection_permissions: null }]
402
+ rows: [{ roles: ["admin"] }]
427
403
  });
428
404
 
429
405
  const result = await userService.getUserWithRoles("user-123");
@@ -431,7 +407,7 @@ describe("Auth Services", () => {
431
407
  expect(result).toEqual({
432
408
  user: mockUserData({}),
433
409
  roles: [{ id: "admin",
434
- name: "Admin",
410
+ name: "admin",
435
411
  isAdmin: true,
436
412
  defaultPermissions: null,
437
413
  collectionPermissions: null }]
@@ -472,125 +448,6 @@ describe("Auth Services", () => {
472
448
  });
473
449
  });
474
450
 
475
- describe("RoleService", () => {
476
- let roleService: RoleService;
477
-
478
- beforeEach(() => {
479
- roleService = new RoleService(db);
480
- });
481
-
482
- describe("getRoleById", () => {
483
- it("should return role when found", async () => {
484
- mockExecute.mockResolvedValueOnce({
485
- rows: [{ id: "admin",
486
- name: "Admin",
487
- is_admin: true,
488
- default_permissions: null,
489
- collection_permissions: null }]
490
- });
491
-
492
- const result = await roleService.getRoleById("admin");
493
-
494
- expect(result).toEqual({
495
- id: "admin",
496
- name: "Admin",
497
- isAdmin: true,
498
- defaultPermissions: null,
499
- collectionPermissions: null
500
- });
501
- });
502
-
503
- it("should return null when role not found", async () => {
504
- mockExecute.mockResolvedValueOnce({ rows: [] });
505
-
506
- const result = await roleService.getRoleById("nonexistent");
507
-
508
- expect(result).toBeNull();
509
- });
510
- });
511
-
512
- describe("listRoles", () => {
513
- it("should return all roles", async () => {
514
- mockExecute.mockResolvedValueOnce({
515
- rows: [
516
- { id: "admin",
517
- name: "Admin",
518
- is_admin: true,
519
- default_permissions: null,
520
- collection_permissions: null },
521
- { id: "editor",
522
- name: "Editor",
523
- is_admin: false,
524
- default_permissions: null,
525
- collection_permissions: null }
526
- ]
527
- });
528
-
529
- const roles = await roleService.listRoles();
530
-
531
- expect(roles).toHaveLength(2);
532
- });
533
- });
534
-
535
- describe("createRole", () => {
536
- it("should create a role", async () => {
537
- mockExecute.mockResolvedValueOnce({
538
- rows: [{ id: "custom",
539
- name: "Custom Role",
540
- is_admin: false,
541
- default_permissions: null,
542
- collection_permissions: null }]
543
- });
544
-
545
- const role = await roleService.createRole({
546
- id: "custom",
547
- name: "Custom Role",
548
- defaultPermissions: null
549
- });
550
-
551
- expect(role.id).toBe("custom");
552
- expect(role.name).toBe("Custom Role");
553
- });
554
- });
555
-
556
- describe("updateRole", () => {
557
- it("should update a role", async () => {
558
- mockExecute
559
- .mockResolvedValueOnce({ rows: [{ id: "admin",
560
- name: "Admin",
561
- is_admin: true,
562
- default_permissions: null,
563
- collection_permissions: null }] })
564
- .mockResolvedValueOnce({ rows: [] })
565
- .mockResolvedValueOnce({ rows: [{ id: "admin",
566
- name: "Super Admin",
567
- is_admin: true,
568
- default_permissions: null,
569
- collection_permissions: null }] });
570
-
571
- const result = await roleService.updateRole("admin", { name: "Super Admin" });
572
-
573
- expect(result?.name).toBe("Super Admin");
574
- });
575
-
576
- it("should return null when role not found", async () => {
577
- mockExecute.mockResolvedValueOnce({ rows: [] });
578
-
579
- const result = await roleService.updateRole("nonexistent", { name: "Test" });
580
-
581
- expect(result).toBeNull();
582
- });
583
- });
584
-
585
- describe("deleteRole", () => {
586
- it("should delete a role", async () => {
587
- await roleService.deleteRole("custom");
588
-
589
- expect(mockExecute).toHaveBeenCalled();
590
- });
591
- });
592
- });
593
-
594
451
  describe("RefreshTokenService", () => {
595
452
  let refreshTokenService: RefreshTokenService;
596
453
 
@@ -135,8 +135,12 @@ columnType: "time" } as DateProperty)).toBe("time without time zone");
135
135
  it("should map json types correctly", () => {
136
136
  expect(getExpectedColumnType({ type: "map" })).toBe("jsonb");
137
137
  expect(getExpectedColumnType({ type: "array" })).toBe("jsonb");
138
- expect(getExpectedColumnType({ type: "array",
139
- columnType: "json" } as ArrayProperty)).toBe("json");
138
+ expect(getExpectedColumnType({ type: "array", columnType: "json" } as ArrayProperty)).toBe("json");
139
+
140
+ // Native array element type mappings
141
+ expect(getExpectedColumnType({ type: "array", of: { type: "string" } } as ArrayProperty)).toBe("ARRAY");
142
+ expect(getExpectedColumnType({ type: "array", of: { type: "number", validation: { integer: true } } } as ArrayProperty)).toBe("ARRAY");
143
+ expect(getExpectedColumnType({ type: "array", of: { type: "boolean" } } as ArrayProperty)).toBe("ARRAY");
140
144
  });
141
145
 
142
146
  it("should map enum string to USER-DEFINED", () => {
@@ -1,2 +0,0 @@
1
- import { PostgresCollection } from "@rebasepro/types";
2
- export declare const defaultUsersCollection: PostgresCollection;
@@ -1,14 +0,0 @@
1
- export type Role = {
2
- /**
3
- * ID of the role
4
- */
5
- id: string;
6
- /**
7
- * Name of the role
8
- */
9
- name: string;
10
- /**
11
- * If this flag is true, the user can perform any action
12
- */
13
- isAdmin?: boolean;
14
- };
@@ -1,69 +0,0 @@
1
- import { PostgresCollection } from "@rebasepro/types";
2
-
3
- export const defaultUsersCollection: PostgresCollection = {
4
- name: "Users",
5
- singularName: "User",
6
- slug: "users",
7
- table: "users",
8
- schema: "rebase",
9
- icon: "Users",
10
- group: "Settings",
11
- properties: {
12
- id: {
13
- name: "ID",
14
- type: "string",
15
- isId: "uuid"
16
- },
17
- email: {
18
- name: "Email",
19
- type: "string",
20
- validation: { required: true, unique: true }
21
- },
22
- password_hash: {
23
- name: "Password Hash",
24
- type: "string",
25
- ui: { hideFromCollection: true }
26
- },
27
- display_name: {
28
- name: "Display Name",
29
- type: "string"
30
- },
31
- photo_url: {
32
- name: "Photo URL",
33
- type: "string"
34
- },
35
- email_verified: {
36
- name: "Email Verified",
37
- type: "boolean",
38
- defaultValue: false
39
- },
40
- email_verification_token: {
41
- name: "Email Verification Token",
42
- type: "string",
43
- ui: { hideFromCollection: true }
44
- },
45
- email_verification_sent_at: {
46
- name: "Email Verification Sent At",
47
- type: "date",
48
- ui: { hideFromCollection: true }
49
- },
50
- metadata: {
51
- name: "Metadata",
52
- type: "map",
53
- defaultValue: {},
54
- ui: { hideFromCollection: true }
55
- },
56
- created_at: {
57
- name: "Created At",
58
- type: "date",
59
- autoValue: "on_create",
60
- ui: { readOnly: true, hideFromCollection: true }
61
- },
62
- updated_at: {
63
- name: "Updated At",
64
- type: "date",
65
- autoValue: "on_update",
66
- ui: { readOnly: true, hideFromCollection: true }
67
- }
68
- }
69
- };