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