@jskit-ai/workspaces-core 0.1.31 → 0.1.33
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 +28 -26
- package/src/server/common/validators/routeParamsValidator.js +36 -53
- package/src/server/registerWorkspaceCore.js +9 -10
- 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 +77 -3
- package/test/workspaceService.test.js +26 -5
- 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,62 +1,72 @@
|
|
|
1
|
-
import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
|
|
2
1
|
import {
|
|
2
|
+
createWithTransaction,
|
|
3
3
|
normalizeRecordId,
|
|
4
|
+
isDuplicateEntryError,
|
|
4
5
|
normalizeText,
|
|
5
|
-
|
|
6
|
-
isDuplicateEntryError
|
|
6
|
+
toIsoString
|
|
7
7
|
} from "./repositoryUtils.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createJsonApiInputRecord,
|
|
10
|
+
createJsonApiRelationship,
|
|
11
|
+
createJsonRestContext,
|
|
12
|
+
simplifyJsonApiDocument
|
|
13
|
+
} from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
|
|
9
14
|
|
|
10
|
-
const
|
|
11
|
-
context: "internal.repository.workspaces"
|
|
12
|
-
});
|
|
15
|
+
const RESOURCE_TYPE = "workspaces";
|
|
13
16
|
|
|
14
|
-
function normalizeWorkspaceRecord(payload) {
|
|
15
|
-
if (!payload) {
|
|
17
|
+
function normalizeWorkspaceRecord(payload = null) {
|
|
18
|
+
if (!payload || typeof payload !== "object") {
|
|
16
19
|
return null;
|
|
17
20
|
}
|
|
18
|
-
return workspacesResource.operations.view.outputValidator.normalize(payload);
|
|
19
|
-
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
return {
|
|
23
|
+
id: normalizeRecordId(payload.id, { fallback: null }),
|
|
24
|
+
slug: normalizeText(payload.slug),
|
|
25
|
+
name: normalizeText(payload.name),
|
|
26
|
+
ownerUserId: normalizeRecordId(payload.ownerUserId || payload?.owner?.id, { fallback: null }),
|
|
27
|
+
isPersonal: payload.isPersonal === true,
|
|
28
|
+
avatarUrl: normalizeText(payload.avatarUrl),
|
|
29
|
+
createdAt: payload.createdAt ? toIsoString(payload.createdAt) : null,
|
|
30
|
+
updatedAt: payload.updatedAt ? toIsoString(payload.updatedAt) : null,
|
|
31
|
+
deletedAt: payload.deletedAt ? toIsoString(payload.deletedAt) : null
|
|
32
|
+
};
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
function createWorkspaceRelationships(source = {}) {
|
|
36
|
+
const relationships = {};
|
|
37
|
+
const ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: null });
|
|
38
|
+
|
|
39
|
+
if (ownerUserId) {
|
|
40
|
+
relationships.owner = createJsonApiRelationship("userProfiles", ownerUserId);
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
return
|
|
31
|
-
...normalizeWorkspaceRecord(row),
|
|
32
|
-
roleSid: normalizeLowerText(row.role_sid || "member"),
|
|
33
|
-
membershipStatus: normalizeLowerText(row.membership_status || "active") || "active"
|
|
34
|
-
};
|
|
43
|
+
return relationships;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
function createRepository(knex) {
|
|
46
|
+
function createRepository({ api, knex } = {}) {
|
|
47
|
+
if (!api?.resources?.workspaces || !api?.resources?.workspaceMemberships) {
|
|
48
|
+
throw new TypeError("workspacesRepository requires json-rest-api workspaces and workspaceMemberships resources.");
|
|
49
|
+
}
|
|
38
50
|
if (typeof knex !== "function") {
|
|
39
51
|
throw new TypeError("workspacesRepository requires knex.");
|
|
40
52
|
}
|
|
41
|
-
|
|
42
|
-
const withTransaction =
|
|
43
|
-
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
return columns;
|
|
53
|
+
|
|
54
|
+
const withTransaction = createWithTransaction(knex);
|
|
55
|
+
|
|
56
|
+
async function queryFirst(filters = {}, options = {}) {
|
|
57
|
+
const result = await api.resources.workspaces.query(
|
|
58
|
+
{
|
|
59
|
+
queryParams: {
|
|
60
|
+
filters
|
|
61
|
+
},
|
|
62
|
+
transaction: options?.trx || null,
|
|
63
|
+
simplified: false
|
|
64
|
+
},
|
|
65
|
+
createJsonRestContext(options?.context || null)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const rows = simplifyJsonApiDocument(result);
|
|
69
|
+
return normalizeWorkspaceRecord(Array.isArray(rows) ? rows[0] || null : null);
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
async function findById(workspaceId, options = {}) {
|
|
@@ -65,24 +75,16 @@ function createRepository(knex) {
|
|
|
65
75
|
return null;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
|
-
return
|
|
69
|
-
...options,
|
|
70
|
-
include: "none"
|
|
71
|
-
});
|
|
78
|
+
return queryFirst({ id: normalizedWorkspaceId }, options);
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
async function findBySlug(slug, options = {}) {
|
|
75
|
-
const
|
|
76
|
-
const normalizedSlug = normalizeLowerText(slug);
|
|
82
|
+
const normalizedSlug = typeof slug === "string" ? slug.trim().toLowerCase() : "";
|
|
77
83
|
if (!normalizedSlug) {
|
|
78
84
|
return null;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
.where({ "w.slug": normalizedSlug })
|
|
83
|
-
.select(workspaceSelectColumns())
|
|
84
|
-
.first();
|
|
85
|
-
return normalizeWorkspaceRecord(row);
|
|
87
|
+
return queryFirst({ slug: normalizedSlug }, options);
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
async function findPersonalByOwnerUserId(userId, options = {}) {
|
|
@@ -91,41 +93,78 @@ function createRepository(knex) {
|
|
|
91
93
|
return null;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
const result = await api.resources.workspaces.query(
|
|
97
|
+
{
|
|
98
|
+
queryParams: {
|
|
99
|
+
filters: {
|
|
100
|
+
owner: normalizedUserId,
|
|
101
|
+
isPersonal: true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
transaction: options?.trx || null,
|
|
105
|
+
simplified: false
|
|
106
|
+
},
|
|
107
|
+
createJsonRestContext(options?.context || null)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const rows = Array.isArray(simplifyJsonApiDocument(result))
|
|
111
|
+
? simplifyJsonApiDocument(result).map((row) => normalizeWorkspaceRecord(row)).filter(Boolean)
|
|
112
|
+
: [];
|
|
113
|
+
rows.sort((left, right) => {
|
|
114
|
+
const leftId = Number(left?.id);
|
|
115
|
+
const rightId = Number(right?.id);
|
|
116
|
+
if (Number.isFinite(leftId) && Number.isFinite(rightId)) {
|
|
117
|
+
return leftId - rightId;
|
|
118
|
+
}
|
|
119
|
+
return String(left?.id || "").localeCompare(String(right?.id || ""));
|
|
120
|
+
});
|
|
121
|
+
return rows[0] || null;
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
async function insert(payload = {}, options = {}) {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const ownerUserId = normalizeRecordId(normalizedPayload.ownerUserId, { fallback: null });
|
|
125
|
+
const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
126
|
+
const ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: null });
|
|
107
127
|
if (!ownerUserId) {
|
|
108
128
|
throw new TypeError("workspacesRepository.insert requires ownerUserId.");
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
const createPayload = {
|
|
112
|
-
...
|
|
113
|
-
|
|
114
|
-
isPersonal:
|
|
115
|
-
avatarUrl:
|
|
132
|
+
...(Object.hasOwn(source, "slug") ? { slug: source.slug } : {}),
|
|
133
|
+
...(Object.hasOwn(source, "name") ? { name: source.name } : {}),
|
|
134
|
+
...(Object.hasOwn(source, "isPersonal") ? { isPersonal: source.isPersonal } : {}),
|
|
135
|
+
...(Object.hasOwn(source, "avatarUrl") ? { avatarUrl: source.avatarUrl } : {})
|
|
116
136
|
};
|
|
117
137
|
|
|
118
138
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
const created = await api.resources.workspaces.post(
|
|
140
|
+
{
|
|
141
|
+
inputRecord: createJsonApiInputRecord(
|
|
142
|
+
RESOURCE_TYPE,
|
|
143
|
+
{
|
|
144
|
+
...createPayload,
|
|
145
|
+
createdAt: new Date(),
|
|
146
|
+
updatedAt: new Date()
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
relationships: createWorkspaceRelationships({ ownerUserId })
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
transaction: options?.trx || null,
|
|
153
|
+
simplified: false
|
|
154
|
+
},
|
|
155
|
+
createJsonRestContext(options?.context || null)
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return normalizeWorkspaceRecord(simplifyJsonApiDocument(created));
|
|
124
159
|
} catch (error) {
|
|
125
160
|
if (!isDuplicateEntryError(error)) {
|
|
126
161
|
throw error;
|
|
127
162
|
}
|
|
128
|
-
|
|
163
|
+
if (!Object.hasOwn(createPayload, "slug")) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const bySlug = await findBySlug(createPayload.slug, options);
|
|
129
168
|
if (bySlug) {
|
|
130
169
|
return bySlug;
|
|
131
170
|
}
|
|
@@ -139,10 +178,30 @@ function createRepository(knex) {
|
|
|
139
178
|
return null;
|
|
140
179
|
}
|
|
141
180
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
const sourcePatch = patch && typeof patch === "object" && !Array.isArray(patch) ? patch : {};
|
|
182
|
+
const workspacePatch = {
|
|
183
|
+
...sourcePatch,
|
|
184
|
+
updatedAt: new Date()
|
|
185
|
+
};
|
|
186
|
+
const relationships = createWorkspaceRelationships(sourcePatch);
|
|
187
|
+
|
|
188
|
+
const updated = await api.resources.workspaces.patch(
|
|
189
|
+
{
|
|
190
|
+
inputRecord: createJsonApiInputRecord(
|
|
191
|
+
RESOURCE_TYPE,
|
|
192
|
+
workspacePatch,
|
|
193
|
+
{
|
|
194
|
+
id: normalizedWorkspaceId,
|
|
195
|
+
relationships
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
transaction: options?.trx || null,
|
|
199
|
+
simplified: false
|
|
200
|
+
},
|
|
201
|
+
createJsonRestContext(options?.context || null)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return normalizeWorkspaceRecord(simplifyJsonApiDocument(updated));
|
|
146
205
|
}
|
|
147
206
|
|
|
148
207
|
async function listForUserId(userId, options = {}) {
|
|
@@ -151,16 +210,50 @@ function createRepository(knex) {
|
|
|
151
210
|
return [];
|
|
152
211
|
}
|
|
153
212
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
213
|
+
const result = await api.resources.workspaceMemberships.query(
|
|
214
|
+
{
|
|
215
|
+
queryParams: {
|
|
216
|
+
filters: {
|
|
217
|
+
user: normalizedUserId,
|
|
218
|
+
status: "active"
|
|
219
|
+
},
|
|
220
|
+
include: ["workspace"]
|
|
221
|
+
},
|
|
222
|
+
transaction: options?.trx || null,
|
|
223
|
+
simplified: false
|
|
224
|
+
},
|
|
225
|
+
createJsonRestContext(options?.context || null)
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const rows = Array.isArray(simplifyJsonApiDocument(result)) ? simplifyJsonApiDocument(result) : [];
|
|
229
|
+
const workspaces = rows
|
|
230
|
+
.map((row) => {
|
|
231
|
+
const workspace = normalizeWorkspaceRecord(row?.workspace);
|
|
232
|
+
if (!workspace || workspace.deletedAt != null) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
...workspace,
|
|
238
|
+
roleSid: row?.roleSid,
|
|
239
|
+
membershipStatus: row?.status
|
|
240
|
+
};
|
|
241
|
+
})
|
|
242
|
+
.filter(Boolean);
|
|
243
|
+
|
|
244
|
+
workspaces.sort((left, right) => {
|
|
245
|
+
if (left.isPersonal !== right.isPersonal) {
|
|
246
|
+
return left.isPersonal ? -1 : 1;
|
|
247
|
+
}
|
|
248
|
+
const leftId = Number(left?.id);
|
|
249
|
+
const rightId = Number(right?.id);
|
|
250
|
+
if (Number.isFinite(leftId) && Number.isFinite(rightId)) {
|
|
251
|
+
return leftId - rightId;
|
|
252
|
+
}
|
|
253
|
+
return String(left?.id || "").localeCompare(String(right?.id || ""));
|
|
254
|
+
});
|
|
162
255
|
|
|
163
|
-
return
|
|
256
|
+
return workspaces;
|
|
164
257
|
}
|
|
165
258
|
|
|
166
259
|
return Object.freeze({
|
|
@@ -174,4 +267,4 @@ function createRepository(knex) {
|
|
|
174
267
|
});
|
|
175
268
|
}
|
|
176
269
|
|
|
177
|
-
export { createRepository
|
|
270
|
+
export { createRepository };
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "../formatters/workspaceFormatter.js";
|
|
13
13
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
14
14
|
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
15
|
-
import { authenticatedUserValidator } from "../validators/authenticatedUserValidator.js";
|
|
16
15
|
|
|
17
16
|
function toSlugPart(value) {
|
|
18
17
|
const normalized = normalizeLowerText(value)
|
|
@@ -105,38 +104,38 @@ function createService({
|
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
async function ensurePersonalWorkspaceForUser(user, options = {}) {
|
|
108
|
-
const
|
|
109
|
-
if (!
|
|
107
|
+
const normalizedUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
108
|
+
if (!normalizedUserId) {
|
|
110
109
|
throw new AppError(400, "Invalid authenticated user payload.");
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
const existing = await workspacesRepository.findPersonalByOwnerUserId(
|
|
112
|
+
const existing = await workspacesRepository.findPersonalByOwnerUserId(normalizedUserId, options);
|
|
114
113
|
if (existing) {
|
|
115
|
-
await workspaceMembershipsRepository.ensureOwnerMembership(existing.id,
|
|
114
|
+
await workspaceMembershipsRepository.ensureOwnerMembership(existing.id, normalizedUserId, options);
|
|
116
115
|
await ensureWorkspaceSettingsForWorkspace(existing, options);
|
|
117
116
|
return existing;
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
const slug = await ensureUniqueWorkspaceSlug(buildWorkspaceBaseSlug(
|
|
119
|
+
const slug = await ensureUniqueWorkspaceSlug(buildWorkspaceBaseSlug(user), options);
|
|
121
120
|
const inserted = await workspacesRepository.insert(
|
|
122
121
|
{
|
|
123
122
|
slug,
|
|
124
|
-
name: buildWorkspaceName(
|
|
125
|
-
ownerUserId:
|
|
123
|
+
name: buildWorkspaceName(user),
|
|
124
|
+
ownerUserId: normalizedUserId,
|
|
126
125
|
isPersonal: true,
|
|
127
126
|
avatarUrl: ""
|
|
128
127
|
},
|
|
129
128
|
options
|
|
130
129
|
);
|
|
131
130
|
|
|
132
|
-
await workspaceMembershipsRepository.ensureOwnerMembership(inserted.id,
|
|
131
|
+
await workspaceMembershipsRepository.ensureOwnerMembership(inserted.id, normalizedUserId, options);
|
|
133
132
|
await ensureWorkspaceSettingsForWorkspace(inserted, options);
|
|
134
133
|
return inserted;
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
async function listWorkspacesForUser(user, options = {}) {
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
137
|
+
const normalizedUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
138
|
+
if (!normalizedUserId) {
|
|
140
139
|
return [];
|
|
141
140
|
}
|
|
142
141
|
|
|
@@ -144,7 +143,7 @@ function createService({
|
|
|
144
143
|
return [];
|
|
145
144
|
}
|
|
146
145
|
|
|
147
|
-
const list = await workspacesRepository.listForUserId(
|
|
146
|
+
const list = await workspacesRepository.listForUserId(normalizedUserId, options);
|
|
148
147
|
const accessible = list
|
|
149
148
|
.map((entry) => mapWorkspaceSummary(entry, { roleSid: entry.roleSid, status: entry.membershipStatus }))
|
|
150
149
|
.filter((entry) => entry.isAccessible);
|
|
@@ -156,9 +155,9 @@ function createService({
|
|
|
156
155
|
return listWorkspacesForUser(user, options);
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
async function
|
|
160
|
-
const
|
|
161
|
-
if (!
|
|
158
|
+
async function ensureProvisionedWorkspaceForAuthenticatedUser(user, options = {}) {
|
|
159
|
+
const normalizedUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
160
|
+
if (!normalizedUserId) {
|
|
162
161
|
throw new AppError(400, "Invalid authenticated user payload.");
|
|
163
162
|
}
|
|
164
163
|
|
|
@@ -166,12 +165,15 @@ function createService({
|
|
|
166
165
|
return null;
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
return ensurePersonalWorkspaceForUser(
|
|
168
|
+
return ensurePersonalWorkspaceForUser({
|
|
169
|
+
...user,
|
|
170
|
+
id: normalizedUserId
|
|
171
|
+
}, options);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
async function createWorkspaceForAuthenticatedUser(user, payload = {}, options = {}) {
|
|
173
|
-
const
|
|
174
|
-
if (!
|
|
175
|
+
const normalizedUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
176
|
+
if (!normalizedUserId) {
|
|
175
177
|
throw new AppError(401, "Authentication required.");
|
|
176
178
|
}
|
|
177
179
|
|
|
@@ -190,14 +192,14 @@ function createService({
|
|
|
190
192
|
{
|
|
191
193
|
slug,
|
|
192
194
|
name: createInput.name,
|
|
193
|
-
ownerUserId:
|
|
195
|
+
ownerUserId: normalizedUserId,
|
|
194
196
|
isPersonal: false,
|
|
195
197
|
avatarUrl: ""
|
|
196
198
|
},
|
|
197
199
|
options
|
|
198
200
|
);
|
|
199
201
|
|
|
200
|
-
await workspaceMembershipsRepository.ensureOwnerMembership(inserted.id,
|
|
202
|
+
await workspaceMembershipsRepository.ensureOwnerMembership(inserted.id, normalizedUserId, options);
|
|
201
203
|
await ensureWorkspaceSettingsForWorkspace(inserted, options);
|
|
202
204
|
return inserted;
|
|
203
205
|
}
|
|
@@ -213,8 +215,8 @@ function createService({
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
async function resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options = {}) {
|
|
216
|
-
const
|
|
217
|
-
if (!
|
|
218
|
+
const normalizedUserId = normalizeRecordId(user?.id, { fallback: null });
|
|
219
|
+
if (!normalizedUserId) {
|
|
218
220
|
throw new AppError(401, "Authentication required.");
|
|
219
221
|
}
|
|
220
222
|
|
|
@@ -235,16 +237,16 @@ function createService({
|
|
|
235
237
|
|
|
236
238
|
let membership = await workspaceMembershipsRepository.findByWorkspaceIdAndUserId(
|
|
237
239
|
workspace.id,
|
|
238
|
-
|
|
240
|
+
normalizedUserId,
|
|
239
241
|
options
|
|
240
242
|
);
|
|
241
243
|
const actorOwnsWorkspace =
|
|
242
244
|
normalizeRecordId(workspace.ownerUserId, { fallback: null }) ===
|
|
243
|
-
|
|
245
|
+
normalizedUserId;
|
|
244
246
|
const membershipIsActive = normalizeLowerText(membership?.status) === "active";
|
|
245
247
|
|
|
246
248
|
if (!membershipIsActive && actorOwnsWorkspace) {
|
|
247
|
-
membership = await workspaceMembershipsRepository.ensureOwnerMembership(workspace.id,
|
|
249
|
+
membership = await workspaceMembershipsRepository.ensureOwnerMembership(workspace.id, normalizedUserId, options);
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
if (!membership || normalizeLowerText(membership.status) !== "active") {
|
|
@@ -268,7 +270,7 @@ function createService({
|
|
|
268
270
|
buildWorkspaceBaseSlug,
|
|
269
271
|
hashInviteToken,
|
|
270
272
|
ensurePersonalWorkspaceForUser,
|
|
271
|
-
|
|
273
|
+
ensureProvisionedWorkspaceForAuthenticatedUser,
|
|
272
274
|
createWorkspaceForAuthenticatedUser,
|
|
273
275
|
getWorkspaceForAuthenticatedUser,
|
|
274
276
|
updateWorkspaceForAuthenticatedUser,
|
|
@@ -1,62 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
3
|
+
|
|
4
|
+
const routeParamsSchema = createSchema({
|
|
5
|
+
workspaceSlug: {
|
|
6
|
+
type: "string",
|
|
7
|
+
required: false,
|
|
8
|
+
lowercase: true,
|
|
9
|
+
minLength: 1
|
|
10
|
+
},
|
|
11
|
+
memberUserId: {
|
|
12
|
+
type: "id",
|
|
13
|
+
required: false
|
|
14
|
+
},
|
|
15
|
+
inviteId: {
|
|
16
|
+
type: "id",
|
|
17
|
+
required: false
|
|
18
|
+
},
|
|
19
|
+
provider: {
|
|
20
|
+
type: "string",
|
|
21
|
+
required: false,
|
|
22
|
+
minLength: 1
|
|
15
23
|
}
|
|
24
|
+
});
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return normalized;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function normalizeWorkspaceSlugParams(input = {}) {
|
|
29
|
-
const source = normalizeObjectInput(input);
|
|
30
|
-
const normalized = {};
|
|
31
|
-
|
|
32
|
-
if (Object.hasOwn(source, "workspaceSlug")) {
|
|
33
|
-
normalized.workspaceSlug = normalizeText(source.workspaceSlug).toLowerCase();
|
|
26
|
+
const workspaceSlugParamsSchema = createSchema({
|
|
27
|
+
workspaceSlug: {
|
|
28
|
+
type: "string",
|
|
29
|
+
required: false,
|
|
30
|
+
lowercase: true,
|
|
31
|
+
minLength: 1
|
|
34
32
|
}
|
|
33
|
+
});
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const routeParamsValidator = Object.freeze({
|
|
40
|
-
schema: Type.Object(
|
|
41
|
-
{
|
|
42
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1 })),
|
|
43
|
-
memberUserId: Type.Optional(Type.String({ minLength: 1 })),
|
|
44
|
-
inviteId: Type.Optional(Type.String({ minLength: 1 })),
|
|
45
|
-
provider: Type.Optional(Type.String({ minLength: 1 }))
|
|
46
|
-
},
|
|
47
|
-
{ additionalProperties: false }
|
|
48
|
-
),
|
|
49
|
-
normalize: normalizeRouteParams
|
|
35
|
+
const routeParamsValidator = deepFreeze({
|
|
36
|
+
schema: routeParamsSchema,
|
|
37
|
+
mode: "patch"
|
|
50
38
|
});
|
|
51
39
|
|
|
52
|
-
const workspaceSlugParamsValidator =
|
|
53
|
-
schema:
|
|
54
|
-
|
|
55
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1 }))
|
|
56
|
-
},
|
|
57
|
-
{ additionalProperties: false }
|
|
58
|
-
),
|
|
59
|
-
normalize: normalizeWorkspaceSlugParams
|
|
40
|
+
const workspaceSlugParamsValidator = deepFreeze({
|
|
41
|
+
schema: workspaceSlugParamsSchema,
|
|
42
|
+
mode: "patch"
|
|
60
43
|
});
|
|
61
44
|
|
|
62
45
|
export { routeParamsValidator, workspaceSlugParamsValidator };
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
registerActionContextContributor
|
|
3
3
|
} from "@jskit-ai/kernel/server/actions";
|
|
4
4
|
import { registerRouteVisibilityResolver } from "@jskit-ai/kernel/server/http";
|
|
5
|
+
import { registerAuthPolicyContextResolver } from "@jskit-ai/auth-core/server/authPolicyContextResolverRegistry";
|
|
5
6
|
import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
|
|
6
7
|
import { registerProfileSyncLifecycleContributor } from "@jskit-ai/users-core/server/profileSyncLifecycleContributorRegistry";
|
|
7
8
|
import { createService as createWorkspaceService } from "./common/services/workspaceContextService.js";
|
|
@@ -66,12 +67,12 @@ function registerWorkspaceCore(app) {
|
|
|
66
67
|
return Object.freeze({
|
|
67
68
|
contributorId: "workspaces.core.profileSync",
|
|
68
69
|
order: 100,
|
|
69
|
-
async afterIdentityProfileSynced({ profile,
|
|
70
|
-
if (!
|
|
70
|
+
async afterIdentityProfileSynced({ profile, options } = {}) {
|
|
71
|
+
if (!profile || typeof workspaceService?.ensureProvisionedWorkspaceForAuthenticatedUser !== "function") {
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
await workspaceService.
|
|
75
|
+
await workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser(profile, options);
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
});
|
|
@@ -84,13 +85,11 @@ function registerWorkspaceCore(app) {
|
|
|
84
85
|
});
|
|
85
86
|
});
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
}
|
|
88
|
+
registerAuthPolicyContextResolver(app, "workspaces.core.authPolicyContextResolver", (scope) =>
|
|
89
|
+
createWorkspaceAuthPolicyContextResolver({
|
|
90
|
+
workspaceService: scope.make("workspaces.service")
|
|
91
|
+
})
|
|
92
|
+
);
|
|
94
93
|
|
|
95
94
|
registerRouteVisibilityResolver(app, "users.core.workspace.routeVisibilityResolver", (scope) =>
|
|
96
95
|
createWorkspaceRouteVisibilityResolver({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { INTERNAL_JSON_REST_API } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
|
|
1
2
|
import { createRepository as createWorkspacesRepository } from "./common/repositories/workspacesRepository.js";
|
|
2
3
|
import { createRepository as createWorkspaceMembershipsRepository } from "./common/repositories/workspaceMembershipsRepository.js";
|
|
3
4
|
import { createRepository as createWorkspaceInvitesRepository } from "./common/repositories/workspaceInvitesRepository.js";
|
|
@@ -8,18 +9,21 @@ function registerWorkspaceRepositories(app) {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
app.singleton("internal.repository.workspaces", (scope) => {
|
|
12
|
+
const api = scope.make(INTERNAL_JSON_REST_API);
|
|
11
13
|
const knex = scope.make("jskit.database.knex");
|
|
12
|
-
return createWorkspacesRepository(knex);
|
|
14
|
+
return createWorkspacesRepository({ api, knex });
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
app.singleton("internal.repository.workspace-memberships", (scope) => {
|
|
18
|
+
const api = scope.make(INTERNAL_JSON_REST_API);
|
|
16
19
|
const knex = scope.make("jskit.database.knex");
|
|
17
|
-
return createWorkspaceMembershipsRepository(knex);
|
|
20
|
+
return createWorkspaceMembershipsRepository({ api, knex });
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
app.singleton("internal.repository.workspace-invites", (scope) => {
|
|
24
|
+
const api = scope.make(INTERNAL_JSON_REST_API);
|
|
21
25
|
const knex = scope.make("jskit.database.knex");
|
|
22
|
-
return createWorkspaceInvitesRepository(knex);
|
|
26
|
+
return createWorkspaceInvitesRepository({ api, knex });
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -5,7 +5,7 @@ import { resolveWorkspace } from "./resolveWorkspace.js";
|
|
|
5
5
|
function createWorkspaceServerScopeSupport() {
|
|
6
6
|
return Object.freeze({
|
|
7
7
|
available: true,
|
|
8
|
-
|
|
8
|
+
params: workspaceSlugParamsValidator,
|
|
9
9
|
buildInputFromRouteParams(params = {}) {
|
|
10
10
|
return buildWorkspaceInputFromRouteParams(params);
|
|
11
11
|
},
|