@jskit-ai/workspaces-core 0.1.31 → 0.1.32
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 +11 -22
- package/package.json +11 -9
- package/src/server/WorkspacesCoreServiceProvider.js +22 -2
- package/src/server/common/repositories/workspaceInvitesRepository.js +233 -78
- package/src/server/common/repositories/workspaceMembershipsRepository.js +177 -86
- package/src/server/common/repositories/workspacesRepository.js +179 -86
- package/src/server/common/services/workspaceContextService.js +26 -24
- package/src/server/common/validators/routeParamsValidator.js +36 -53
- package/src/server/registerWorkspaceCore.js +6 -7
- package/src/server/registerWorkspaceRepositories.js +7 -3
- package/src/server/support/workspaceServerScopeSupport.js +1 -1
- package/src/server/workspaceBootstrapContributor.js +5 -14
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +54 -27
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +30 -24
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +70 -32
- package/src/server/workspaceMembers/workspaceMembersActions.js +61 -27
- package/src/server/workspaceMembers/workspaceMembersService.js +43 -7
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +28 -13
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +13 -15
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +33 -10
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +32 -13
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +5 -1
- package/src/server/workspaceSettings/workspaceSettingsActions.js +18 -12
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +104 -91
- package/src/server/workspaceSettings/workspaceSettingsService.js +5 -6
- package/src/shared/jsonApiTransports.js +79 -0
- package/src/shared/resources/workspaceInvitesResource.js +158 -0
- package/src/shared/resources/workspaceMembersResource.js +176 -311
- package/src/shared/resources/workspaceMembershipsResource.js +96 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +25 -72
- package/src/shared/resources/workspaceResource.js +113 -144
- package/src/shared/resources/workspaceRoleCatalogSchema.js +31 -0
- package/src/shared/resources/workspaceSettingsResource.js +276 -148
- package/test/repositoryContracts.test.js +16 -4
- package/test/resourcesCanonical.test.js +39 -16
- package/test/routeParamsValidator.test.js +37 -19
- package/test/usersRouteResources.test.js +27 -17
- package/test/workspaceActionContextContributor.test.js +1 -1
- package/test/workspaceInternalCrudResources.test.js +98 -0
- package/test/workspaceInvitesRepository.test.js +196 -148
- package/test/workspaceMembersResource.test.js +35 -0
- package/test/workspaceMembershipsRepository.test.js +155 -115
- package/test/workspacePendingInvitationsResource.test.js +18 -23
- package/test/workspacePendingInvitationsService.test.js +2 -1
- package/test/workspaceServerScopeSupport.test.js +21 -3
- package/test/workspaceSettingsActions.test.js +5 -7
- package/test/workspaceSettingsInternalResource.test.js +8 -0
- package/test/workspaceSettingsRepository.test.js +158 -123
- package/test/workspaceSettingsResource.test.js +51 -62
- package/test/workspaceSettingsService.test.js +0 -1
- package/test/workspacesRepository.test.js +318 -174
- package/test/workspacesRouteRequestInputValidator.test.js +25 -11
- package/src/server/common/resources/workspaceInvitesResource.js +0 -207
- package/src/server/common/resources/workspaceMembershipsResource.js +0 -154
- package/src/server/common/resources/workspacesResource.js +0 -170
- package/src/server/common/validators/authenticatedUserValidator.js +0 -43
- package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
- package/src/shared/resources/workspaceSettingsFields.js +0 -65
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
- package/test/settingsFieldRegistriesSingleton.test.js +0 -14
- package/test-support/registerDefaultSettingsFields.js +0 -1
|
@@ -1,27 +1,50 @@
|
|
|
1
|
-
import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
|
|
2
1
|
import {
|
|
2
|
+
createWithTransaction,
|
|
3
3
|
normalizeLowerText,
|
|
4
4
|
normalizeRecordId,
|
|
5
5
|
normalizeDbRecordId,
|
|
6
6
|
normalizeText,
|
|
7
|
-
isDuplicateEntryError
|
|
7
|
+
isDuplicateEntryError,
|
|
8
|
+
toIsoString
|
|
8
9
|
} from "./repositoryUtils.js";
|
|
10
|
+
import {
|
|
11
|
+
createJsonApiInputRecord,
|
|
12
|
+
createJsonApiRelationship,
|
|
13
|
+
createJsonRestContext,
|
|
14
|
+
simplifyJsonApiDocument
|
|
15
|
+
} from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
|
|
9
16
|
import { OWNER_ROLE_ID } from "../../../shared/roles.js";
|
|
10
|
-
import { workspaceMembershipsResource } from "../resources/workspaceMembershipsResource.js";
|
|
11
17
|
|
|
12
|
-
const
|
|
13
|
-
context: "internal.repository.workspace-memberships"
|
|
14
|
-
});
|
|
18
|
+
const RESOURCE_TYPE = "workspaceMemberships";
|
|
15
19
|
|
|
16
20
|
function normalizeMembershipRecord(payload) {
|
|
17
21
|
if (!payload) {
|
|
18
22
|
return null;
|
|
19
23
|
}
|
|
20
|
-
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
id: normalizeDbRecordId(payload.id, { fallback: null }),
|
|
27
|
+
workspaceId: normalizeDbRecordId(payload?.workspace?.id || payload?.workspaceId, { fallback: null }),
|
|
28
|
+
userId: normalizeDbRecordId(payload?.user?.id || payload?.userId, { fallback: null }),
|
|
29
|
+
roleSid: normalizeLowerText(payload.roleSid || "member") || "member",
|
|
30
|
+
status: normalizeLowerText(payload.status || "active") || "active",
|
|
31
|
+
createdAt: payload.createdAt ? toIsoString(payload.createdAt) : null,
|
|
32
|
+
updatedAt: payload.updatedAt ? toIsoString(payload.updatedAt) : null
|
|
33
|
+
};
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
function normalizeMembershipPatchPayload(payload = {}) {
|
|
24
|
-
|
|
37
|
+
const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
38
|
+
const normalized = {};
|
|
39
|
+
|
|
40
|
+
if (Object.hasOwn(source, "roleSid")) {
|
|
41
|
+
normalized.roleSid = normalizeLowerText(source.roleSid);
|
|
42
|
+
}
|
|
43
|
+
if (Object.hasOwn(source, "status")) {
|
|
44
|
+
normalized.status = normalizeLowerText(source.status);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return normalized;
|
|
25
48
|
}
|
|
26
49
|
|
|
27
50
|
function normalizeMemberSummaryRow(row) {
|
|
@@ -38,12 +61,44 @@ function normalizeMemberSummaryRow(row) {
|
|
|
38
61
|
};
|
|
39
62
|
}
|
|
40
63
|
|
|
41
|
-
function
|
|
64
|
+
function createMembershipRelationships({ workspaceId = null, userId = null } = {}) {
|
|
65
|
+
const relationships = {};
|
|
66
|
+
|
|
67
|
+
if (workspaceId) {
|
|
68
|
+
relationships.workspace = createJsonApiRelationship("workspaces", workspaceId);
|
|
69
|
+
}
|
|
70
|
+
if (userId) {
|
|
71
|
+
relationships.user = createJsonApiRelationship("userProfiles", userId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return relationships;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createRepository({ api, knex } = {}) {
|
|
78
|
+
if (!api?.resources?.workspaceMemberships) {
|
|
79
|
+
throw new TypeError("workspaceMembershipsRepository requires json-rest-api workspaceMemberships resource.");
|
|
80
|
+
}
|
|
42
81
|
if (typeof knex !== "function") {
|
|
43
82
|
throw new TypeError("workspaceMembershipsRepository requires knex.");
|
|
44
83
|
}
|
|
45
|
-
|
|
46
|
-
const withTransaction =
|
|
84
|
+
|
|
85
|
+
const withTransaction = createWithTransaction(knex);
|
|
86
|
+
|
|
87
|
+
async function queryMemberships(filters = {}, options = {}, { includeUser = false } = {}) {
|
|
88
|
+
const result = await api.resources.workspaceMemberships.query(
|
|
89
|
+
{
|
|
90
|
+
queryParams: {
|
|
91
|
+
filters,
|
|
92
|
+
...(includeUser ? { include: ["user"] } : {})
|
|
93
|
+
},
|
|
94
|
+
transaction: options?.trx || null,
|
|
95
|
+
simplified: false
|
|
96
|
+
},
|
|
97
|
+
createJsonRestContext(options?.context || null)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return Array.isArray(simplifyJsonApiDocument(result)) ? simplifyJsonApiDocument(result) : [];
|
|
101
|
+
}
|
|
47
102
|
|
|
48
103
|
async function findByWorkspaceIdAndUserId(workspaceId, userId, options = {}) {
|
|
49
104
|
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
@@ -52,11 +107,15 @@ function createRepository(knex) {
|
|
|
52
107
|
return null;
|
|
53
108
|
}
|
|
54
109
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
110
|
+
const rows = await queryMemberships(
|
|
111
|
+
{
|
|
112
|
+
workspace: normalizedWorkspaceId,
|
|
113
|
+
user: normalizedUserId
|
|
114
|
+
},
|
|
115
|
+
options
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return normalizeMembershipRecord(rows[0] || null);
|
|
60
119
|
}
|
|
61
120
|
|
|
62
121
|
async function ensureOwnerMembership(workspaceId, userId, options = {}) {
|
|
@@ -66,40 +125,53 @@ function createRepository(knex) {
|
|
|
66
125
|
throw new TypeError("workspaceMembershipsRepository.ensureOwnerMembership requires workspaceId and userId.");
|
|
67
126
|
}
|
|
68
127
|
|
|
69
|
-
const
|
|
70
|
-
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
128
|
+
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, options);
|
|
71
129
|
if (existing) {
|
|
72
130
|
if (existing.roleSid !== OWNER_ROLE_ID || existing.status !== "active") {
|
|
73
|
-
await
|
|
74
|
-
existing.id,
|
|
131
|
+
await api.resources.workspaceMemberships.patch(
|
|
75
132
|
{
|
|
76
|
-
|
|
77
|
-
|
|
133
|
+
inputRecord: createJsonApiInputRecord(
|
|
134
|
+
RESOURCE_TYPE,
|
|
135
|
+
{
|
|
136
|
+
roleSid: OWNER_ROLE_ID,
|
|
137
|
+
status: "active",
|
|
138
|
+
updatedAt: new Date()
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: existing.id
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
transaction: options?.trx || null,
|
|
145
|
+
simplified: false
|
|
78
146
|
},
|
|
79
|
-
|
|
80
|
-
...options,
|
|
81
|
-
trx: client,
|
|
82
|
-
include: "none",
|
|
83
|
-
existingRecord: existing
|
|
84
|
-
}
|
|
147
|
+
createJsonRestContext(options?.context || null)
|
|
85
148
|
);
|
|
86
149
|
}
|
|
87
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId,
|
|
150
|
+
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, options);
|
|
88
151
|
}
|
|
89
152
|
|
|
90
153
|
try {
|
|
91
|
-
await
|
|
154
|
+
await api.resources.workspaceMemberships.post(
|
|
92
155
|
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
156
|
+
inputRecord: createJsonApiInputRecord(
|
|
157
|
+
RESOURCE_TYPE,
|
|
158
|
+
{
|
|
159
|
+
roleSid: OWNER_ROLE_ID,
|
|
160
|
+
status: "active",
|
|
161
|
+
createdAt: new Date(),
|
|
162
|
+
updatedAt: new Date()
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
relationships: createMembershipRelationships({
|
|
166
|
+
workspaceId: normalizedWorkspaceId,
|
|
167
|
+
userId: normalizedUserId
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
transaction: options?.trx || null,
|
|
172
|
+
simplified: false
|
|
97
173
|
},
|
|
98
|
-
|
|
99
|
-
...options,
|
|
100
|
-
trx: client,
|
|
101
|
-
include: "none"
|
|
102
|
-
}
|
|
174
|
+
createJsonRestContext(options?.context || null)
|
|
103
175
|
);
|
|
104
176
|
} catch (error) {
|
|
105
177
|
if (!isDuplicateEntryError(error)) {
|
|
@@ -107,7 +179,7 @@ function createRepository(knex) {
|
|
|
107
179
|
}
|
|
108
180
|
}
|
|
109
181
|
|
|
110
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId,
|
|
182
|
+
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, options);
|
|
111
183
|
}
|
|
112
184
|
|
|
113
185
|
async function upsertMembership(workspaceId, userId, patch = {}, options = {}) {
|
|
@@ -117,8 +189,7 @@ function createRepository(knex) {
|
|
|
117
189
|
throw new TypeError("workspaceMembershipsRepository.upsertMembership requires workspaceId and userId.");
|
|
118
190
|
}
|
|
119
191
|
|
|
120
|
-
const
|
|
121
|
-
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
192
|
+
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, options);
|
|
122
193
|
const normalizedPatch = normalizeMembershipPatchPayload({
|
|
123
194
|
roleSid: patch?.roleSid ?? existing?.roleSid ?? "member",
|
|
124
195
|
status: patch?.status ?? existing?.status ?? "active"
|
|
@@ -128,42 +199,56 @@ function createRepository(knex) {
|
|
|
128
199
|
|
|
129
200
|
if (!existing) {
|
|
130
201
|
try {
|
|
131
|
-
await
|
|
202
|
+
await api.resources.workspaceMemberships.post(
|
|
132
203
|
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
204
|
+
inputRecord: createJsonApiInputRecord(
|
|
205
|
+
RESOURCE_TYPE,
|
|
206
|
+
{
|
|
207
|
+
roleSid,
|
|
208
|
+
status,
|
|
209
|
+
createdAt: new Date(),
|
|
210
|
+
updatedAt: new Date()
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
relationships: createMembershipRelationships({
|
|
214
|
+
workspaceId: normalizedWorkspaceId,
|
|
215
|
+
userId: normalizedUserId
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
),
|
|
219
|
+
transaction: options?.trx || null,
|
|
220
|
+
simplified: false
|
|
137
221
|
},
|
|
138
|
-
|
|
139
|
-
...options,
|
|
140
|
-
trx: client,
|
|
141
|
-
include: "none"
|
|
142
|
-
}
|
|
222
|
+
createJsonRestContext(options?.context || null)
|
|
143
223
|
);
|
|
144
224
|
} catch (error) {
|
|
145
225
|
if (!isDuplicateEntryError(error)) {
|
|
146
226
|
throw error;
|
|
147
227
|
}
|
|
148
228
|
}
|
|
149
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId,
|
|
229
|
+
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, options);
|
|
150
230
|
}
|
|
151
231
|
|
|
152
|
-
await
|
|
153
|
-
existing.id,
|
|
232
|
+
await api.resources.workspaceMemberships.patch(
|
|
154
233
|
{
|
|
155
|
-
|
|
156
|
-
|
|
234
|
+
inputRecord: createJsonApiInputRecord(
|
|
235
|
+
RESOURCE_TYPE,
|
|
236
|
+
{
|
|
237
|
+
roleSid,
|
|
238
|
+
status,
|
|
239
|
+
updatedAt: new Date()
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: existing.id
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
transaction: options?.trx || null,
|
|
246
|
+
simplified: false
|
|
157
247
|
},
|
|
158
|
-
|
|
159
|
-
...options,
|
|
160
|
-
trx: client,
|
|
161
|
-
include: "none",
|
|
162
|
-
existingRecord: existing
|
|
163
|
-
}
|
|
248
|
+
createJsonRestContext(options?.context || null)
|
|
164
249
|
);
|
|
165
250
|
|
|
166
|
-
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId,
|
|
251
|
+
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, options);
|
|
167
252
|
}
|
|
168
253
|
|
|
169
254
|
async function listActiveByWorkspaceId(workspaceId, options = {}) {
|
|
@@ -172,20 +257,27 @@ function createRepository(knex) {
|
|
|
172
257
|
return [];
|
|
173
258
|
}
|
|
174
259
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
260
|
+
const rows = await queryMemberships(
|
|
261
|
+
{
|
|
262
|
+
workspace: normalizedWorkspaceId,
|
|
263
|
+
status: "active"
|
|
264
|
+
},
|
|
265
|
+
options,
|
|
266
|
+
{ includeUser: true }
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const members = rows
|
|
270
|
+
.map((row) => normalizeMemberSummaryRow({
|
|
271
|
+
user_id: row?.user?.id,
|
|
272
|
+
role_sid: row?.roleSid,
|
|
273
|
+
status: row?.status,
|
|
274
|
+
display_name: row?.user?.displayName,
|
|
275
|
+
email: row?.user?.email
|
|
276
|
+
}))
|
|
277
|
+
.filter(Boolean);
|
|
278
|
+
|
|
279
|
+
members.sort((left, right) => String(left.displayName || "").localeCompare(String(right.displayName || "")));
|
|
280
|
+
return members;
|
|
189
281
|
}
|
|
190
282
|
|
|
191
283
|
async function listActiveWorkspaceIdsByUserId(userId, options = {}) {
|
|
@@ -194,17 +286,16 @@ function createRepository(knex) {
|
|
|
194
286
|
return [];
|
|
195
287
|
}
|
|
196
288
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
user_id: normalizedUserId,
|
|
289
|
+
const rows = await queryMemberships(
|
|
290
|
+
{
|
|
291
|
+
user: normalizedUserId,
|
|
201
292
|
status: "active"
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
293
|
+
},
|
|
294
|
+
options
|
|
295
|
+
);
|
|
205
296
|
|
|
206
297
|
return rows
|
|
207
|
-
.map((row) => normalizeDbRecordId(row
|
|
298
|
+
.map((row) => normalizeDbRecordId(row?.workspace?.id || row?.workspaceId, { fallback: null }))
|
|
208
299
|
.filter(Boolean);
|
|
209
300
|
}
|
|
210
301
|
|