@simple-auth-kit/cli 1.4.3 → 1.5.1
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/lib/copy.ts +1 -1
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +4 -4
- package/simple-auth-kit.ts +42 -11
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ConflictException,
|
|
3
|
+
Inject,
|
|
4
|
+
Injectable,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
} from "@nestjs/common";
|
|
2
7
|
import { resolvePermissions } from "@/lib/auth/core/rbac.js";
|
|
3
|
-
import { Prisma, PrismaClient } from "
|
|
4
|
-
import { PermissionCache } from "
|
|
5
|
-
import { toId, toIdOrNull } from "
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
|
|
9
|
+
import { PermissionCache } from "../permission-cache.js";
|
|
10
|
+
import { toId, toIdOrNull } from "../id.helper.js";
|
|
11
|
+
import {
|
|
12
|
+
buildPageMeta,
|
|
13
|
+
normalizeLimit,
|
|
14
|
+
normalizePage,
|
|
15
|
+
type Paginated,
|
|
16
|
+
} from "../pagination.js";
|
|
17
|
+
|
|
18
|
+
const USER_ROLES_INCLUDE = {
|
|
19
|
+
roles: { select: { role: { select: { slug: true } } } },
|
|
20
|
+
} as const;
|
|
9
21
|
|
|
10
22
|
/** A `User` row as `findUnique`/`findMany` return it with its roles joined in. */
|
|
11
|
-
export type UserRow = Prisma.UserGetPayload<{
|
|
23
|
+
export type UserRow = Prisma.UserGetPayload<{
|
|
24
|
+
include: typeof USER_ROLES_INCLUDE;
|
|
25
|
+
}>;
|
|
12
26
|
|
|
13
27
|
/**
|
|
14
28
|
* Shapes a raw row into the wire DTO — bigint id to string, `Date` to ISO, the roles relation
|
|
@@ -116,7 +130,9 @@ export interface RoleSummary {
|
|
|
116
130
|
permissions: string[];
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
const ROLE_PERMISSIONS_INCLUDE = {
|
|
133
|
+
const ROLE_PERMISSIONS_INCLUDE = {
|
|
134
|
+
permissions: { select: { permission: { select: { slug: true } } } },
|
|
135
|
+
} as const;
|
|
120
136
|
|
|
121
137
|
const PERMISSION_SELECT = {
|
|
122
138
|
id: true,
|
|
@@ -130,7 +146,14 @@ const PERMISSION_SELECT = {
|
|
|
130
146
|
isActive: true,
|
|
131
147
|
} as const;
|
|
132
148
|
|
|
133
|
-
const ROLE_SELECT = {
|
|
149
|
+
const ROLE_SELECT = {
|
|
150
|
+
id: true,
|
|
151
|
+
slug: true,
|
|
152
|
+
name: true,
|
|
153
|
+
displayName: true,
|
|
154
|
+
isDefault: true,
|
|
155
|
+
isActive: true,
|
|
156
|
+
} as const;
|
|
134
157
|
|
|
135
158
|
@Injectable()
|
|
136
159
|
export class RbacRepository {
|
|
@@ -146,7 +169,9 @@ export class RbacRepository {
|
|
|
146
169
|
// The hot path: every authenticated request runs this once. Walks user → role_user → roles →
|
|
147
170
|
// permission_role → permissions, and user → permission_user → permissions — constant cost in
|
|
148
171
|
// the number of users, since every hop is an indexed read.
|
|
149
|
-
async resolveAuthzContext(
|
|
172
|
+
async resolveAuthzContext(
|
|
173
|
+
userId: string,
|
|
174
|
+
): Promise<{ roles: string[]; permissions: string[] }> {
|
|
150
175
|
const user = await this.prisma.user.findUnique({
|
|
151
176
|
where: { id: toId(userId), isDeleted: false },
|
|
152
177
|
select: {
|
|
@@ -156,17 +181,25 @@ export class RbacRepository {
|
|
|
156
181
|
role: {
|
|
157
182
|
select: {
|
|
158
183
|
slug: true,
|
|
159
|
-
permissions: {
|
|
184
|
+
permissions: {
|
|
185
|
+
where: { permission: { isActive: true, isDeleted: false } },
|
|
186
|
+
select: { permission: { select: { slug: true } } },
|
|
187
|
+
},
|
|
160
188
|
},
|
|
161
189
|
},
|
|
162
190
|
},
|
|
163
191
|
},
|
|
164
|
-
permissions: {
|
|
192
|
+
permissions: {
|
|
193
|
+
where: { permission: { isActive: true, isDeleted: false } },
|
|
194
|
+
select: { permission: { select: { slug: true } } },
|
|
195
|
+
},
|
|
165
196
|
},
|
|
166
197
|
});
|
|
167
198
|
if (!user) return { roles: [], permissions: [] };
|
|
168
199
|
|
|
169
|
-
const rolePermissions = user.roles.flatMap((roleUser) =>
|
|
200
|
+
const rolePermissions = user.roles.flatMap((roleUser) =>
|
|
201
|
+
roleUser.role.permissions.map((pr) => pr.permission.slug),
|
|
202
|
+
);
|
|
170
203
|
const directPermissions = user.permissions.map((pu) => pu.permission.slug);
|
|
171
204
|
|
|
172
205
|
return {
|
|
@@ -185,7 +218,9 @@ export class RbacRepository {
|
|
|
185
218
|
const limit = normalizeLimit(filter.limit);
|
|
186
219
|
const where = {
|
|
187
220
|
isDeleted: false,
|
|
188
|
-
email: filter.search
|
|
221
|
+
email: filter.search
|
|
222
|
+
? { contains: filter.search, mode: "insensitive" as const }
|
|
223
|
+
: undefined,
|
|
189
224
|
};
|
|
190
225
|
|
|
191
226
|
const [rows, total] = await this.prisma.$transaction([
|
|
@@ -233,7 +268,9 @@ export class RbacRepository {
|
|
|
233
268
|
},
|
|
234
269
|
actorUserId: string | null,
|
|
235
270
|
): Promise<UserSummary> {
|
|
236
|
-
const existing = await this.prisma.user.findUnique({
|
|
271
|
+
const existing = await this.prisma.user.findUnique({
|
|
272
|
+
where: { email: input.email },
|
|
273
|
+
});
|
|
237
274
|
if (existing) throw new ConflictException("email already registered");
|
|
238
275
|
|
|
239
276
|
const user = await this.prisma.user.create({
|
|
@@ -255,9 +292,15 @@ export class RbacRepository {
|
|
|
255
292
|
});
|
|
256
293
|
|
|
257
294
|
if (input.roles && input.roles.length > 0) {
|
|
258
|
-
const roles = await this.prisma.role.findMany({
|
|
295
|
+
const roles = await this.prisma.role.findMany({
|
|
296
|
+
where: { slug: { in: input.roles }, isActive: true, isDeleted: false },
|
|
297
|
+
select: { id: true },
|
|
298
|
+
});
|
|
259
299
|
if (roles.length > 0) {
|
|
260
|
-
await this.prisma.roleUser.createMany({
|
|
300
|
+
await this.prisma.roleUser.createMany({
|
|
301
|
+
data: roles.map((role) => ({ userId: user.id, roleId: role.id })),
|
|
302
|
+
skipDuplicates: true,
|
|
303
|
+
});
|
|
261
304
|
await this.cache.invalidateSubject(user.id.toString());
|
|
262
305
|
}
|
|
263
306
|
} else {
|
|
@@ -285,15 +328,26 @@ export class RbacRepository {
|
|
|
285
328
|
actorUserId: string | null,
|
|
286
329
|
): Promise<UserSummary> {
|
|
287
330
|
const userIdBig = toId(userId);
|
|
288
|
-
const existing = await this.prisma.user.findUnique({
|
|
331
|
+
const existing = await this.prisma.user.findUnique({
|
|
332
|
+
where: { id: userIdBig, isDeleted: false },
|
|
333
|
+
select: { id: true },
|
|
334
|
+
});
|
|
289
335
|
if (!existing) throw new NotFoundException(`user "${userId}" not found`);
|
|
290
336
|
await this.prisma.user.update({
|
|
291
337
|
where: { id: userIdBig },
|
|
292
338
|
data: {
|
|
293
339
|
...input,
|
|
294
340
|
// Date columns need Date values; `undefined` leaves them alone, like every other field here.
|
|
295
|
-
dob:
|
|
296
|
-
|
|
341
|
+
dob:
|
|
342
|
+
input.dob === undefined
|
|
343
|
+
? undefined
|
|
344
|
+
: input.dob === null
|
|
345
|
+
? null
|
|
346
|
+
: new Date(input.dob),
|
|
347
|
+
joinedDate:
|
|
348
|
+
input.joinedDate === undefined
|
|
349
|
+
? undefined
|
|
350
|
+
: new Date(input.joinedDate),
|
|
297
351
|
updatedBy: toIdOrNull(actorUserId),
|
|
298
352
|
},
|
|
299
353
|
});
|
|
@@ -303,24 +357,45 @@ export class RbacRepository {
|
|
|
303
357
|
// Soft-delete, matching every other table's `isDeleted`/`deletedAt`/`deletedBy`/`deletedReason`
|
|
304
358
|
// pattern — the row survives for audit purposes, it just stops appearing in listings and can
|
|
305
359
|
// no longer authenticate (mirrors how `block` already disables login without removing the row).
|
|
306
|
-
async deleteUser(
|
|
360
|
+
async deleteUser(
|
|
361
|
+
userId: string,
|
|
362
|
+
actorUserId: string | null,
|
|
363
|
+
reason?: string,
|
|
364
|
+
): Promise<void> {
|
|
307
365
|
const userIdBig = toId(userId);
|
|
308
|
-
const existing = await this.prisma.user.findUnique({
|
|
366
|
+
const existing = await this.prisma.user.findUnique({
|
|
367
|
+
where: { id: userIdBig, isDeleted: false },
|
|
368
|
+
select: { id: true },
|
|
369
|
+
});
|
|
309
370
|
if (!existing) throw new NotFoundException(`user "${userId}" not found`);
|
|
310
371
|
await this.prisma.user.update({
|
|
311
372
|
where: { id: userIdBig },
|
|
312
|
-
data: {
|
|
373
|
+
data: {
|
|
374
|
+
isDeleted: true,
|
|
375
|
+
deletedAt: new Date(),
|
|
376
|
+
deletedBy: toIdOrNull(actorUserId),
|
|
377
|
+
deletedReason: reason ?? null,
|
|
378
|
+
},
|
|
313
379
|
});
|
|
314
380
|
await this.cache.invalidateSubject(userId);
|
|
315
381
|
}
|
|
316
382
|
|
|
317
383
|
async updateRole(
|
|
318
384
|
roleId: string,
|
|
319
|
-
input: {
|
|
385
|
+
input: {
|
|
386
|
+
name?: string;
|
|
387
|
+
displayName?: string;
|
|
388
|
+
description?: string | null;
|
|
389
|
+
isDefault?: boolean;
|
|
390
|
+
isActive?: boolean;
|
|
391
|
+
},
|
|
320
392
|
actorUserId: string | null,
|
|
321
393
|
): Promise<RoleSummary> {
|
|
322
394
|
const roleIdBig = toId(roleId);
|
|
323
|
-
const existing = await this.prisma.role.findUnique({
|
|
395
|
+
const existing = await this.prisma.role.findUnique({
|
|
396
|
+
where: { id: roleIdBig, isDeleted: false },
|
|
397
|
+
select: { id: true },
|
|
398
|
+
});
|
|
324
399
|
if (!existing) throw new NotFoundException("role not found");
|
|
325
400
|
const role = await this.prisma.role.update({
|
|
326
401
|
where: { id: roleIdBig },
|
|
@@ -329,19 +404,35 @@ export class RbacRepository {
|
|
|
329
404
|
});
|
|
330
405
|
// Renaming/deactivating changes what every holder resolves to.
|
|
331
406
|
await this.cache.invalidatePolicy();
|
|
332
|
-
return {
|
|
407
|
+
return {
|
|
408
|
+
...role,
|
|
409
|
+
id: role.id.toString(),
|
|
410
|
+
permissions: role.permissions.map((p) => p.permission.slug).sort(),
|
|
411
|
+
};
|
|
333
412
|
}
|
|
334
413
|
|
|
335
414
|
// Soft-delete. The `role_user`/`permission_role` rows pointing at it are left in place — a
|
|
336
415
|
// deleted role simply stops being returned by `listRoles`/`resolveAuthzContext`, rather than
|
|
337
416
|
// cascading a delete that would silently strip a user's other roles' foreign keys.
|
|
338
|
-
async deleteRole(
|
|
417
|
+
async deleteRole(
|
|
418
|
+
roleId: string,
|
|
419
|
+
actorUserId: string | null,
|
|
420
|
+
reason?: string,
|
|
421
|
+
): Promise<void> {
|
|
339
422
|
const roleIdBig = toId(roleId);
|
|
340
|
-
const existing = await this.prisma.role.findUnique({
|
|
423
|
+
const existing = await this.prisma.role.findUnique({
|
|
424
|
+
where: { id: roleIdBig, isDeleted: false },
|
|
425
|
+
select: { id: true },
|
|
426
|
+
});
|
|
341
427
|
if (!existing) throw new NotFoundException("role not found");
|
|
342
428
|
await this.prisma.role.update({
|
|
343
429
|
where: { id: roleIdBig },
|
|
344
|
-
data: {
|
|
430
|
+
data: {
|
|
431
|
+
isDeleted: true,
|
|
432
|
+
deletedAt: new Date(),
|
|
433
|
+
deletedBy: toIdOrNull(actorUserId),
|
|
434
|
+
deletedReason: reason ?? null,
|
|
435
|
+
},
|
|
345
436
|
});
|
|
346
437
|
await this.cache.invalidatePolicy();
|
|
347
438
|
}
|
|
@@ -351,14 +442,22 @@ export class RbacRepository {
|
|
|
351
442
|
const rows = await this.prisma.permission.findMany({
|
|
352
443
|
where: { isDeleted: false, isActive: activeOnly ? true : undefined },
|
|
353
444
|
select: PERMISSION_SELECT,
|
|
354
|
-
orderBy: [
|
|
445
|
+
orderBy: [
|
|
446
|
+
{ groupOrder: "asc" },
|
|
447
|
+
{ group: "asc" },
|
|
448
|
+
{ order: "asc" },
|
|
449
|
+
{ slug: "asc" },
|
|
450
|
+
],
|
|
355
451
|
});
|
|
356
452
|
return rows.map((row) => ({ ...row, id: row.id.toString() }));
|
|
357
453
|
}
|
|
358
454
|
|
|
359
455
|
// Upserted on `slug`, the stable identifier grants/revocations use, so renaming the display
|
|
360
456
|
// name never breaks a grant.
|
|
361
|
-
async upsertPermission(
|
|
457
|
+
async upsertPermission(
|
|
458
|
+
input: PermissionInput,
|
|
459
|
+
actorUserId: string | null,
|
|
460
|
+
): Promise<PermissionSummary> {
|
|
362
461
|
const shared = {
|
|
363
462
|
name: input.name,
|
|
364
463
|
displayName: input.displayName,
|
|
@@ -394,11 +493,22 @@ export class RbacRepository {
|
|
|
394
493
|
select: { ...ROLE_SELECT, ...ROLE_PERMISSIONS_INCLUDE },
|
|
395
494
|
orderBy: [{ order: "asc" }, { slug: "asc" }],
|
|
396
495
|
});
|
|
397
|
-
return rows.map((row) => ({
|
|
496
|
+
return rows.map((row) => ({
|
|
497
|
+
...row,
|
|
498
|
+
id: row.id.toString(),
|
|
499
|
+
permissions: row.permissions.map((p) => p.permission.slug).sort(),
|
|
500
|
+
}));
|
|
398
501
|
}
|
|
399
502
|
|
|
400
503
|
async createRole(
|
|
401
|
-
input: {
|
|
504
|
+
input: {
|
|
505
|
+
slug: string;
|
|
506
|
+
name?: string;
|
|
507
|
+
displayName?: string;
|
|
508
|
+
description?: string | null;
|
|
509
|
+
isDefault?: boolean;
|
|
510
|
+
isActive?: boolean;
|
|
511
|
+
},
|
|
402
512
|
actorUserId: string | null,
|
|
403
513
|
): Promise<RoleSummary> {
|
|
404
514
|
const role = await this.prisma.role.create({
|
|
@@ -417,38 +527,64 @@ export class RbacRepository {
|
|
|
417
527
|
return { ...role, id: role.id.toString(), permissions: [] };
|
|
418
528
|
}
|
|
419
529
|
|
|
420
|
-
async attachPermissionToRole(
|
|
530
|
+
async attachPermissionToRole(
|
|
531
|
+
roleId: string,
|
|
532
|
+
permissionSlug: string,
|
|
533
|
+
actorUserId: string | null,
|
|
534
|
+
): Promise<void> {
|
|
421
535
|
const roleIdBig = toId(roleId);
|
|
422
|
-
const role = await this.prisma.role.findUnique({
|
|
536
|
+
const role = await this.prisma.role.findUnique({
|
|
537
|
+
where: { id: roleIdBig, isDeleted: false },
|
|
538
|
+
select: { id: true },
|
|
539
|
+
});
|
|
423
540
|
if (!role) throw new NotFoundException("role not found");
|
|
424
541
|
|
|
425
542
|
const permission = await this.ensurePermission(permissionSlug, actorUserId);
|
|
426
543
|
await this.prisma.permissionRole.upsert({
|
|
427
|
-
where: {
|
|
544
|
+
where: {
|
|
545
|
+
permissionId_roleId: { permissionId: permission.id, roleId: roleIdBig },
|
|
546
|
+
},
|
|
428
547
|
create: { permissionId: permission.id, roleId: roleIdBig },
|
|
429
548
|
update: {},
|
|
430
549
|
});
|
|
431
550
|
await this.cache.invalidatePolicy(); // everyone holding this role is affected
|
|
432
551
|
}
|
|
433
552
|
|
|
434
|
-
async detachPermissionFromRole(
|
|
553
|
+
async detachPermissionFromRole(
|
|
554
|
+
roleId: string,
|
|
555
|
+
permissionSlug: string,
|
|
556
|
+
): Promise<void> {
|
|
435
557
|
const roleIdBig = toId(roleId);
|
|
436
|
-
const role = await this.prisma.role.findUnique({
|
|
558
|
+
const role = await this.prisma.role.findUnique({
|
|
559
|
+
where: { id: roleIdBig, isDeleted: false },
|
|
560
|
+
select: { id: true },
|
|
561
|
+
});
|
|
437
562
|
if (!role) throw new NotFoundException("role not found");
|
|
438
563
|
|
|
439
|
-
const permission = await this.prisma.permission.findUnique({
|
|
564
|
+
const permission = await this.prisma.permission.findUnique({
|
|
565
|
+
where: { slug: permissionSlug },
|
|
566
|
+
select: { id: true },
|
|
567
|
+
});
|
|
440
568
|
if (!permission) throw new NotFoundException("permission not found");
|
|
441
569
|
|
|
442
|
-
await this.prisma.permissionRole.deleteMany({
|
|
570
|
+
await this.prisma.permissionRole.deleteMany({
|
|
571
|
+
where: { roleId: roleIdBig, permissionId: permission.id },
|
|
572
|
+
});
|
|
443
573
|
await this.cache.invalidatePolicy(); // everyone holding this role is affected
|
|
444
574
|
}
|
|
445
575
|
|
|
446
576
|
async assignRoleToUser(userId: string, roleSlug: string): Promise<void> {
|
|
447
577
|
const userIdBig = toId(userId);
|
|
448
|
-
const role = await this.prisma.role.findUnique({
|
|
578
|
+
const role = await this.prisma.role.findUnique({
|
|
579
|
+
where: { slug: roleSlug, isDeleted: false },
|
|
580
|
+
select: { id: true },
|
|
581
|
+
});
|
|
449
582
|
if (!role) throw new NotFoundException(`role "${roleSlug}" not found`);
|
|
450
583
|
|
|
451
|
-
await this.prisma.user.findUniqueOrThrow({
|
|
584
|
+
await this.prisma.user.findUniqueOrThrow({
|
|
585
|
+
where: { id: userIdBig },
|
|
586
|
+
select: { id: true },
|
|
587
|
+
});
|
|
452
588
|
await this.prisma.roleUser.upsert({
|
|
453
589
|
where: { userId_roleId: { userId: userIdBig, roleId: role.id } },
|
|
454
590
|
create: { userId: userIdBig, roleId: role.id },
|
|
@@ -458,9 +594,14 @@ export class RbacRepository {
|
|
|
458
594
|
}
|
|
459
595
|
|
|
460
596
|
async revokeRoleFromUser(userId: string, roleSlug: string): Promise<void> {
|
|
461
|
-
const role = await this.prisma.role.findUnique({
|
|
597
|
+
const role = await this.prisma.role.findUnique({
|
|
598
|
+
where: { slug: roleSlug },
|
|
599
|
+
select: { id: true },
|
|
600
|
+
});
|
|
462
601
|
if (!role) return;
|
|
463
|
-
await this.prisma.roleUser.deleteMany({
|
|
602
|
+
await this.prisma.roleUser.deleteMany({
|
|
603
|
+
where: { userId: toId(userId), roleId: role.id },
|
|
604
|
+
});
|
|
464
605
|
await this.cache.invalidateSubject(userId);
|
|
465
606
|
}
|
|
466
607
|
|
|
@@ -468,9 +609,15 @@ export class RbacRepository {
|
|
|
468
609
|
// Takes the bigint directly: every caller already has the user row in hand (just created or
|
|
469
610
|
// just fetched), so there's no string to convert from.
|
|
470
611
|
async assignDefaultRoles(userId: bigint): Promise<void> {
|
|
471
|
-
const defaults = await this.prisma.role.findMany({
|
|
612
|
+
const defaults = await this.prisma.role.findMany({
|
|
613
|
+
where: { isDefault: true, isActive: true, isDeleted: false },
|
|
614
|
+
select: { id: true },
|
|
615
|
+
});
|
|
472
616
|
if (!defaults.length) return;
|
|
473
|
-
await this.prisma.roleUser.createMany({
|
|
617
|
+
await this.prisma.roleUser.createMany({
|
|
618
|
+
data: defaults.map((role) => ({ userId, roleId: role.id })),
|
|
619
|
+
skipDuplicates: true,
|
|
620
|
+
});
|
|
474
621
|
await this.cache.invalidateSubject(userId.toString());
|
|
475
622
|
}
|
|
476
623
|
|
|
@@ -478,21 +625,35 @@ export class RbacRepository {
|
|
|
478
625
|
return (await this.prisma.roleUser.count({ where: { userId } })) > 0;
|
|
479
626
|
}
|
|
480
627
|
|
|
481
|
-
async grantPermissionToUser(
|
|
628
|
+
async grantPermissionToUser(
|
|
629
|
+
userId: string,
|
|
630
|
+
permissionSlug: string,
|
|
631
|
+
actorUserId: string | null,
|
|
632
|
+
): Promise<void> {
|
|
482
633
|
const userIdBig = toId(userId);
|
|
483
634
|
const permission = await this.ensurePermission(permissionSlug, actorUserId);
|
|
484
635
|
await this.prisma.permissionUser.upsert({
|
|
485
|
-
where: {
|
|
636
|
+
where: {
|
|
637
|
+
userId_permissionId: { userId: userIdBig, permissionId: permission.id },
|
|
638
|
+
},
|
|
486
639
|
create: { userId: userIdBig, permissionId: permission.id },
|
|
487
640
|
update: {},
|
|
488
641
|
});
|
|
489
642
|
await this.cache.invalidateSubject(userId);
|
|
490
643
|
}
|
|
491
644
|
|
|
492
|
-
async revokePermissionFromUser(
|
|
493
|
-
|
|
645
|
+
async revokePermissionFromUser(
|
|
646
|
+
userId: string,
|
|
647
|
+
permissionSlug: string,
|
|
648
|
+
): Promise<void> {
|
|
649
|
+
const permission = await this.prisma.permission.findUnique({
|
|
650
|
+
where: { slug: permissionSlug },
|
|
651
|
+
select: { id: true },
|
|
652
|
+
});
|
|
494
653
|
if (!permission) return;
|
|
495
|
-
await this.prisma.permissionUser.deleteMany({
|
|
654
|
+
await this.prisma.permissionUser.deleteMany({
|
|
655
|
+
where: { userId: toId(userId), permissionId: permission.id },
|
|
656
|
+
});
|
|
496
657
|
await this.cache.invalidateSubject(userId);
|
|
497
658
|
}
|
|
498
659
|
|
|
@@ -501,12 +662,25 @@ export class RbacRepository {
|
|
|
501
662
|
}
|
|
502
663
|
|
|
503
664
|
// Creates the row for a slug granted before anyone defined it; leaves an existing definition alone.
|
|
504
|
-
private async ensurePermission(
|
|
505
|
-
|
|
665
|
+
private async ensurePermission(
|
|
666
|
+
slug: string,
|
|
667
|
+
actorUserId: string | null,
|
|
668
|
+
): Promise<{ id: bigint }> {
|
|
669
|
+
const existing = await this.prisma.permission.findUnique({
|
|
670
|
+
where: { slug },
|
|
671
|
+
select: { id: true },
|
|
672
|
+
});
|
|
506
673
|
return (
|
|
507
674
|
existing ??
|
|
508
675
|
(await this.prisma.permission.create({
|
|
509
|
-
data: {
|
|
676
|
+
data: {
|
|
677
|
+
slug,
|
|
678
|
+
name: slug,
|
|
679
|
+
displayName: slug,
|
|
680
|
+
group: "Custom",
|
|
681
|
+
createdBy: toIdOrNull(actorUserId),
|
|
682
|
+
updatedBy: toIdOrNull(actorUserId),
|
|
683
|
+
},
|
|
510
684
|
select: { id: true },
|
|
511
685
|
}))
|
|
512
686
|
);
|
|
@@ -6,43 +6,81 @@
|
|
|
6
6
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
7
7
|
import { hashPassword } from "@/lib/auth/core/crypto.js";
|
|
8
8
|
import { PrismaClient } from "../generated/prisma/client.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_ROLES,
|
|
11
|
+
PERMISSION_SLUGS,
|
|
12
|
+
provisionDefaultRoles,
|
|
13
|
+
SEED_ADMIN_ROLES,
|
|
14
|
+
SEED_SUPERADMIN_ROLES,
|
|
15
|
+
} from "./rbac.defaults.js";
|
|
10
16
|
|
|
11
17
|
function requireEnv(name: string): string {
|
|
12
18
|
const value = process.env[name];
|
|
13
|
-
if (!value)
|
|
19
|
+
if (!value)
|
|
20
|
+
throw new Error(
|
|
21
|
+
`${name} is not set — the seeder cannot reach the database without it`,
|
|
22
|
+
);
|
|
14
23
|
return value;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
async function seedRbacDefaults(prisma: PrismaClient): Promise<void> {
|
|
18
27
|
await provisionDefaultRoles(prisma);
|
|
19
28
|
console.log(`permissions: ${PERMISSION_SLUGS.length} slug(s) in the catalog`);
|
|
20
|
-
for (const role of DEFAULT_ROLES)
|
|
29
|
+
for (const role of DEFAULT_ROLES)
|
|
30
|
+
console.log(
|
|
31
|
+
`role "${role.slug}": ${role.permissions.length} permission(s)${role.isDefault ? " (signup default)" : ""}`,
|
|
32
|
+
);
|
|
21
33
|
|
|
22
34
|
// Slugs in the database but not this build's catalog aren't an error — a deployment can
|
|
23
35
|
// define its own — but a typo would otherwise silently grant nothing, so surface them.
|
|
24
|
-
const rows = await prisma.permission.findMany({
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
const rows = await prisma.permission.findMany({
|
|
37
|
+
select: { slug: true, isActive: true },
|
|
38
|
+
});
|
|
39
|
+
const unknown = rows.filter(
|
|
40
|
+
(row) => !(PERMISSION_SLUGS as string[]).includes(row.slug),
|
|
41
|
+
);
|
|
42
|
+
if (unknown.length)
|
|
43
|
+
console.log(
|
|
44
|
+
`permissions: ${unknown.length} slug(s) outside this build's catalog (no route names them): ${unknown.map((r) => r.slug).join(", ")}`,
|
|
45
|
+
);
|
|
27
46
|
const inactive = rows.filter((row) => !row.isActive);
|
|
28
|
-
if (inactive.length)
|
|
47
|
+
if (inactive.length)
|
|
48
|
+
console.log(
|
|
49
|
+
`permissions: ${inactive.length} deactivated, granting nothing: ${inactive.map((r) => r.slug).join(", ")}`,
|
|
50
|
+
);
|
|
29
51
|
}
|
|
30
52
|
|
|
31
53
|
async function seedAdminUser(prisma: PrismaClient): Promise<void> {
|
|
32
54
|
const email = process.env["SEED_ADMIN_EMAIL"];
|
|
33
55
|
const password = process.env["SEED_ADMIN_PASSWORD"];
|
|
34
56
|
if (!email || !password) {
|
|
35
|
-
console.log(
|
|
57
|
+
console.log(
|
|
58
|
+
"admin: skipped — set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD to create one (there is no default password)",
|
|
59
|
+
);
|
|
36
60
|
return;
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
// Rewriting the password here would silently reset one the user may have already changed.
|
|
40
64
|
const existing = await prisma.user.findUnique({ where: { email } });
|
|
41
|
-
const user =
|
|
42
|
-
|
|
65
|
+
const user =
|
|
66
|
+
existing ??
|
|
67
|
+
(await prisma.user.create({
|
|
68
|
+
data: { email, passwordHash: await hashPassword(password) },
|
|
69
|
+
}));
|
|
70
|
+
console.log(
|
|
71
|
+
existing
|
|
72
|
+
? `admin: ${email} already exists — password left unchanged`
|
|
73
|
+
: `admin: created ${email}`,
|
|
74
|
+
);
|
|
43
75
|
|
|
44
|
-
const roles = await prisma.role.findMany({
|
|
45
|
-
|
|
76
|
+
const roles = await prisma.role.findMany({
|
|
77
|
+
where: { slug: { in: SEED_ADMIN_ROLES } },
|
|
78
|
+
select: { id: true, slug: true },
|
|
79
|
+
});
|
|
80
|
+
await prisma.roleUser.createMany({
|
|
81
|
+
data: roles.map((role) => ({ userId: user.id, roleId: role.id })),
|
|
82
|
+
skipDuplicates: true,
|
|
83
|
+
});
|
|
46
84
|
console.log(`admin: holds roles ${roles.map((r) => r.slug).join(", ")}`);
|
|
47
85
|
}
|
|
48
86
|
|
|
@@ -50,19 +88,37 @@ async function seedSuperAdminUser(prisma: PrismaClient): Promise<void> {
|
|
|
50
88
|
const email = process.env["SEED_SUPERADMIN_EMAIL"];
|
|
51
89
|
const password = process.env["SEED_SUPERADMIN_PASSWORD"];
|
|
52
90
|
if (!email || !password) {
|
|
53
|
-
console.log(
|
|
91
|
+
console.log(
|
|
92
|
+
"super admin: skipped — set SEED_SUPERADMIN_EMAIL and SEED_SUPERADMIN_PASSWORD to create one (there is no default password)",
|
|
93
|
+
);
|
|
54
94
|
return;
|
|
55
95
|
}
|
|
56
96
|
const username = process.env["SEED_SUPERADMIN_USERNAME"] || undefined;
|
|
57
97
|
|
|
58
98
|
// Rewriting the password here would silently reset one the user may have already changed.
|
|
59
99
|
const existing = await prisma.user.findUnique({ where: { email } });
|
|
60
|
-
const user =
|
|
61
|
-
|
|
100
|
+
const user =
|
|
101
|
+
existing ??
|
|
102
|
+
(await prisma.user.create({
|
|
103
|
+
data: { email, username, passwordHash: await hashPassword(password) },
|
|
104
|
+
}));
|
|
105
|
+
console.log(
|
|
106
|
+
existing
|
|
107
|
+
? `super admin: ${email} already exists — password left unchanged`
|
|
108
|
+
: `super admin: created ${email}${username ? ` (username ${username})` : ""}`,
|
|
109
|
+
);
|
|
62
110
|
|
|
63
|
-
const roles = await prisma.role.findMany({
|
|
64
|
-
|
|
65
|
-
|
|
111
|
+
const roles = await prisma.role.findMany({
|
|
112
|
+
where: { slug: { in: SEED_SUPERADMIN_ROLES } },
|
|
113
|
+
select: { id: true, slug: true },
|
|
114
|
+
});
|
|
115
|
+
await prisma.roleUser.createMany({
|
|
116
|
+
data: roles.map((role) => ({ userId: user.id, roleId: role.id })),
|
|
117
|
+
skipDuplicates: true,
|
|
118
|
+
});
|
|
119
|
+
console.log(
|
|
120
|
+
`super admin: holds roles ${roles.map((r) => r.slug).join(", ")}`,
|
|
121
|
+
);
|
|
66
122
|
}
|
|
67
123
|
|
|
68
124
|
async function main(): Promise<void> {
|
|
@@ -72,7 +128,9 @@ async function main(): Promise<void> {
|
|
|
72
128
|
// No .env file — the environment is expected to carry the variables itself.
|
|
73
129
|
}
|
|
74
130
|
|
|
75
|
-
const prisma = new PrismaClient({
|
|
131
|
+
const prisma = new PrismaClient({
|
|
132
|
+
adapter: new PrismaPg({ connectionString: requireEnv("DATABASE_URL") }),
|
|
133
|
+
});
|
|
76
134
|
try {
|
|
77
135
|
await seedRbacDefaults(prisma);
|
|
78
136
|
await seedAdminUser(prisma);
|