@simple-auth-kit/cli 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +57 -22
- 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 +67 -19
- 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 +78 -20
- 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 +81 -26
- 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 +73 -21
- 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 +109 -29
- 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 +59 -28
- 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 +98 -27
- 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 +78 -20
- 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 +55 -27
- 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 +104 -29
- 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 +109 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +6 -4
- package/simple-auth-kit.ts +582 -124
- 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,10 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ConflictException,
|
|
3
|
+
Inject,
|
|
4
|
+
Injectable,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
} from "@nestjs/common";
|
|
2
7
|
import { and, asc, eq, inArray } from "drizzle-orm";
|
|
3
|
-
import { DRIZZLE_DB, type Database } from "
|
|
4
|
-
import {
|
|
8
|
+
import { DRIZZLE_DB, type Database } from "../db.js";
|
|
9
|
+
import {
|
|
10
|
+
provisionDefaultRoles,
|
|
11
|
+
WORKSPACE_CREATOR_ROLES,
|
|
12
|
+
} from "../rbac.defaults.js";
|
|
5
13
|
import { RbacRepository } from "./rbac.repository.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
14
|
+
import {
|
|
15
|
+
roleMember,
|
|
16
|
+
roles,
|
|
17
|
+
users,
|
|
18
|
+
workspaceMembers,
|
|
19
|
+
workspaces,
|
|
20
|
+
} from "../schema.js";
|
|
21
|
+
import { toId, toIdOrNull } from "../id.helper.js";
|
|
8
22
|
|
|
9
23
|
export interface WorkspaceSummary {
|
|
10
24
|
id: string;
|
|
@@ -43,16 +57,34 @@ export class WorkspaceRepository {
|
|
|
43
57
|
async create(userId: string, name: string): Promise<WorkspaceSummary> {
|
|
44
58
|
const userIdBig = toId(userId);
|
|
45
59
|
const workspace = await this.db.transaction(async (tx) => {
|
|
46
|
-
const [created] = await tx
|
|
60
|
+
const [created] = await tx
|
|
61
|
+
.insert(workspaces)
|
|
62
|
+
.values({ name, createdBy: userIdBig, updatedBy: userIdBig })
|
|
63
|
+
.returning();
|
|
47
64
|
await provisionDefaultRoles(tx, created.id);
|
|
48
65
|
|
|
49
66
|
const creatorRoles = await tx
|
|
50
67
|
.select({ id: roles.id })
|
|
51
68
|
.from(roles)
|
|
52
|
-
.where(
|
|
53
|
-
|
|
69
|
+
.where(
|
|
70
|
+
and(
|
|
71
|
+
eq(roles.workspaceId, created.id),
|
|
72
|
+
inArray(roles.slug, WORKSPACE_CREATOR_ROLES),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
const [member] = await tx
|
|
76
|
+
.insert(workspaceMembers)
|
|
77
|
+
.values({ workspaceId: created.id, userId: userIdBig })
|
|
78
|
+
.returning({ id: workspaceMembers.id });
|
|
54
79
|
if (creatorRoles.length) {
|
|
55
|
-
await tx
|
|
80
|
+
await tx
|
|
81
|
+
.insert(roleMember)
|
|
82
|
+
.values(
|
|
83
|
+
creatorRoles.map((role) => ({
|
|
84
|
+
memberId: member.id,
|
|
85
|
+
roleId: role.id,
|
|
86
|
+
})),
|
|
87
|
+
);
|
|
56
88
|
}
|
|
57
89
|
return created;
|
|
58
90
|
});
|
|
@@ -74,11 +106,19 @@ export class WorkspaceRepository {
|
|
|
74
106
|
createdAt: workspaces.createdAt,
|
|
75
107
|
})
|
|
76
108
|
.from(workspaceMembers)
|
|
77
|
-
.innerJoin(
|
|
109
|
+
.innerJoin(
|
|
110
|
+
workspaces,
|
|
111
|
+
and(
|
|
112
|
+
eq(workspaces.id, workspaceMembers.workspaceId),
|
|
113
|
+
eq(workspaces.isDeleted, false),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
78
116
|
.where(eq(workspaceMembers.userId, toId(userId)))
|
|
79
117
|
.orderBy(asc(workspaceMembers.createdAt));
|
|
80
118
|
|
|
81
|
-
const rolesByMember = await this.rolesByMember(
|
|
119
|
+
const rolesByMember = await this.rolesByMember(
|
|
120
|
+
memberships.map((m) => m.memberId),
|
|
121
|
+
);
|
|
82
122
|
return memberships.map((m) => ({
|
|
83
123
|
id: m.id.toString(),
|
|
84
124
|
name: m.name,
|
|
@@ -96,7 +136,10 @@ export class WorkspaceRepository {
|
|
|
96
136
|
email: users.email,
|
|
97
137
|
})
|
|
98
138
|
.from(workspaceMembers)
|
|
99
|
-
.innerJoin(
|
|
139
|
+
.innerJoin(
|
|
140
|
+
users,
|
|
141
|
+
and(eq(users.id, workspaceMembers.userId), eq(users.isDeleted, false)),
|
|
142
|
+
)
|
|
100
143
|
.where(eq(workspaceMembers.workspaceId, toId(workspaceId)))
|
|
101
144
|
.orderBy(asc(workspaceMembers.createdAt));
|
|
102
145
|
|
|
@@ -116,17 +159,32 @@ export class WorkspaceRepository {
|
|
|
116
159
|
* spelled in code. Named slugs are validated against this workspace: naming one that doesn't
|
|
117
160
|
* exist here is rejected rather than silently granting nothing.
|
|
118
161
|
*/
|
|
119
|
-
private async resolveMemberRoles(
|
|
162
|
+
private async resolveMemberRoles(
|
|
163
|
+
workspaceIdBig: bigint,
|
|
164
|
+
roleSlugs?: string[],
|
|
165
|
+
): Promise<{ id: bigint; slug: string }[]> {
|
|
120
166
|
const granted = await this.db
|
|
121
167
|
.select({ id: roles.id, slug: roles.slug })
|
|
122
168
|
.from(roles)
|
|
123
169
|
.where(
|
|
124
170
|
roleSlugs
|
|
125
|
-
? and(
|
|
126
|
-
|
|
171
|
+
? and(
|
|
172
|
+
eq(roles.workspaceId, workspaceIdBig),
|
|
173
|
+
inArray(roles.slug, roleSlugs),
|
|
174
|
+
)
|
|
175
|
+
: and(
|
|
176
|
+
eq(roles.workspaceId, workspaceIdBig),
|
|
177
|
+
eq(roles.isDefault, true),
|
|
178
|
+
eq(roles.isActive, true),
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
const unknown = (roleSlugs ?? []).filter(
|
|
182
|
+
(slug) => !granted.some((role) => role.slug === slug),
|
|
183
|
+
);
|
|
184
|
+
if (unknown.length)
|
|
185
|
+
throw new NotFoundException(
|
|
186
|
+
`role(s) not defined in this workspace: ${unknown.join(", ")}`,
|
|
127
187
|
);
|
|
128
|
-
const unknown = (roleSlugs ?? []).filter((slug) => !granted.some((role) => role.slug === slug));
|
|
129
|
-
if (unknown.length) throw new NotFoundException(`role(s) not defined in this workspace: ${unknown.join(", ")}`);
|
|
130
188
|
return granted;
|
|
131
189
|
}
|
|
132
190
|
|
|
@@ -135,23 +193,45 @@ export class WorkspaceRepository {
|
|
|
135
193
|
* consuming app's job. With no roles named, the new membership gets whichever of this
|
|
136
194
|
* workspace's roles are flagged `isDefault`, not a slug spelled in code.
|
|
137
195
|
*/
|
|
138
|
-
async addMember(
|
|
196
|
+
async addMember(
|
|
197
|
+
workspaceId: string,
|
|
198
|
+
email: string,
|
|
199
|
+
roleSlugs?: string[],
|
|
200
|
+
): Promise<MembershipSummary> {
|
|
139
201
|
const workspaceIdBig = toId(workspaceId);
|
|
140
|
-
const [user] = await this.db
|
|
202
|
+
const [user] = await this.db
|
|
203
|
+
.select({ id: users.id, email: users.email })
|
|
204
|
+
.from(users)
|
|
205
|
+
.where(eq(users.email, email))
|
|
206
|
+
.limit(1);
|
|
141
207
|
if (!user) throw new NotFoundException("no user with that email");
|
|
142
208
|
|
|
143
209
|
const [existing] = await this.db
|
|
144
210
|
.select({ id: workspaceMembers.id })
|
|
145
211
|
.from(workspaceMembers)
|
|
146
|
-
.where(
|
|
212
|
+
.where(
|
|
213
|
+
and(
|
|
214
|
+
eq(workspaceMembers.userId, user.id),
|
|
215
|
+
eq(workspaceMembers.workspaceId, workspaceIdBig),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
147
218
|
.limit(1);
|
|
148
|
-
if (existing)
|
|
219
|
+
if (existing)
|
|
220
|
+
throw new ConflictException("already a member of this workspace");
|
|
149
221
|
|
|
150
222
|
const granted = await this.resolveMemberRoles(workspaceIdBig, roleSlugs);
|
|
151
223
|
|
|
152
224
|
const member = await this.db.transaction(async (tx) => {
|
|
153
|
-
const [created] = await tx
|
|
154
|
-
|
|
225
|
+
const [created] = await tx
|
|
226
|
+
.insert(workspaceMembers)
|
|
227
|
+
.values({ workspaceId: workspaceIdBig, userId: user.id })
|
|
228
|
+
.returning();
|
|
229
|
+
if (granted.length)
|
|
230
|
+
await tx
|
|
231
|
+
.insert(roleMember)
|
|
232
|
+
.values(
|
|
233
|
+
granted.map((role) => ({ memberId: created.id, roleId: role.id })),
|
|
234
|
+
);
|
|
155
235
|
return created;
|
|
156
236
|
});
|
|
157
237
|
|
|
@@ -184,7 +264,11 @@ export class WorkspaceRepository {
|
|
|
184
264
|
actorUserId: string | null,
|
|
185
265
|
): Promise<MembershipSummary> {
|
|
186
266
|
const workspaceIdBig = toId(workspaceId);
|
|
187
|
-
const [existing] = await this.db
|
|
267
|
+
const [existing] = await this.db
|
|
268
|
+
.select({ id: users.id })
|
|
269
|
+
.from(users)
|
|
270
|
+
.where(eq(users.email, input.email))
|
|
271
|
+
.limit(1);
|
|
188
272
|
if (existing) throw new ConflictException("email already registered");
|
|
189
273
|
|
|
190
274
|
const granted = await this.resolveMemberRoles(workspaceIdBig, input.roles);
|
|
@@ -203,8 +287,16 @@ export class WorkspaceRepository {
|
|
|
203
287
|
.returning();
|
|
204
288
|
|
|
205
289
|
const member = await this.db.transaction(async (tx) => {
|
|
206
|
-
const [created] = await tx
|
|
207
|
-
|
|
290
|
+
const [created] = await tx
|
|
291
|
+
.insert(workspaceMembers)
|
|
292
|
+
.values({ workspaceId: workspaceIdBig, userId: user.id })
|
|
293
|
+
.returning();
|
|
294
|
+
if (granted.length)
|
|
295
|
+
await tx
|
|
296
|
+
.insert(roleMember)
|
|
297
|
+
.values(
|
|
298
|
+
granted.map((role) => ({ memberId: created.id, roleId: role.id })),
|
|
299
|
+
);
|
|
208
300
|
return created;
|
|
209
301
|
});
|
|
210
302
|
|
|
@@ -221,23 +313,32 @@ export class WorkspaceRepository {
|
|
|
221
313
|
const workspaceIdBig = toId(workspaceId);
|
|
222
314
|
const memberIdBig = toId(memberId);
|
|
223
315
|
const [member] = await this.db
|
|
224
|
-
.select({
|
|
316
|
+
.select({
|
|
317
|
+
id: workspaceMembers.id,
|
|
318
|
+
userId: workspaceMembers.userId,
|
|
319
|
+
workspaceId: workspaceMembers.workspaceId,
|
|
320
|
+
})
|
|
225
321
|
.from(workspaceMembers)
|
|
226
322
|
.where(eq(workspaceMembers.id, memberIdBig))
|
|
227
323
|
.limit(1);
|
|
228
|
-
if (!member || member.workspaceId !== workspaceIdBig)
|
|
324
|
+
if (!member || member.workspaceId !== workspaceIdBig)
|
|
325
|
+
throw new NotFoundException("member not found in this workspace");
|
|
229
326
|
|
|
230
327
|
// Role assignments and direct grants belong to the membership, so they go with it — that is
|
|
231
328
|
// the point of hanging them off the member row. `onDelete: "cascade"` on both join tables is
|
|
232
329
|
// what makes that a database guarantee rather than two deletes someone has to remember.
|
|
233
|
-
await this.db
|
|
330
|
+
await this.db
|
|
331
|
+
.delete(workspaceMembers)
|
|
332
|
+
.where(eq(workspaceMembers.id, memberIdBig));
|
|
234
333
|
// A removed member must lose access on their very next request, so their entry has to become
|
|
235
334
|
// unreachable — the resolution that replaces it returns "not a member", which is never cached.
|
|
236
335
|
await this.rbac.invalidateMember(member.userId.toString(), workspaceId);
|
|
237
336
|
}
|
|
238
337
|
|
|
239
338
|
/** One read for a page's role assignments rather than one per membership. */
|
|
240
|
-
private async rolesByMember(
|
|
339
|
+
private async rolesByMember(
|
|
340
|
+
memberIds: bigint[],
|
|
341
|
+
): Promise<Map<string, string[]>> {
|
|
241
342
|
if (!memberIds.length) return new Map();
|
|
242
343
|
const rows = await this.db
|
|
243
344
|
.select({ memberId: roleMember.memberId, slug: roles.slug })
|