@rebasepro/server-postgresql 0.0.1-canary.09e5ec5
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.
- package/LICENSE +6 -0
- package/README.md +106 -0
- package/build-errors.txt +37 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +58 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +22 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index.es.js +11298 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +11306 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +100 -0
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +40 -0
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +6 -0
- package/dist/server-postgresql/src/auth/services.d.ts +192 -0
- package/dist/server-postgresql/src/cli.d.ts +1 -0
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +43 -0
- package/dist/server-postgresql/src/connection.d.ts +40 -0
- package/dist/server-postgresql/src/data-transformer.d.ts +58 -0
- package/dist/server-postgresql/src/databasePoolManager.d.ts +20 -0
- package/dist/server-postgresql/src/history/HistoryService.d.ts +71 -0
- package/dist/server-postgresql/src/history/ensure-history-table.d.ts +7 -0
- package/dist/server-postgresql/src/index.d.ts +13 -0
- package/dist/server-postgresql/src/interfaces.d.ts +18 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +868 -0
- package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +2 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema.d.ts +1 -0
- package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +82 -0
- package/dist/server-postgresql/src/schema/introspect-db.d.ts +1 -0
- package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
- package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +209 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
- package/dist/server-postgresql/src/services/RelationService.d.ts +98 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +38 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +104 -0
- package/dist/server-postgresql/src/services/index.d.ts +4 -0
- package/dist/server-postgresql/src/services/realtimeService.d.ts +188 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +116 -0
- package/dist/server-postgresql/src/websocket.d.ts +5 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +119 -0
- package/dist/types/src/controllers/client.d.ts +170 -0
- package/dist/types/src/controllers/collection_registry.d.ts +45 -0
- package/dist/types/src/controllers/customization_controller.d.ts +60 -0
- package/dist/types/src/controllers/data.d.ts +168 -0
- package/dist/types/src/controllers/data_driver.d.ts +160 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +18 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +54 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +171 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +105 -0
- package/dist/types/src/types/backend.d.ts +536 -0
- package/dist/types/src/types/builders.d.ts +15 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +856 -0
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +10 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +23 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +279 -0
- package/dist/types/src/types/properties.d.ts +1176 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +252 -0
- package/dist/types/src/types/translations.d.ts +870 -0
- package/dist/types/src/types/user_management_delegate.d.ts +121 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/drizzle-test/0000_woozy_junta.sql +6 -0
- package/drizzle-test/0001_youthful_arachne.sql +1 -0
- package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
- package/drizzle-test/0003_mean_king_cobra.sql +2 -0
- package/drizzle-test/meta/0000_snapshot.json +47 -0
- package/drizzle-test/meta/0001_snapshot.json +48 -0
- package/drizzle-test/meta/0002_snapshot.json +38 -0
- package/drizzle-test/meta/0003_snapshot.json +48 -0
- package/drizzle-test/meta/_journal.json +34 -0
- package/drizzle-test-out/0000_tan_trauma.sql +6 -0
- package/drizzle-test-out/0001_rapid_drax.sql +1 -0
- package/drizzle-test-out/meta/0000_snapshot.json +44 -0
- package/drizzle-test-out/meta/0001_snapshot.json +54 -0
- package/drizzle-test-out/meta/_journal.json +20 -0
- package/drizzle.test.config.ts +10 -0
- package/jest-all.log +3128 -0
- package/jest.log +49 -0
- package/package.json +92 -0
- package/scratch.ts +41 -0
- package/src/PostgresBackendDriver.ts +1008 -0
- package/src/PostgresBootstrapper.ts +231 -0
- package/src/auth/ensure-tables.ts +381 -0
- package/src/auth/services.ts +799 -0
- package/src/cli.ts +648 -0
- package/src/collections/PostgresCollectionRegistry.ts +96 -0
- package/src/connection.ts +84 -0
- package/src/data-transformer.ts +608 -0
- package/src/databasePoolManager.ts +85 -0
- package/src/history/HistoryService.ts +248 -0
- package/src/history/ensure-history-table.ts +45 -0
- package/src/index.ts +13 -0
- package/src/interfaces.ts +60 -0
- package/src/schema/auth-schema.ts +169 -0
- package/src/schema/doctor-cli.ts +47 -0
- package/src/schema/doctor.ts +595 -0
- package/src/schema/generate-drizzle-schema-logic.ts +765 -0
- package/src/schema/generate-drizzle-schema.ts +151 -0
- package/src/schema/introspect-db-logic.ts +542 -0
- package/src/schema/introspect-db.ts +211 -0
- package/src/schema/test-schema.ts +11 -0
- package/src/services/BranchService.ts +237 -0
- package/src/services/EntityFetchService.ts +1576 -0
- package/src/services/EntityPersistService.ts +349 -0
- package/src/services/RelationService.ts +1274 -0
- package/src/services/entity-helpers.ts +147 -0
- package/src/services/entityService.ts +211 -0
- package/src/services/index.ts +13 -0
- package/src/services/realtimeService.ts +1034 -0
- package/src/utils/drizzle-conditions.ts +1000 -0
- package/src/websocket.ts +518 -0
- package/test/auth-services.test.ts +661 -0
- package/test/batch-many-to-many-regression.test.ts +573 -0
- package/test/branchService.test.ts +367 -0
- package/test/data-transformer-hardening.test.ts +417 -0
- package/test/data-transformer.test.ts +175 -0
- package/test/doctor.test.ts +182 -0
- package/test/drizzle-conditions.test.ts +895 -0
- package/test/entityService.errors.test.ts +367 -0
- package/test/entityService.relations.test.ts +1008 -0
- package/test/entityService.subcollection-search.test.ts +566 -0
- package/test/entityService.test.ts +1035 -0
- package/test/generate-drizzle-schema.test.ts +988 -0
- package/test/historyService.test.ts +141 -0
- package/test/introspect-db-generation.test.ts +436 -0
- package/test/introspect-db-utils.test.ts +389 -0
- package/test/n-plus-one-regression.test.ts +314 -0
- package/test/postgresDataDriver.test.ts +648 -0
- package/test/realtimeService.test.ts +307 -0
- package/test/relation-pipeline-gaps.test.ts +637 -0
- package/test/relations.test.ts +1115 -0
- package/test/unmapped-tables-safety.test.ts +345 -0
- package/test-drizzle-bug.ts +18 -0
- package/test-drizzle-out/0000_cultured_freak.sql +7 -0
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
- package/test-drizzle-out/meta/0000_snapshot.json +55 -0
- package/test-drizzle-out/meta/0001_snapshot.json +63 -0
- package/test-drizzle-out/meta/_journal.json +20 -0
- package/test-drizzle-prompt.sh +2 -0
- package/test-policy-prompt.sh +3 -0
- package/test-programmatic.ts +30 -0
- package/test-programmatic2.ts +59 -0
- package/test-schema-no-policies.ts +12 -0
- package/test_drizzle_mock.js +3 -0
- package/test_find_changed.mjs +32 -0
- package/test_hash.js +14 -0
- package/test_output.txt +3145 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +82 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import { eq, sql } from "drizzle-orm";
|
|
2
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
|
+
import { users, userIdentities, refreshTokens, passwordResetTokens, User, NewUser } from "../schema/auth-schema";
|
|
4
|
+
import {
|
|
5
|
+
UserRepository,
|
|
6
|
+
RoleRepository,
|
|
7
|
+
TokenRepository,
|
|
8
|
+
AuthRepository,
|
|
9
|
+
UserData,
|
|
10
|
+
CreateUserData,
|
|
11
|
+
RoleData,
|
|
12
|
+
CreateRoleData,
|
|
13
|
+
RefreshTokenInfo,
|
|
14
|
+
PasswordResetTokenInfo,
|
|
15
|
+
UserIdentityData,
|
|
16
|
+
ListUsersOptions,
|
|
17
|
+
PaginatedUsersResult,
|
|
18
|
+
RoleData as Role
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
} from "@rebasepro/server-core";
|
|
21
|
+
|
|
22
|
+
export type { Role };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* PostgreSQL implementation of UserRepository.
|
|
26
|
+
* Handles all user-related database operations using Drizzle ORM.
|
|
27
|
+
*/
|
|
28
|
+
export class UserService implements UserRepository {
|
|
29
|
+
constructor(private db: NodePgDatabase) { }
|
|
30
|
+
|
|
31
|
+
async createUser(data: NewUser): Promise<User> {
|
|
32
|
+
const [user] = await this.db.insert(users).values(data).returning();
|
|
33
|
+
return user;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getUserById(id: string): Promise<User | null> {
|
|
37
|
+
const [user] = await this.db.select().from(users).where(eq(users.id, id));
|
|
38
|
+
return user || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getUserByEmail(email: string): Promise<User | null> {
|
|
42
|
+
const [user] = await this.db.select().from(users).where(eq(users.email, email.toLowerCase()));
|
|
43
|
+
return user || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getUserByIdentity(provider: string, providerId: string): Promise<User | null> {
|
|
47
|
+
const result = await this.db.execute(sql`
|
|
48
|
+
SELECT u.*
|
|
49
|
+
FROM rebase.users u
|
|
50
|
+
INNER JOIN rebase.user_identities ui ON u.id = ui.user_id
|
|
51
|
+
WHERE ui.provider = ${provider} AND ui.provider_id = ${providerId}
|
|
52
|
+
LIMIT 1
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
if (result.rows.length === 0) return null;
|
|
56
|
+
|
|
57
|
+
const row = result.rows[0] as Record<string, unknown>;
|
|
58
|
+
return {
|
|
59
|
+
id: row.id as string,
|
|
60
|
+
email: row.email as string,
|
|
61
|
+
passwordHash: (row.password_hash as string | null) ?? null,
|
|
62
|
+
displayName: (row.display_name as string | null) ?? null,
|
|
63
|
+
photoUrl: (row.photo_url as string | null) ?? null,
|
|
64
|
+
emailVerified: (row.email_verified as boolean | undefined) ?? false,
|
|
65
|
+
emailVerificationToken: (row.email_verification_token as string | null) ?? null,
|
|
66
|
+
emailVerificationSentAt: (row.email_verification_sent_at as Date | null) ?? null,
|
|
67
|
+
createdAt: row.created_at as Date,
|
|
68
|
+
updatedAt: row.updated_at as Date
|
|
69
|
+
} as User;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
|
|
73
|
+
const result = await this.db.execute(sql`
|
|
74
|
+
SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
|
|
75
|
+
FROM rebase.user_identities
|
|
76
|
+
WHERE user_id = ${userId}
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
return result.rows.map((row: Record<string, unknown>) => ({
|
|
80
|
+
id: row.id as string,
|
|
81
|
+
userId: row.user_id as string,
|
|
82
|
+
provider: row.provider as string,
|
|
83
|
+
providerId: row.provider_id as string,
|
|
84
|
+
profileData: (row.profile_data as Record<string, unknown> | null) ?? null,
|
|
85
|
+
createdAt: row.created_at as Date,
|
|
86
|
+
updatedAt: row.updated_at as Date
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
|
|
91
|
+
await this.db.insert(userIdentities).values({
|
|
92
|
+
userId,
|
|
93
|
+
provider,
|
|
94
|
+
providerId,
|
|
95
|
+
profileData: profileData || null
|
|
96
|
+
}).onConflictDoNothing({ target: [userIdentities.provider, userIdentities.providerId] });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async updateUser(id: string, data: Partial<Omit<NewUser, "id">>): Promise<User | null> {
|
|
100
|
+
const [user] = await this.db
|
|
101
|
+
.update(users)
|
|
102
|
+
.set({ ...data,
|
|
103
|
+
updatedAt: new Date() })
|
|
104
|
+
.where(eq(users.id, id))
|
|
105
|
+
.returning();
|
|
106
|
+
return user || null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async deleteUser(id: string): Promise<void> {
|
|
110
|
+
await this.db.delete(users).where(eq(users.id, id));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async listUsers(): Promise<User[]> {
|
|
114
|
+
return this.db.select().from(users);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {
|
|
118
|
+
const limit = options?.limit ?? 25;
|
|
119
|
+
const offset = options?.offset ?? 0;
|
|
120
|
+
const search = options?.search?.trim() || "";
|
|
121
|
+
const orderBy = options?.orderBy || "createdAt";
|
|
122
|
+
const orderDir = options?.orderDir || "desc";
|
|
123
|
+
const roleId = options?.roleId;
|
|
124
|
+
|
|
125
|
+
// Map camelCase field names to snake_case column names
|
|
126
|
+
const columnMap: Record<string, string> = {
|
|
127
|
+
email: "email",
|
|
128
|
+
displayName: "display_name",
|
|
129
|
+
createdAt: "created_at",
|
|
130
|
+
updatedAt: "updated_at",
|
|
131
|
+
provider: "provider"
|
|
132
|
+
};
|
|
133
|
+
const orderColumn = columnMap[orderBy] || "created_at";
|
|
134
|
+
const direction = orderDir === "asc" ? sql`ASC` : sql`DESC`;
|
|
135
|
+
|
|
136
|
+
const conditions = [];
|
|
137
|
+
if (roleId) {
|
|
138
|
+
conditions.push(sql`EXISTS (SELECT 1 FROM rebase.user_roles ur WHERE ur.user_id = users.id AND ur.role_id = ${roleId})`);
|
|
139
|
+
}
|
|
140
|
+
if (search) {
|
|
141
|
+
const pattern = `%${search}%`;
|
|
142
|
+
conditions.push(sql`(email ILIKE ${pattern} OR display_name ILIKE ${pattern})`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;
|
|
146
|
+
|
|
147
|
+
// Sorting: users with roles first if no role filter, then by requested order
|
|
148
|
+
const orderByClause = roleId
|
|
149
|
+
? sql`ORDER BY ${sql.raw(orderColumn)} ${direction}`
|
|
150
|
+
: sql`ORDER BY (SELECT count(*) FROM rebase.user_roles ur WHERE ur.user_id = users.id) DESC, ${sql.raw(orderColumn)} ${direction}`;
|
|
151
|
+
|
|
152
|
+
const countResult = await this.db.execute(sql`
|
|
153
|
+
SELECT count(*)::int as total FROM rebase.users
|
|
154
|
+
${whereClause}
|
|
155
|
+
`);
|
|
156
|
+
const total = (countResult.rows[0] as { total: number }).total;
|
|
157
|
+
|
|
158
|
+
const dataResult = await this.db.execute(sql`
|
|
159
|
+
SELECT * FROM rebase.users
|
|
160
|
+
${whereClause}
|
|
161
|
+
${orderByClause}
|
|
162
|
+
LIMIT ${limit} OFFSET ${offset}
|
|
163
|
+
`);
|
|
164
|
+
const rows = dataResult.rows as User[];
|
|
165
|
+
|
|
166
|
+
// Map snake_case rows to camelCase UserData
|
|
167
|
+
const mappedUsers: User[] = rows.map((row: Record<string, unknown>) => ({
|
|
168
|
+
id: row.id as string,
|
|
169
|
+
email: row.email as string,
|
|
170
|
+
passwordHash: ((row.password_hash ?? row.passwordHash) as string | null) ?? null,
|
|
171
|
+
displayName: ((row.display_name ?? row.displayName) as string | null) ?? null,
|
|
172
|
+
photoUrl: ((row.photo_url ?? row.photoUrl) as string | null) ?? null,
|
|
173
|
+
emailVerified: ((row.email_verified ?? row.emailVerified) as boolean | undefined) ?? false,
|
|
174
|
+
emailVerificationToken: ((row.email_verification_token ?? row.emailVerificationToken) as string | null) ?? null,
|
|
175
|
+
emailVerificationSentAt: ((row.email_verification_sent_at ?? row.emailVerificationSentAt) as Date | null) ?? null,
|
|
176
|
+
createdAt: (row.created_at ?? row.createdAt) as Date,
|
|
177
|
+
updatedAt: (row.updated_at ?? row.updatedAt) as Date
|
|
178
|
+
})) as User[];
|
|
179
|
+
|
|
180
|
+
return { users: mappedUsers,
|
|
181
|
+
total,
|
|
182
|
+
limit,
|
|
183
|
+
offset };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Update user's password hash
|
|
188
|
+
*/
|
|
189
|
+
async updatePassword(id: string, passwordHash: string): Promise<void> {
|
|
190
|
+
await this.db
|
|
191
|
+
.update(users)
|
|
192
|
+
.set({ passwordHash,
|
|
193
|
+
updatedAt: new Date() })
|
|
194
|
+
.where(eq(users.id, id));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Set email verification status
|
|
199
|
+
*/
|
|
200
|
+
async setEmailVerified(id: string, verified: boolean): Promise<void> {
|
|
201
|
+
await this.db
|
|
202
|
+
.update(users)
|
|
203
|
+
.set({
|
|
204
|
+
emailVerified: verified,
|
|
205
|
+
emailVerificationToken: null,
|
|
206
|
+
updatedAt: new Date()
|
|
207
|
+
})
|
|
208
|
+
.where(eq(users.id, id));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Set email verification token
|
|
213
|
+
*/
|
|
214
|
+
async setVerificationToken(id: string, token: string | null): Promise<void> {
|
|
215
|
+
await this.db
|
|
216
|
+
.update(users)
|
|
217
|
+
.set({
|
|
218
|
+
emailVerificationToken: token,
|
|
219
|
+
emailVerificationSentAt: token ? new Date() : null,
|
|
220
|
+
updatedAt: new Date()
|
|
221
|
+
})
|
|
222
|
+
.where(eq(users.id, id));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Find user by email verification token
|
|
227
|
+
*/
|
|
228
|
+
async getUserByVerificationToken(token: string): Promise<User | null> {
|
|
229
|
+
const [user] = await this.db
|
|
230
|
+
.select()
|
|
231
|
+
.from(users)
|
|
232
|
+
.where(eq(users.emailVerificationToken, token));
|
|
233
|
+
return user || null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get roles for a user from database
|
|
238
|
+
*/
|
|
239
|
+
async getUserRoles(userId: string): Promise<Role[]> {
|
|
240
|
+
const result = await this.db.execute(sql`
|
|
241
|
+
SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
|
|
242
|
+
FROM rebase.roles r
|
|
243
|
+
INNER JOIN rebase.user_roles ur ON r.id = ur.role_id
|
|
244
|
+
WHERE ur.user_id = ${userId}
|
|
245
|
+
`);
|
|
246
|
+
|
|
247
|
+
return (result.rows as Array<{ id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null; config: Record<string, unknown> | null }>).map(row => ({
|
|
248
|
+
id: row.id,
|
|
249
|
+
name: row.name,
|
|
250
|
+
isAdmin: row.is_admin,
|
|
251
|
+
defaultPermissions: row.default_permissions,
|
|
252
|
+
collectionPermissions: row.collection_permissions,
|
|
253
|
+
config: row.config
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get role IDs for a user
|
|
259
|
+
*/
|
|
260
|
+
async getUserRoleIds(userId: string): Promise<string[]> {
|
|
261
|
+
const roles = await this.getUserRoles(userId);
|
|
262
|
+
return roles.map(r => r.id);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Set roles for a user
|
|
267
|
+
*/
|
|
268
|
+
async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
|
|
269
|
+
// Delete existing roles
|
|
270
|
+
await this.db.execute(sql`DELETE FROM rebase.user_roles WHERE user_id = ${userId}`);
|
|
271
|
+
|
|
272
|
+
// Insert new roles
|
|
273
|
+
for (const roleId of roleIds) {
|
|
274
|
+
await this.db.execute(sql`
|
|
275
|
+
INSERT INTO rebase.user_roles (user_id, role_id)
|
|
276
|
+
VALUES (${userId}, ${roleId})
|
|
277
|
+
ON CONFLICT DO NOTHING
|
|
278
|
+
`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Assign a specific role to new user
|
|
284
|
+
*/
|
|
285
|
+
async assignDefaultRole(userId: string, roleId: string): Promise<void> {
|
|
286
|
+
await this.db.execute(sql`
|
|
287
|
+
INSERT INTO rebase.user_roles (user_id, role_id)
|
|
288
|
+
VALUES (${userId}, ${roleId})
|
|
289
|
+
ON CONFLICT DO NOTHING
|
|
290
|
+
`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get user with their roles
|
|
295
|
+
*/
|
|
296
|
+
async getUserWithRoles(userId: string): Promise<{ user: User; roles: Role[] } | null> {
|
|
297
|
+
const user = await this.getUserById(userId);
|
|
298
|
+
if (!user) return null;
|
|
299
|
+
|
|
300
|
+
const roles = await this.getUserRoles(userId);
|
|
301
|
+
return { user,
|
|
302
|
+
roles };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* PostgreSQL implementation of RoleRepository.
|
|
308
|
+
* Handles all role-related database operations using Drizzle ORM.
|
|
309
|
+
*/
|
|
310
|
+
export class RoleService implements RoleRepository {
|
|
311
|
+
constructor(private db: NodePgDatabase) { }
|
|
312
|
+
|
|
313
|
+
async getRoleById(id: string): Promise<Role | null> {
|
|
314
|
+
const result = await this.db.execute(sql`
|
|
315
|
+
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
316
|
+
FROM rebase.roles
|
|
317
|
+
WHERE id = ${id}
|
|
318
|
+
`);
|
|
319
|
+
|
|
320
|
+
if (result.rows.length === 0) return null;
|
|
321
|
+
|
|
322
|
+
const row = result.rows[0] as { id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null; config: Record<string, unknown> | null };
|
|
323
|
+
return {
|
|
324
|
+
id: row.id,
|
|
325
|
+
name: row.name,
|
|
326
|
+
isAdmin: row.is_admin,
|
|
327
|
+
defaultPermissions: row.default_permissions,
|
|
328
|
+
collectionPermissions: row.collection_permissions,
|
|
329
|
+
config: row.config
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async listRoles(): Promise<Role[]> {
|
|
334
|
+
const result = await this.db.execute(sql`
|
|
335
|
+
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
336
|
+
FROM rebase.roles
|
|
337
|
+
ORDER BY name
|
|
338
|
+
`);
|
|
339
|
+
|
|
340
|
+
return (result.rows as Array<{ id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null; config: Record<string, unknown> | null }>).map(row => ({
|
|
341
|
+
id: row.id,
|
|
342
|
+
name: row.name,
|
|
343
|
+
isAdmin: row.is_admin,
|
|
344
|
+
defaultPermissions: row.default_permissions,
|
|
345
|
+
collectionPermissions: row.collection_permissions,
|
|
346
|
+
config: row.config
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async createRole(data: Omit<Role, "isAdmin" | "collectionPermissions"> & { isAdmin?: boolean; collectionPermissions?: Role["collectionPermissions"] }): Promise<Role> {
|
|
351
|
+
const result = await this.db.execute(sql`
|
|
352
|
+
INSERT INTO rebase.roles (id, name, is_admin, default_permissions, collection_permissions, config)
|
|
353
|
+
VALUES (
|
|
354
|
+
${data.id},
|
|
355
|
+
${data.name},
|
|
356
|
+
${data.isAdmin ?? false},
|
|
357
|
+
${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : null}::jsonb,
|
|
358
|
+
${data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null}::jsonb,
|
|
359
|
+
${data.config ? JSON.stringify(data.config) : null}::jsonb
|
|
360
|
+
)
|
|
361
|
+
RETURNING id, name, is_admin, default_permissions, collection_permissions, config
|
|
362
|
+
`);
|
|
363
|
+
|
|
364
|
+
const row = result.rows[0] as { id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null; config: Record<string, unknown> | null };
|
|
365
|
+
return {
|
|
366
|
+
id: row.id,
|
|
367
|
+
name: row.name,
|
|
368
|
+
isAdmin: row.is_admin,
|
|
369
|
+
defaultPermissions: row.default_permissions,
|
|
370
|
+
collectionPermissions: row.collection_permissions,
|
|
371
|
+
config: row.config
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async updateRole(id: string, data: Partial<Omit<Role, "id">>): Promise<Role | null> {
|
|
376
|
+
// For now, use simpler approach
|
|
377
|
+
const existing = await this.getRoleById(id);
|
|
378
|
+
if (!existing) return null;
|
|
379
|
+
|
|
380
|
+
await this.db.execute(sql`
|
|
381
|
+
UPDATE rebase.roles
|
|
382
|
+
SET
|
|
383
|
+
name = ${data.name ?? existing.name},
|
|
384
|
+
is_admin = ${data.isAdmin ?? existing.isAdmin},
|
|
385
|
+
default_permissions = ${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : JSON.stringify(existing.defaultPermissions)}::jsonb,
|
|
386
|
+
collection_permissions = ${data.collectionPermissions !== undefined ? (data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null) : (existing.collectionPermissions ? JSON.stringify(existing.collectionPermissions) : null)}::jsonb,
|
|
387
|
+
config = ${data.config ? JSON.stringify(data.config) : (existing.config ? JSON.stringify(existing.config) : null)}::jsonb
|
|
388
|
+
WHERE id = ${id}
|
|
389
|
+
`);
|
|
390
|
+
|
|
391
|
+
return this.getRoleById(id);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async deleteRole(id: string): Promise<void> {
|
|
395
|
+
await this.db.execute(sql`DELETE FROM rebase.roles WHERE id = ${id}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export class RefreshTokenService {
|
|
400
|
+
constructor(private db: NodePgDatabase) { }
|
|
401
|
+
|
|
402
|
+
async createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
|
|
403
|
+
// Fallback to empty string because UNIQUE constraints treat NULLs as strictly distinct in standard Postgres.
|
|
404
|
+
// We want (userId, NULL, NULL) to collide and overwrite, so we map undefined/null to empty strings.
|
|
405
|
+
const safeUserAgent = userAgent || "";
|
|
406
|
+
const safeIpAddress = ipAddress || "";
|
|
407
|
+
|
|
408
|
+
// Delete any existing session for this user/device combo, then insert.
|
|
409
|
+
// This approach doesn't require the unique_device_session constraint to exist.
|
|
410
|
+
await this.db.execute(sql`
|
|
411
|
+
DELETE FROM rebase.refresh_tokens
|
|
412
|
+
WHERE user_id = ${userId}
|
|
413
|
+
AND user_agent = ${safeUserAgent}
|
|
414
|
+
AND ip_address = ${safeIpAddress}
|
|
415
|
+
`);
|
|
416
|
+
|
|
417
|
+
await this.db.insert(refreshTokens)
|
|
418
|
+
.values({
|
|
419
|
+
userId,
|
|
420
|
+
tokenHash,
|
|
421
|
+
expiresAt,
|
|
422
|
+
userAgent: safeUserAgent,
|
|
423
|
+
ipAddress: safeIpAddress
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async findByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
|
|
428
|
+
const [token] = await this.db
|
|
429
|
+
.select({
|
|
430
|
+
id: refreshTokens.id,
|
|
431
|
+
userId: refreshTokens.userId,
|
|
432
|
+
tokenHash: refreshTokens.tokenHash,
|
|
433
|
+
expiresAt: refreshTokens.expiresAt,
|
|
434
|
+
createdAt: refreshTokens.createdAt,
|
|
435
|
+
userAgent: refreshTokens.userAgent,
|
|
436
|
+
ipAddress: refreshTokens.ipAddress
|
|
437
|
+
})
|
|
438
|
+
.from(refreshTokens)
|
|
439
|
+
.where(eq(refreshTokens.tokenHash, tokenHash));
|
|
440
|
+
|
|
441
|
+
return token || null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async deleteByHash(tokenHash: string): Promise<void> {
|
|
445
|
+
await this.db.delete(refreshTokens).where(eq(refreshTokens.tokenHash, tokenHash));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async deleteAllForUser(userId: string): Promise<void> {
|
|
449
|
+
await this.db.delete(refreshTokens).where(eq(refreshTokens.userId, userId));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async listForUser(userId: string): Promise<RefreshTokenInfo[]> {
|
|
453
|
+
const tokens = await this.db
|
|
454
|
+
.select({
|
|
455
|
+
id: refreshTokens.id,
|
|
456
|
+
userId: refreshTokens.userId,
|
|
457
|
+
tokenHash: refreshTokens.tokenHash,
|
|
458
|
+
expiresAt: refreshTokens.expiresAt,
|
|
459
|
+
createdAt: refreshTokens.createdAt,
|
|
460
|
+
userAgent: refreshTokens.userAgent,
|
|
461
|
+
ipAddress: refreshTokens.ipAddress
|
|
462
|
+
})
|
|
463
|
+
.from(refreshTokens)
|
|
464
|
+
.where(eq(refreshTokens.userId, userId))
|
|
465
|
+
.orderBy(refreshTokens.createdAt);
|
|
466
|
+
|
|
467
|
+
return tokens;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async deleteById(id: string, userId: string): Promise<void> {
|
|
471
|
+
await this.db.delete(refreshTokens)
|
|
472
|
+
.where(sql`${refreshTokens.id} = ${id} AND ${refreshTokens.userId} = ${userId}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Password reset token service
|
|
478
|
+
*/
|
|
479
|
+
export class PasswordResetTokenService {
|
|
480
|
+
constructor(private db: NodePgDatabase) { }
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Create a password reset token
|
|
484
|
+
*/
|
|
485
|
+
async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
|
|
486
|
+
// Delete any existing unused tokens for this user
|
|
487
|
+
await this.db.execute(sql`
|
|
488
|
+
DELETE FROM rebase.password_reset_tokens
|
|
489
|
+
WHERE user_id = ${userId} AND used_at IS NULL
|
|
490
|
+
`);
|
|
491
|
+
|
|
492
|
+
await this.db.insert(passwordResetTokens).values({
|
|
493
|
+
userId,
|
|
494
|
+
tokenHash,
|
|
495
|
+
expiresAt
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Find a valid (not expired, not used) token by hash
|
|
501
|
+
*/
|
|
502
|
+
async findValidByHash(tokenHash: string): Promise<{ userId: string; expiresAt: Date } | null> {
|
|
503
|
+
const [token] = await this.db
|
|
504
|
+
.select({
|
|
505
|
+
userId: passwordResetTokens.userId,
|
|
506
|
+
expiresAt: passwordResetTokens.expiresAt
|
|
507
|
+
})
|
|
508
|
+
.from(passwordResetTokens)
|
|
509
|
+
.where(eq(passwordResetTokens.tokenHash, tokenHash));
|
|
510
|
+
|
|
511
|
+
if (!token) return null;
|
|
512
|
+
|
|
513
|
+
// Check if expired or used
|
|
514
|
+
const result = await this.db.execute(sql`
|
|
515
|
+
SELECT user_id, expires_at
|
|
516
|
+
FROM rebase.password_reset_tokens
|
|
517
|
+
WHERE token_hash = ${tokenHash}
|
|
518
|
+
AND used_at IS NULL
|
|
519
|
+
AND expires_at > NOW()
|
|
520
|
+
`);
|
|
521
|
+
|
|
522
|
+
if (result.rows.length === 0) return null;
|
|
523
|
+
|
|
524
|
+
const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
|
|
525
|
+
return {
|
|
526
|
+
userId: row.user_id,
|
|
527
|
+
expiresAt: new Date(row.expires_at)
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Mark token as used
|
|
533
|
+
*/
|
|
534
|
+
async markAsUsed(tokenHash: string): Promise<void> {
|
|
535
|
+
await this.db
|
|
536
|
+
.update(passwordResetTokens)
|
|
537
|
+
.set({ usedAt: new Date() })
|
|
538
|
+
.where(eq(passwordResetTokens.tokenHash, tokenHash));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Delete all tokens for a user
|
|
543
|
+
*/
|
|
544
|
+
async deleteAllForUser(userId: string): Promise<void> {
|
|
545
|
+
await this.db.delete(passwordResetTokens).where(eq(passwordResetTokens.userId, userId));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Clean up expired tokens
|
|
550
|
+
*/
|
|
551
|
+
async deleteExpired(): Promise<void> {
|
|
552
|
+
await this.db.execute(sql`
|
|
553
|
+
DELETE FROM rebase.password_reset_tokens
|
|
554
|
+
WHERE expires_at < NOW()
|
|
555
|
+
`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* PostgreSQL implementation of TokenRepository.
|
|
561
|
+
* Combines refresh token and password reset token operations.
|
|
562
|
+
*/
|
|
563
|
+
export class PostgresTokenRepository implements TokenRepository {
|
|
564
|
+
private refreshTokenService: RefreshTokenService;
|
|
565
|
+
private passwordResetTokenService: PasswordResetTokenService;
|
|
566
|
+
|
|
567
|
+
constructor(private db: NodePgDatabase) {
|
|
568
|
+
this.refreshTokenService = new RefreshTokenService(db);
|
|
569
|
+
this.passwordResetTokenService = new PasswordResetTokenService(db);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Refresh token operations
|
|
573
|
+
|
|
574
|
+
async createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
|
|
575
|
+
await this.refreshTokenService.createToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
|
|
579
|
+
return this.refreshTokenService.findByHash(tokenHash);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async deleteRefreshToken(tokenHash: string): Promise<void> {
|
|
583
|
+
await this.refreshTokenService.deleteByHash(tokenHash);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
|
|
587
|
+
await this.refreshTokenService.deleteAllForUser(userId);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
|
|
591
|
+
return this.refreshTokenService.listForUser(userId);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
|
|
595
|
+
await this.refreshTokenService.deleteById(id, userId);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Password reset token operations
|
|
599
|
+
|
|
600
|
+
async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
|
|
601
|
+
await this.passwordResetTokenService.createToken(userId, tokenHash, expiresAt);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
|
|
605
|
+
return this.passwordResetTokenService.findValidByHash(tokenHash);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async markPasswordResetTokenUsed(tokenHash: string): Promise<void> {
|
|
609
|
+
await this.passwordResetTokenService.markAsUsed(tokenHash);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
|
|
613
|
+
await this.passwordResetTokenService.deleteAllForUser(userId);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async deleteExpiredTokens(): Promise<void> {
|
|
617
|
+
await this.passwordResetTokenService.deleteExpired();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* PostgreSQL implementation of AuthRepository.
|
|
623
|
+
* Combines user, role, and token repository operations.
|
|
624
|
+
* This provides a convenient single-class interface for all auth operations.
|
|
625
|
+
*/
|
|
626
|
+
export class PostgresAuthRepository implements AuthRepository {
|
|
627
|
+
private userService: UserService;
|
|
628
|
+
private roleService: RoleService;
|
|
629
|
+
private tokenRepository: PostgresTokenRepository;
|
|
630
|
+
|
|
631
|
+
constructor(private db: NodePgDatabase) {
|
|
632
|
+
this.userService = new UserService(db);
|
|
633
|
+
this.roleService = new RoleService(db);
|
|
634
|
+
this.tokenRepository = new PostgresTokenRepository(db);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// User operations (delegate to UserService)
|
|
638
|
+
|
|
639
|
+
async createUser(data: CreateUserData): Promise<UserData> {
|
|
640
|
+
return this.userService.createUser(data as NewUser) as Promise<UserData>;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async getUserById(id: string): Promise<UserData | null> {
|
|
644
|
+
return this.userService.getUserById(id) as Promise<UserData | null>;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async getUserByEmail(email: string): Promise<UserData | null> {
|
|
648
|
+
return this.userService.getUserByEmail(email) as Promise<UserData | null>;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {
|
|
652
|
+
return this.userService.getUserByIdentity(provider, providerId) as Promise<UserData | null>;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
|
|
656
|
+
return this.userService.getUserIdentities(userId);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
|
|
660
|
+
return this.userService.linkUserIdentity(userId, provider, providerId, profileData);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
|
|
664
|
+
return this.userService.updateUser(id, data) as Promise<UserData | null>;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async deleteUser(id: string): Promise<void> {
|
|
668
|
+
await this.userService.deleteUser(id);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async listUsers(): Promise<UserData[]> {
|
|
672
|
+
return this.userService.listUsers() as Promise<UserData[]>;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {
|
|
676
|
+
return this.userService.listUsersPaginated(options);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async updatePassword(id: string, passwordHash: string): Promise<void> {
|
|
680
|
+
await this.userService.updatePassword(id, passwordHash);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
async setEmailVerified(id: string, verified: boolean): Promise<void> {
|
|
684
|
+
await this.userService.setEmailVerified(id, verified);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async setVerificationToken(id: string, token: string | null): Promise<void> {
|
|
688
|
+
await this.userService.setVerificationToken(id, token);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async getUserByVerificationToken(token: string): Promise<UserData | null> {
|
|
692
|
+
return this.userService.getUserByVerificationToken(token) as Promise<UserData | null>;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async getUserRoles(userId: string): Promise<RoleData[]> {
|
|
696
|
+
return this.userService.getUserRoles(userId);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async getUserRoleIds(userId: string): Promise<string[]> {
|
|
700
|
+
return this.userService.getUserRoleIds(userId);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
|
|
704
|
+
await this.userService.setUserRoles(userId, roleIds);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async assignDefaultRole(userId: string, roleId: string): Promise<void> {
|
|
708
|
+
await this.userService.assignDefaultRole(userId, roleId);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
|
|
712
|
+
const result = await this.userService.getUserWithRoles(userId);
|
|
713
|
+
return result as { user: UserData; roles: RoleData[] } | null;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Role operations (delegate to RoleService)
|
|
717
|
+
|
|
718
|
+
async getRoleById(id: string): Promise<RoleData | null> {
|
|
719
|
+
return this.roleService.getRoleById(id);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async listRoles(): Promise<RoleData[]> {
|
|
723
|
+
return this.roleService.listRoles();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async createRole(data: CreateRoleData): Promise<RoleData> {
|
|
727
|
+
return this.roleService.createRole({
|
|
728
|
+
...data,
|
|
729
|
+
defaultPermissions: data.defaultPermissions ?? null,
|
|
730
|
+
collectionPermissions: data.collectionPermissions ?? null,
|
|
731
|
+
config: data.config ?? null
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null> {
|
|
736
|
+
return this.roleService.updateRole(id, data);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async deleteRole(id: string): Promise<void> {
|
|
740
|
+
await this.roleService.deleteRole(id);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Token operations (delegate to PostgresTokenRepository)
|
|
744
|
+
|
|
745
|
+
async createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
|
|
746
|
+
await this.tokenRepository.createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
|
|
750
|
+
return this.tokenRepository.findRefreshTokenByHash(tokenHash);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
async deleteRefreshToken(tokenHash: string): Promise<void> {
|
|
754
|
+
await this.tokenRepository.deleteRefreshToken(tokenHash);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
|
|
758
|
+
await this.tokenRepository.deleteAllRefreshTokensForUser(userId);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
|
|
762
|
+
return this.tokenRepository.listRefreshTokensForUser(userId);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
|
|
766
|
+
await this.tokenRepository.deleteRefreshTokenById(id, userId);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
|
|
770
|
+
await this.tokenRepository.createPasswordResetToken(userId, tokenHash, expiresAt);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
|
|
774
|
+
return this.tokenRepository.findValidPasswordResetToken(tokenHash);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async markPasswordResetTokenUsed(tokenHash: string): Promise<void> {
|
|
778
|
+
await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
|
|
782
|
+
await this.tokenRepository.deleteAllPasswordResetTokensForUser(userId);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async deleteExpiredTokens(): Promise<void> {
|
|
786
|
+
await this.tokenRepository.deleteExpiredTokens();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// =============================================================================
|
|
791
|
+
// PostgreSQL Type Aliases (for consistent naming with other implementations)
|
|
792
|
+
// =============================================================================
|
|
793
|
+
|
|
794
|
+
/** PostgreSQL user repository implementation */
|
|
795
|
+
export type PostgresUserRepository = UserService;
|
|
796
|
+
|
|
797
|
+
/** PostgreSQL role repository implementation */
|
|
798
|
+
export type PostgresRoleRepository = RoleService;
|
|
799
|
+
|