@jskit-ai/users-core 0.1.48 → 0.1.49
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.descriptor.mjs +7 -7
- package/package.json +7 -17
- package/src/server/common/services/authProfileSyncService.js +28 -7
- package/src/server/common/support/realtimeServiceEvents.js +1 -59
- package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
- package/src/server/registerUsersBootstrap.js +0 -1
- package/src/server/registerUsersCore.js +2 -14
- package/src/server/usersBootstrapContributor.js +2 -64
- package/src/shared/index.js +2 -99
- package/src/shared/settings.js +1 -119
- package/test/authProfileSyncService.test.js +19 -10
- package/test/registerServiceRealtimeEvents.test.js +0 -86
- package/test/registerUsersCore.test.js +6 -15
- package/test/repositoryContracts.test.js +1 -9
- package/test/resourcesCanonical.test.js +0 -16
- package/test/settingsFieldRegistriesSingleton.test.js +0 -5
- package/test/usersBootstrapContributor.test.js +2 -26
- package/test/usersRouteResources.test.js +0 -16
- package/src/server/UsersWorkspacesServiceProvider.js +0 -44
- package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
- package/src/server/common/formatters/workspaceFormatter.js +0 -53
- package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
- package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
- package/src/server/common/repositories/workspacesRepository.js +0 -202
- package/src/server/common/services/workspaceContextService.js +0 -281
- package/src/server/common/support/workspaceRoutePaths.js +0 -17
- package/src/server/common/validators/routeParamsValidator.js +0 -62
- package/src/server/registerWorkspaceBootstrap.js +0 -27
- package/src/server/registerWorkspaceCore.js +0 -73
- package/src/server/registerWorkspaceRepositories.js +0 -26
- package/src/server/support/resolveWorkspace.js +0 -16
- package/src/server/support/workspaceActionSurfaces.js +0 -118
- package/src/server/support/workspaceInvitationsPolicy.js +0 -45
- package/src/server/support/workspaceRouteInput.js +0 -22
- package/src/server/workspaceBootstrapContributor.js +0 -212
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
- package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
- package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
- package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
- package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
- package/src/shared/resources/workspaceMembersResource.js +0 -354
- package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
- package/src/shared/resources/workspaceResource.js +0 -176
- package/src/shared/resources/workspaceSettingsFields.js +0 -59
- package/src/shared/resources/workspaceSettingsResource.js +0 -169
- package/src/shared/roles.js +0 -161
- package/src/shared/support/usersApiPaths.js +0 -43
- package/src/shared/support/usersVisibility.js +0 -42
- package/src/shared/support/workspacePathModel.js +0 -145
- package/src/shared/tenancyMode.js +0 -35
- package/src/shared/tenancyProfile.js +0 -73
- package/test/registerWorkspaceDirectory.test.js +0 -31
- package/test/registerWorkspaceSettings.test.js +0 -40
- package/test/roles.test.js +0 -159
- package/test/tenancyProfile.test.js +0 -67
- package/test/usersApiPaths.test.js +0 -49
- package/test/usersRouteValidators.test.js +0 -49
- package/test/usersVisibility.test.js +0 -27
- package/test/workspaceActionContextContributor.test.js +0 -344
- package/test/workspaceActionSurfaces.test.js +0 -85
- package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
- package/test/workspaceBootstrapContributor.test.js +0 -154
- package/test/workspaceInvitationsPolicy.test.js +0 -71
- package/test/workspaceInvitesRepository.test.js +0 -111
- package/test/workspaceMembersService.test.js +0 -398
- package/test/workspacePathModel.test.js +0 -93
- package/test/workspacePendingInvitationsResource.test.js +0 -38
- package/test/workspacePendingInvitationsService.test.js +0 -151
- package/test/workspaceRouteVisibilityResolver.test.js +0 -83
- package/test/workspaceService.test.js +0 -546
- package/test/workspaceSettingsActions.test.js +0 -52
- package/test/workspaceSettingsRepository.test.js +0 -202
- package/test/workspaceSettingsResource.test.js +0 -169
- package/test/workspaceSettingsService.test.js +0 -140
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { resolveWorkspaceThemePalettes } from "../../../shared/settings.js";
|
|
2
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
-
|
|
5
|
-
function mapWorkspaceSummary(workspace, membership) {
|
|
6
|
-
return {
|
|
7
|
-
id: normalizeRecordId(workspace.id, { fallback: "" }),
|
|
8
|
-
slug: normalizeText(workspace.slug),
|
|
9
|
-
name: normalizeText(workspace.name),
|
|
10
|
-
avatarUrl: normalizeText(workspace.avatarUrl),
|
|
11
|
-
roleSid: normalizeLowerText(membership?.roleSid || "member") || "member",
|
|
12
|
-
isAccessible: normalizeLowerText(membership?.status || "active") === "active"
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function mapWorkspaceSettingsPublic(workspaceSettings, { workspaceInvitationsEnabled = true } = {}) {
|
|
17
|
-
const source = workspaceSettings && typeof workspaceSettings === "object" ? workspaceSettings : {};
|
|
18
|
-
const invitesAvailable = workspaceInvitationsEnabled === true;
|
|
19
|
-
const invitesEnabled = invitesAvailable && source.invitesEnabled !== false;
|
|
20
|
-
const themePalettes = resolveWorkspaceThemePalettes(source);
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
lightPrimaryColor: themePalettes.light.color,
|
|
24
|
-
lightSecondaryColor: themePalettes.light.secondaryColor,
|
|
25
|
-
lightSurfaceColor: themePalettes.light.surfaceColor,
|
|
26
|
-
lightSurfaceVariantColor: themePalettes.light.surfaceVariantColor,
|
|
27
|
-
darkPrimaryColor: themePalettes.dark.color,
|
|
28
|
-
darkSecondaryColor: themePalettes.dark.secondaryColor,
|
|
29
|
-
darkSurfaceColor: themePalettes.dark.surfaceColor,
|
|
30
|
-
darkSurfaceVariantColor: themePalettes.dark.surfaceVariantColor,
|
|
31
|
-
invitesEnabled,
|
|
32
|
-
invitesAvailable,
|
|
33
|
-
invitesEffective: invitesAvailable && invitesEnabled
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function mapMembershipSummary(membership, workspace) {
|
|
38
|
-
if (!membership) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
workspaceId: normalizeRecordId(workspace?.id || membership.workspaceId, { fallback: "" }),
|
|
44
|
-
roleSid: normalizeLowerText(membership.roleSid || "member") || "member",
|
|
45
|
-
status: normalizeLowerText(membership.status || "active") || "active"
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export {
|
|
50
|
-
mapMembershipSummary,
|
|
51
|
-
mapWorkspaceSettingsPublic,
|
|
52
|
-
mapWorkspaceSummary
|
|
53
|
-
};
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
|
|
2
|
-
import {
|
|
3
|
-
normalizeLowerText,
|
|
4
|
-
normalizeDbRecordId,
|
|
5
|
-
normalizeRecordId,
|
|
6
|
-
normalizeText,
|
|
7
|
-
toIsoString,
|
|
8
|
-
toNullableIso,
|
|
9
|
-
toNullableDateTime,
|
|
10
|
-
nowDb,
|
|
11
|
-
isDuplicateEntryError,
|
|
12
|
-
createWithTransaction
|
|
13
|
-
} from "./repositoryUtils.js";
|
|
14
|
-
|
|
15
|
-
function mapRow(row) {
|
|
16
|
-
if (!row) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
22
|
-
workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" }),
|
|
23
|
-
email: normalizeLowerText(row.email),
|
|
24
|
-
roleSid: normalizeLowerText(row.role_sid || "member") || "member",
|
|
25
|
-
status: normalizeLowerText(row.status || "pending") || "pending",
|
|
26
|
-
tokenHash: normalizeText(row.token_hash),
|
|
27
|
-
invitedByUserId: row.invited_by_user_id == null ? null : normalizeDbRecordId(row.invited_by_user_id, { fallback: null }),
|
|
28
|
-
expiresAt: toNullableIso(row.expires_at),
|
|
29
|
-
acceptedAt: toNullableIso(row.accepted_at),
|
|
30
|
-
revokedAt: toNullableIso(row.revoked_at),
|
|
31
|
-
createdAt: toIsoString(row.created_at),
|
|
32
|
-
updatedAt: toIsoString(row.updated_at),
|
|
33
|
-
workspaceSlug: row.workspace_slug ? normalizeText(row.workspace_slug) : undefined,
|
|
34
|
-
workspaceName: row.workspace_name ? normalizeText(row.workspace_name) : undefined,
|
|
35
|
-
workspaceAvatarUrl: row.workspace_avatar_url ? normalizeText(row.workspace_avatar_url) : undefined
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const WORKSPACE_INVITE_WITH_WORKSPACE_SELECT = Object.freeze([
|
|
40
|
-
"wi.*",
|
|
41
|
-
"w.slug as workspace_slug",
|
|
42
|
-
"w.name as workspace_name",
|
|
43
|
-
"w.avatar_url as workspace_avatar_url"
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
function createRepository(knex) {
|
|
47
|
-
if (typeof knex !== "function") {
|
|
48
|
-
throw new TypeError("workspaceInvitesRepository requires knex.");
|
|
49
|
-
}
|
|
50
|
-
const withTransaction = createWithTransaction(knex);
|
|
51
|
-
|
|
52
|
-
async function findPendingByTokenHash(tokenHash, options = {}) {
|
|
53
|
-
const client = options?.trx || knex;
|
|
54
|
-
const row = await client("workspace_invites")
|
|
55
|
-
.where({ token_hash: normalizeText(tokenHash), status: "pending" })
|
|
56
|
-
.first();
|
|
57
|
-
return mapRow(row);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function listPendingByEmail(email, options = {}) {
|
|
61
|
-
const client = options?.trx || knex;
|
|
62
|
-
const normalizedEmail = normalizeLowerText(email);
|
|
63
|
-
if (!normalizedEmail) {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const rows = await client("workspace_invites as wi")
|
|
68
|
-
.join("workspaces as w", "w.id", "wi.workspace_id")
|
|
69
|
-
.where({ "wi.email": normalizedEmail, "wi.status": "pending" })
|
|
70
|
-
.orderBy("wi.created_at", "desc")
|
|
71
|
-
.select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
|
|
72
|
-
|
|
73
|
-
return rows.map(mapRow).filter(Boolean);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function listPendingByWorkspaceIdWithWorkspace(workspaceId, options = {}) {
|
|
77
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
78
|
-
if (!normalizedWorkspaceId) {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const client = options?.trx || knex;
|
|
83
|
-
const rows = await client("workspace_invites as wi")
|
|
84
|
-
.join("workspaces as w", "w.id", "wi.workspace_id")
|
|
85
|
-
.where({ "wi.workspace_id": normalizedWorkspaceId, "wi.status": "pending" })
|
|
86
|
-
.orderBy("wi.created_at", "desc")
|
|
87
|
-
.select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
|
|
88
|
-
|
|
89
|
-
return rows.map(mapRow).filter(Boolean);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function insert(payload = {}, options = {}) {
|
|
93
|
-
const client = options?.trx || knex;
|
|
94
|
-
const source = payload && typeof payload === "object" ? payload : {};
|
|
95
|
-
const workspaceId = normalizeRecordId(source.workspaceId, { fallback: null });
|
|
96
|
-
if (!workspaceId) {
|
|
97
|
-
throw new TypeError("workspaceInvitesRepository.insert requires workspaceId.");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const insertPayload = {
|
|
101
|
-
workspace_id: workspaceId,
|
|
102
|
-
email: normalizeLowerText(source.email),
|
|
103
|
-
role_sid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
104
|
-
status: normalizeLowerText(source.status || "pending") || "pending",
|
|
105
|
-
token_hash: normalizeText(source.tokenHash),
|
|
106
|
-
invited_by_user_id: source.invitedByUserId == null ? null : normalizeRecordId(source.invitedByUserId, { fallback: null }),
|
|
107
|
-
expires_at: toNullableDateTime(source.expiresAt),
|
|
108
|
-
accepted_at: null,
|
|
109
|
-
revoked_at: null,
|
|
110
|
-
created_at: nowDb(),
|
|
111
|
-
updated_at: nowDb()
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const result = await client("workspace_invites").insert(insertPayload);
|
|
116
|
-
const insertedId = resolveInsertedRecordId(result, { fallback: null });
|
|
117
|
-
if (insertedId) {
|
|
118
|
-
const row = await client("workspace_invites").where({ id: insertedId }).first();
|
|
119
|
-
return mapRow(row);
|
|
120
|
-
}
|
|
121
|
-
} catch (error) {
|
|
122
|
-
if (!isDuplicateEntryError(error)) {
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const row = await client("workspace_invites")
|
|
128
|
-
.where({ workspace_id: insertPayload.workspace_id, email: insertPayload.email, status: "pending" })
|
|
129
|
-
.orderBy("id", "desc")
|
|
130
|
-
.first();
|
|
131
|
-
return mapRow(row);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function expirePendingByWorkspaceIdAndEmail(workspaceId, email, options = {}) {
|
|
135
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
136
|
-
if (!normalizedWorkspaceId) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const client = options?.trx || knex;
|
|
141
|
-
await client("workspace_invites")
|
|
142
|
-
.where({ workspace_id: normalizedWorkspaceId, email: normalizeLowerText(email), status: "pending" })
|
|
143
|
-
.update({
|
|
144
|
-
status: "expired",
|
|
145
|
-
updated_at: nowDb()
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function markAcceptedById(inviteId, options = {}) {
|
|
150
|
-
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
151
|
-
if (!normalizedInviteId) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const client = options?.trx || knex;
|
|
156
|
-
await client("workspace_invites")
|
|
157
|
-
.where({ id: normalizedInviteId })
|
|
158
|
-
.update({
|
|
159
|
-
status: "accepted",
|
|
160
|
-
accepted_at: nowDb(),
|
|
161
|
-
updated_at: nowDb()
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async function revokeById(inviteId, options = {}) {
|
|
166
|
-
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
167
|
-
if (!normalizedInviteId) {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const client = options?.trx || knex;
|
|
172
|
-
await client("workspace_invites")
|
|
173
|
-
.where({ id: normalizedInviteId })
|
|
174
|
-
.update({
|
|
175
|
-
status: "revoked",
|
|
176
|
-
revoked_at: nowDb(),
|
|
177
|
-
updated_at: nowDb()
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function findPendingByIdForWorkspace(inviteId, workspaceId, options = {}) {
|
|
182
|
-
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
183
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
184
|
-
if (!normalizedInviteId || !normalizedWorkspaceId) {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const client = options?.trx || knex;
|
|
189
|
-
const row = await client("workspace_invites")
|
|
190
|
-
.where({ id: normalizedInviteId, workspace_id: normalizedWorkspaceId, status: "pending" })
|
|
191
|
-
.first();
|
|
192
|
-
return mapRow(row);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return Object.freeze({
|
|
196
|
-
withTransaction,
|
|
197
|
-
findPendingByTokenHash,
|
|
198
|
-
listPendingByEmail,
|
|
199
|
-
listPendingByWorkspaceIdWithWorkspace,
|
|
200
|
-
insert,
|
|
201
|
-
expirePendingByWorkspaceIdAndEmail,
|
|
202
|
-
markAcceptedById,
|
|
203
|
-
revokeById,
|
|
204
|
-
findPendingByIdForWorkspace
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export { createRepository, mapRow };
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeLowerText,
|
|
3
|
-
normalizeRecordId,
|
|
4
|
-
normalizeDbRecordId,
|
|
5
|
-
normalizeText,
|
|
6
|
-
toIsoString,
|
|
7
|
-
nowDb,
|
|
8
|
-
isDuplicateEntryError,
|
|
9
|
-
createWithTransaction
|
|
10
|
-
} from "./repositoryUtils.js";
|
|
11
|
-
import { OWNER_ROLE_ID } from "../../../shared/roles.js";
|
|
12
|
-
|
|
13
|
-
function mapRow(row) {
|
|
14
|
-
if (!row) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
20
|
-
workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" }),
|
|
21
|
-
userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
|
|
22
|
-
roleSid: normalizeLowerText(row.role_sid || "member") || "member",
|
|
23
|
-
status: normalizeLowerText(row.status || "active") || "active",
|
|
24
|
-
createdAt: toIsoString(row.created_at),
|
|
25
|
-
updatedAt: toIsoString(row.updated_at)
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function mapMemberSummaryRow(row) {
|
|
30
|
-
if (!row) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
|
|
36
|
-
roleSid: normalizeLowerText(row.role_sid || "member") || "member",
|
|
37
|
-
status: normalizeLowerText(row.status || "active") || "active",
|
|
38
|
-
displayName: normalizeText(row.display_name),
|
|
39
|
-
email: normalizeLowerText(row.email)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function createRepository(knex) {
|
|
44
|
-
if (typeof knex !== "function") {
|
|
45
|
-
throw new TypeError("workspaceMembershipsRepository requires knex.");
|
|
46
|
-
}
|
|
47
|
-
const withTransaction = createWithTransaction(knex);
|
|
48
|
-
|
|
49
|
-
async function findByWorkspaceIdAndUserId(workspaceId, userId, options = {}) {
|
|
50
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
51
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
52
|
-
if (!normalizedWorkspaceId || !normalizedUserId) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const client = options?.trx || knex;
|
|
57
|
-
const row = await client("workspace_memberships")
|
|
58
|
-
.where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
|
|
59
|
-
.first();
|
|
60
|
-
return mapRow(row);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function ensureOwnerMembership(workspaceId, userId, options = {}) {
|
|
64
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
65
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
66
|
-
if (!normalizedWorkspaceId || !normalizedUserId) {
|
|
67
|
-
throw new TypeError("workspaceMembershipsRepository.ensureOwnerMembership requires workspaceId and userId.");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const client = options?.trx || knex;
|
|
71
|
-
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
72
|
-
if (existing) {
|
|
73
|
-
if (existing.roleSid !== OWNER_ROLE_ID || existing.status !== "active") {
|
|
74
|
-
await client("workspace_memberships")
|
|
75
|
-
.where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
|
|
76
|
-
.update({
|
|
77
|
-
role_sid: OWNER_ROLE_ID,
|
|
78
|
-
status: "active",
|
|
79
|
-
updated_at: nowDb()
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
await client("workspace_memberships").insert({
|
|
87
|
-
workspace_id: normalizedWorkspaceId,
|
|
88
|
-
user_id: normalizedUserId,
|
|
89
|
-
role_sid: OWNER_ROLE_ID,
|
|
90
|
-
status: "active",
|
|
91
|
-
created_at: nowDb(),
|
|
92
|
-
updated_at: nowDb()
|
|
93
|
-
});
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!isDuplicateEntryError(error)) {
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function upsertMembership(workspaceId, userId, patch = {}, options = {}) {
|
|
104
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
105
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
106
|
-
if (!normalizedWorkspaceId || !normalizedUserId) {
|
|
107
|
-
throw new TypeError("workspaceMembershipsRepository.upsertMembership requires workspaceId and userId.");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const client = options?.trx || knex;
|
|
111
|
-
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
112
|
-
const roleSid = normalizeLowerText(patch.roleSid || existing?.roleSid || "member") || "member";
|
|
113
|
-
const status = normalizeLowerText(patch.status || existing?.status || "active") || "active";
|
|
114
|
-
|
|
115
|
-
if (!existing) {
|
|
116
|
-
await client("workspace_memberships").insert({
|
|
117
|
-
workspace_id: normalizedWorkspaceId,
|
|
118
|
-
user_id: normalizedUserId,
|
|
119
|
-
role_sid: roleSid,
|
|
120
|
-
status,
|
|
121
|
-
created_at: nowDb(),
|
|
122
|
-
updated_at: nowDb()
|
|
123
|
-
});
|
|
124
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await client("workspace_memberships")
|
|
128
|
-
.where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
|
|
129
|
-
.update({
|
|
130
|
-
role_sid: roleSid,
|
|
131
|
-
status,
|
|
132
|
-
updated_at: nowDb()
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function listActiveByWorkspaceId(workspaceId, options = {}) {
|
|
139
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
140
|
-
if (!normalizedWorkspaceId) {
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const client = options?.trx || knex;
|
|
145
|
-
const rows = await client("workspace_memberships as wm")
|
|
146
|
-
.join("users as up", "up.id", "wm.user_id")
|
|
147
|
-
.where({ "wm.workspace_id": normalizedWorkspaceId, "wm.status": "active" })
|
|
148
|
-
.orderBy("up.display_name", "asc")
|
|
149
|
-
.select([
|
|
150
|
-
"wm.user_id",
|
|
151
|
-
"wm.role_sid",
|
|
152
|
-
"wm.status",
|
|
153
|
-
"up.display_name",
|
|
154
|
-
"up.email"
|
|
155
|
-
]);
|
|
156
|
-
|
|
157
|
-
return rows.map(mapMemberSummaryRow).filter(Boolean);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function listActiveWorkspaceIdsByUserId(userId, options = {}) {
|
|
161
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
162
|
-
if (!normalizedUserId) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const client = options?.trx || knex;
|
|
167
|
-
const rows = await client("workspace_memberships")
|
|
168
|
-
.where({
|
|
169
|
-
user_id: normalizedUserId,
|
|
170
|
-
status: "active"
|
|
171
|
-
})
|
|
172
|
-
.select("workspace_id")
|
|
173
|
-
.orderBy("workspace_id", "asc");
|
|
174
|
-
|
|
175
|
-
return rows
|
|
176
|
-
.map((row) => normalizeDbRecordId(row.workspace_id, { fallback: null }))
|
|
177
|
-
.filter(Boolean);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return Object.freeze({
|
|
181
|
-
withTransaction,
|
|
182
|
-
findByWorkspaceIdAndUserId,
|
|
183
|
-
ensureOwnerMembership,
|
|
184
|
-
upsertMembership,
|
|
185
|
-
listActiveByWorkspaceId,
|
|
186
|
-
listActiveWorkspaceIdsByUserId
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export { createRepository, mapRow, mapMemberSummaryRow };
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
|
|
2
|
-
import {
|
|
3
|
-
normalizeDbRecordId,
|
|
4
|
-
normalizeRecordId,
|
|
5
|
-
normalizeText,
|
|
6
|
-
normalizeLowerText,
|
|
7
|
-
toIsoString,
|
|
8
|
-
toNullableIso,
|
|
9
|
-
nowDb,
|
|
10
|
-
isDuplicateEntryError,
|
|
11
|
-
createWithTransaction
|
|
12
|
-
} from "./repositoryUtils.js";
|
|
13
|
-
|
|
14
|
-
function mapRow(row) {
|
|
15
|
-
if (!row) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
21
|
-
slug: normalizeText(row.slug),
|
|
22
|
-
name: normalizeText(row.name),
|
|
23
|
-
ownerUserId: normalizeDbRecordId(row.owner_user_id, { fallback: "" }),
|
|
24
|
-
isPersonal: Boolean(row.is_personal),
|
|
25
|
-
avatarUrl: row.avatar_url ? normalizeText(row.avatar_url) : "",
|
|
26
|
-
createdAt: toIsoString(row.created_at),
|
|
27
|
-
updatedAt: toIsoString(row.updated_at),
|
|
28
|
-
deletedAt: toNullableIso(row.deleted_at)
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function mapMembershipWorkspaceRow(row) {
|
|
33
|
-
if (!row) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
...mapRow(row),
|
|
39
|
-
roleSid: normalizeLowerText(row.role_sid || "member"),
|
|
40
|
-
membershipStatus: normalizeLowerText(row.membership_status || "active") || "active"
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function createRepository(knex) {
|
|
45
|
-
if (typeof knex !== "function") {
|
|
46
|
-
throw new TypeError("workspacesRepository requires knex.");
|
|
47
|
-
}
|
|
48
|
-
const withTransaction = createWithTransaction(knex);
|
|
49
|
-
|
|
50
|
-
function workspaceSelectColumns({ includeMembership = false } = {}) {
|
|
51
|
-
const columns = [
|
|
52
|
-
"w.id",
|
|
53
|
-
"w.slug",
|
|
54
|
-
"w.name",
|
|
55
|
-
"w.owner_user_id",
|
|
56
|
-
"w.is_personal",
|
|
57
|
-
"w.avatar_url",
|
|
58
|
-
"w.created_at",
|
|
59
|
-
"w.updated_at",
|
|
60
|
-
"w.deleted_at"
|
|
61
|
-
];
|
|
62
|
-
if (includeMembership) {
|
|
63
|
-
columns.push("wm.role_sid", "wm.status as membership_status");
|
|
64
|
-
}
|
|
65
|
-
return columns;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function findById(workspaceId, options = {}) {
|
|
69
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
70
|
-
if (!normalizedWorkspaceId) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const client = options?.trx || knex;
|
|
75
|
-
const row = await client("workspaces as w")
|
|
76
|
-
.where({ "w.id": normalizedWorkspaceId })
|
|
77
|
-
.select(workspaceSelectColumns())
|
|
78
|
-
.first();
|
|
79
|
-
return mapRow(row);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function findBySlug(slug, options = {}) {
|
|
83
|
-
const client = options?.trx || knex;
|
|
84
|
-
const normalizedSlug = normalizeLowerText(slug);
|
|
85
|
-
if (!normalizedSlug) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const row = await client("workspaces as w")
|
|
90
|
-
.where({ "w.slug": normalizedSlug })
|
|
91
|
-
.select(workspaceSelectColumns())
|
|
92
|
-
.first();
|
|
93
|
-
return mapRow(row);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function findPersonalByOwnerUserId(userId, options = {}) {
|
|
97
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
98
|
-
if (!normalizedUserId) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const client = options?.trx || knex;
|
|
103
|
-
const row = await client("workspaces as w")
|
|
104
|
-
.where({ "w.owner_user_id": normalizedUserId, "w.is_personal": 1 })
|
|
105
|
-
.orderBy("w.id", "asc")
|
|
106
|
-
.select(workspaceSelectColumns())
|
|
107
|
-
.first();
|
|
108
|
-
return mapRow(row);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function insert(payload = {}, options = {}) {
|
|
112
|
-
const client = options?.trx || knex;
|
|
113
|
-
const source = payload && typeof payload === "object" ? payload : {};
|
|
114
|
-
const ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: null });
|
|
115
|
-
if (!ownerUserId) {
|
|
116
|
-
throw new TypeError("workspacesRepository.insert requires ownerUserId.");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const insertPayload = {
|
|
120
|
-
slug: normalizeLowerText(source.slug),
|
|
121
|
-
name: normalizeText(source.name),
|
|
122
|
-
owner_user_id: ownerUserId,
|
|
123
|
-
is_personal: source.isPersonal ? 1 : 0,
|
|
124
|
-
avatar_url: normalizeText(source.avatarUrl),
|
|
125
|
-
created_at: nowDb(),
|
|
126
|
-
updated_at: nowDb(),
|
|
127
|
-
deleted_at: null
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const result = await client("workspaces").insert(insertPayload);
|
|
132
|
-
const insertedId = resolveInsertedRecordId(result, { fallback: null });
|
|
133
|
-
if (insertedId) {
|
|
134
|
-
return findById(insertedId, { trx: client });
|
|
135
|
-
}
|
|
136
|
-
const bySlug = await findBySlug(insertPayload.slug, { trx: client });
|
|
137
|
-
return bySlug;
|
|
138
|
-
} catch (error) {
|
|
139
|
-
if (!isDuplicateEntryError(error)) {
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
const bySlug = await findBySlug(insertPayload.slug, { trx: client });
|
|
143
|
-
if (bySlug) {
|
|
144
|
-
return bySlug;
|
|
145
|
-
}
|
|
146
|
-
throw error;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function updateById(workspaceId, patch = {}, options = {}) {
|
|
151
|
-
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
152
|
-
if (!normalizedWorkspaceId) {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const client = options?.trx || knex;
|
|
157
|
-
const source = patch && typeof patch === "object" ? patch : {};
|
|
158
|
-
const dbPatch = {
|
|
159
|
-
updated_at: nowDb()
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
if (Object.hasOwn(source, "name")) {
|
|
163
|
-
dbPatch.name = normalizeText(source.name);
|
|
164
|
-
}
|
|
165
|
-
if (Object.hasOwn(source, "avatarUrl")) {
|
|
166
|
-
dbPatch.avatar_url = normalizeText(source.avatarUrl);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
await client("workspaces").where({ id: normalizedWorkspaceId }).update(dbPatch);
|
|
170
|
-
return findById(normalizedWorkspaceId, { trx: client });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function listForUserId(userId, options = {}) {
|
|
174
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
175
|
-
if (!normalizedUserId) {
|
|
176
|
-
return [];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const client = options?.trx || knex;
|
|
180
|
-
const rows = await client("workspace_memberships as wm")
|
|
181
|
-
.join("workspaces as w", "w.id", "wm.workspace_id")
|
|
182
|
-
.where({ "wm.user_id": normalizedUserId })
|
|
183
|
-
.whereNull("w.deleted_at")
|
|
184
|
-
.orderBy("w.is_personal", "desc")
|
|
185
|
-
.orderBy("w.id", "asc")
|
|
186
|
-
.select(workspaceSelectColumns({ includeMembership: true }));
|
|
187
|
-
|
|
188
|
-
return rows.map(mapMembershipWorkspaceRow).filter(Boolean);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return Object.freeze({
|
|
192
|
-
withTransaction,
|
|
193
|
-
findById,
|
|
194
|
-
findBySlug,
|
|
195
|
-
findPersonalByOwnerUserId,
|
|
196
|
-
insert,
|
|
197
|
-
updateById,
|
|
198
|
-
listForUserId
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export { createRepository, mapRow, mapMembershipWorkspaceRow };
|