@jskit-ai/users-core 0.1.48 → 0.1.50
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,82 +0,0 @@
|
|
|
1
|
-
import { Type } from "@fastify/type-provider-typebox";
|
|
2
|
-
import { encodeInviteTokenHash } from "@jskit-ai/auth-core/shared/inviteTokens";
|
|
3
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
4
|
-
import { createOperationMessages } from "../operationMessages.js";
|
|
5
|
-
import { normalizeObjectInput, recordIdSchema } from "@jskit-ai/kernel/shared/validators";
|
|
6
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
|
-
|
|
8
|
-
function normalizePendingInvite(invite) {
|
|
9
|
-
const id = normalizeRecordId(invite?.id, { fallback: null });
|
|
10
|
-
const workspaceId = normalizeRecordId(invite?.workspaceId, { fallback: null });
|
|
11
|
-
const tokenHash = normalizeText(invite?.tokenHash);
|
|
12
|
-
|
|
13
|
-
if (!id || !workspaceId || !tokenHash) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
id,
|
|
19
|
-
workspaceId,
|
|
20
|
-
workspaceSlug: normalizeText(invite?.workspaceSlug),
|
|
21
|
-
workspaceName: normalizeText(invite?.workspaceName || invite?.workspaceSlug),
|
|
22
|
-
workspaceAvatarUrl: normalizeText(invite?.workspaceAvatarUrl),
|
|
23
|
-
roleSid: normalizeLowerText(invite?.roleSid || "member") || "member",
|
|
24
|
-
status: normalizeLowerText(invite?.status || "pending") || "pending",
|
|
25
|
-
expiresAt: invite?.expiresAt || null,
|
|
26
|
-
token: encodeInviteTokenHash(tokenHash)
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function normalizePendingInviteList(invites) {
|
|
31
|
-
return (Array.isArray(invites) ? invites : []).map((invite) => normalizePendingInvite(invite)).filter(Boolean);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const pendingInviteRecordValidator = Object.freeze({
|
|
35
|
-
schema: Type.Object(
|
|
36
|
-
{
|
|
37
|
-
id: recordIdSchema,
|
|
38
|
-
workspaceId: recordIdSchema,
|
|
39
|
-
workspaceSlug: Type.String({ minLength: 1 }),
|
|
40
|
-
workspaceName: Type.String({ minLength: 1 }),
|
|
41
|
-
workspaceAvatarUrl: Type.String(),
|
|
42
|
-
roleSid: Type.String({ minLength: 1 }),
|
|
43
|
-
status: Type.String({ minLength: 1 }),
|
|
44
|
-
expiresAt: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
|
|
45
|
-
token: Type.String({ minLength: 1 })
|
|
46
|
-
},
|
|
47
|
-
{ additionalProperties: false }
|
|
48
|
-
),
|
|
49
|
-
normalize: normalizePendingInvite
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const pendingInvitationsListOutputValidator = Object.freeze({
|
|
53
|
-
schema: Type.Object(
|
|
54
|
-
{
|
|
55
|
-
pendingInvites: Type.Array(pendingInviteRecordValidator.schema)
|
|
56
|
-
},
|
|
57
|
-
{ additionalProperties: false }
|
|
58
|
-
),
|
|
59
|
-
normalize(payload = {}) {
|
|
60
|
-
const source = normalizeObjectInput(payload);
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
pendingInvites: normalizePendingInviteList(source.pendingInvites)
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const WORKSPACE_PENDING_INVITATIONS_MESSAGES = createOperationMessages();
|
|
69
|
-
|
|
70
|
-
const workspacePendingInvitationsResource = Object.freeze({
|
|
71
|
-
resource: "workspacePendingInvitations",
|
|
72
|
-
messages: WORKSPACE_PENDING_INVITATIONS_MESSAGES,
|
|
73
|
-
operations: Object.freeze({
|
|
74
|
-
list: Object.freeze({
|
|
75
|
-
method: "GET",
|
|
76
|
-
messages: WORKSPACE_PENDING_INVITATIONS_MESSAGES,
|
|
77
|
-
outputValidator: pendingInvitationsListOutputValidator
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export { workspacePendingInvitationsResource };
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
-
import {
|
|
4
|
-
normalizeObjectInput,
|
|
5
|
-
createCursorListValidator,
|
|
6
|
-
recordIdSchema,
|
|
7
|
-
recordIdInputSchema
|
|
8
|
-
} from "@jskit-ai/kernel/shared/validators";
|
|
9
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
10
|
-
|
|
11
|
-
function normalizeWorkspaceAvatarUrl(value) {
|
|
12
|
-
const avatarUrl = normalizeText(value);
|
|
13
|
-
if (!avatarUrl) {
|
|
14
|
-
return "";
|
|
15
|
-
}
|
|
16
|
-
if (!avatarUrl.startsWith("http://") && !avatarUrl.startsWith("https://")) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
return new URL(avatarUrl).toString();
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function normalizeWorkspaceInput(payload = {}) {
|
|
27
|
-
const source = normalizeObjectInput(payload);
|
|
28
|
-
const normalized = {};
|
|
29
|
-
|
|
30
|
-
if (Object.hasOwn(source, "slug")) {
|
|
31
|
-
normalized.slug = normalizeLowerText(source.slug);
|
|
32
|
-
}
|
|
33
|
-
if (Object.hasOwn(source, "name")) {
|
|
34
|
-
normalized.name = normalizeText(source.name);
|
|
35
|
-
}
|
|
36
|
-
if (Object.hasOwn(source, "ownerUserId")) {
|
|
37
|
-
normalized.ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: "" });
|
|
38
|
-
}
|
|
39
|
-
if (Object.hasOwn(source, "avatarUrl")) {
|
|
40
|
-
normalized.avatarUrl = normalizeWorkspaceAvatarUrl(source.avatarUrl);
|
|
41
|
-
}
|
|
42
|
-
if (Object.hasOwn(source, "isPersonal")) {
|
|
43
|
-
normalized.isPersonal = source.isPersonal === true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return normalized;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function normalizeWorkspaceOutput(payload = {}) {
|
|
50
|
-
const source = normalizeObjectInput(payload);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
id: normalizeRecordId(source.id, { fallback: "" }),
|
|
54
|
-
slug: normalizeLowerText(source.slug),
|
|
55
|
-
name: normalizeText(source.name),
|
|
56
|
-
ownerUserId: normalizeRecordId(source.ownerUserId, { fallback: "" }),
|
|
57
|
-
avatarUrl: normalizeText(source.avatarUrl)
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function normalizeWorkspaceListItemOutput(payload = {}) {
|
|
62
|
-
const source = normalizeObjectInput(payload);
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
id: normalizeRecordId(source.id, { fallback: "" }),
|
|
66
|
-
slug: normalizeLowerText(source.slug),
|
|
67
|
-
name: normalizeText(source.name),
|
|
68
|
-
avatarUrl: normalizeText(source.avatarUrl),
|
|
69
|
-
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
70
|
-
isAccessible: source.isAccessible !== false
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const responseRecordSchema = Type.Object(
|
|
75
|
-
{
|
|
76
|
-
id: recordIdSchema,
|
|
77
|
-
slug: Type.String({ minLength: 1 }),
|
|
78
|
-
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
79
|
-
ownerUserId: recordIdSchema,
|
|
80
|
-
avatarUrl: Type.String()
|
|
81
|
-
},
|
|
82
|
-
{ additionalProperties: false }
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const listItemSchema = Type.Object(
|
|
86
|
-
{
|
|
87
|
-
id: recordIdSchema,
|
|
88
|
-
slug: Type.String({ minLength: 1 }),
|
|
89
|
-
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
90
|
-
avatarUrl: Type.String(),
|
|
91
|
-
roleSid: Type.String({ minLength: 1 }),
|
|
92
|
-
isAccessible: Type.Boolean()
|
|
93
|
-
},
|
|
94
|
-
{ additionalProperties: false }
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const createRequestBodySchema = Type.Object(
|
|
98
|
-
{
|
|
99
|
-
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
100
|
-
slug: Type.Optional(Type.String({ minLength: 1, maxLength: 120 })),
|
|
101
|
-
ownerUserId: Type.Optional(recordIdInputSchema)
|
|
102
|
-
},
|
|
103
|
-
{ additionalProperties: false }
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const patchRequestBodySchema = Type.Object(
|
|
107
|
-
{
|
|
108
|
-
name: Type.Optional(Type.String({ minLength: 1, maxLength: 160 })),
|
|
109
|
-
avatarUrl: Type.Optional(
|
|
110
|
-
Type.String({
|
|
111
|
-
pattern: "^(https?://.+)?$",
|
|
112
|
-
messages: {
|
|
113
|
-
pattern: "Workspace avatar URL must be a valid absolute URL (http:// or https://).",
|
|
114
|
-
default: "Workspace avatar URL must be a valid absolute URL (http:// or https://)."
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
)
|
|
118
|
-
},
|
|
119
|
-
{ additionalProperties: false }
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
const responseRecordValidator = Object.freeze({
|
|
123
|
-
schema: responseRecordSchema,
|
|
124
|
-
normalize: normalizeWorkspaceOutput
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const workspaceSummaryOutputValidator = Object.freeze({
|
|
128
|
-
schema: listItemSchema,
|
|
129
|
-
normalize: normalizeWorkspaceListItemOutput
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const resource = {
|
|
133
|
-
resource: "workspace",
|
|
134
|
-
messages: {
|
|
135
|
-
validation: "Fix invalid workspace values and try again.",
|
|
136
|
-
saveSuccess: "Workspace updated.",
|
|
137
|
-
saveError: "Unable to update workspace.",
|
|
138
|
-
apiValidation: "Validation failed."
|
|
139
|
-
},
|
|
140
|
-
operations: {
|
|
141
|
-
view: {
|
|
142
|
-
method: "GET",
|
|
143
|
-
outputValidator: responseRecordValidator
|
|
144
|
-
},
|
|
145
|
-
list: {
|
|
146
|
-
method: "GET",
|
|
147
|
-
outputValidator: createCursorListValidator(workspaceSummaryOutputValidator)
|
|
148
|
-
},
|
|
149
|
-
create: {
|
|
150
|
-
method: "POST",
|
|
151
|
-
bodyValidator: {
|
|
152
|
-
schema: createRequestBodySchema,
|
|
153
|
-
normalize: normalizeWorkspaceInput
|
|
154
|
-
},
|
|
155
|
-
outputValidator: responseRecordValidator
|
|
156
|
-
},
|
|
157
|
-
replace: {
|
|
158
|
-
method: "PUT",
|
|
159
|
-
bodyValidator: {
|
|
160
|
-
schema: createRequestBodySchema,
|
|
161
|
-
normalize: normalizeWorkspaceInput
|
|
162
|
-
},
|
|
163
|
-
outputValidator: responseRecordValidator
|
|
164
|
-
},
|
|
165
|
-
patch: {
|
|
166
|
-
method: "PATCH",
|
|
167
|
-
bodyValidator: {
|
|
168
|
-
schema: patchRequestBodySchema,
|
|
169
|
-
normalize: normalizeWorkspaceInput
|
|
170
|
-
},
|
|
171
|
-
outputValidator: responseRecordValidator
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
export { resource as workspaceResource };
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
2
|
-
import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
|
|
3
|
-
|
|
4
|
-
const workspaceSettingsFields = resolveGlobalArrayRegistry("jskit.users-core.workspaceSettingsFields");
|
|
5
|
-
|
|
6
|
-
function defineField(field = {}) {
|
|
7
|
-
const key = normalizeText(field.key);
|
|
8
|
-
if (!key) {
|
|
9
|
-
throw new TypeError("workspaceSettingsFields.defineField requires field.key.");
|
|
10
|
-
}
|
|
11
|
-
if (workspaceSettingsFields.some((entry) => entry.key === key)) {
|
|
12
|
-
throw new Error(`workspaceSettingsFields.defineField duplicate key: ${key}`);
|
|
13
|
-
}
|
|
14
|
-
if (!field.inputSchema || typeof field.inputSchema !== "object") {
|
|
15
|
-
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires inputSchema.`);
|
|
16
|
-
}
|
|
17
|
-
if (!field.outputSchema || typeof field.outputSchema !== "object") {
|
|
18
|
-
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires outputSchema.`);
|
|
19
|
-
}
|
|
20
|
-
const dbColumn = normalizeText(field.dbColumn);
|
|
21
|
-
if (!dbColumn) {
|
|
22
|
-
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires dbColumn.`);
|
|
23
|
-
}
|
|
24
|
-
if (typeof field.normalizeInput !== "function") {
|
|
25
|
-
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires normalizeInput.`);
|
|
26
|
-
}
|
|
27
|
-
if (typeof field.normalizeOutput !== "function") {
|
|
28
|
-
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires normalizeOutput.`);
|
|
29
|
-
}
|
|
30
|
-
if (typeof field.resolveDefault !== "function") {
|
|
31
|
-
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires resolveDefault.`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
workspaceSettingsFields.push({
|
|
35
|
-
key,
|
|
36
|
-
dbColumn,
|
|
37
|
-
required: field.required !== false,
|
|
38
|
-
inputSchema: field.inputSchema,
|
|
39
|
-
outputSchema: field.outputSchema,
|
|
40
|
-
normalizeInput: field.normalizeInput,
|
|
41
|
-
normalizeOutput: field.normalizeOutput,
|
|
42
|
-
resolveDefault: field.resolveDefault
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function resetWorkspaceSettingsFields() {
|
|
47
|
-
workspaceSettingsFields.splice(0, workspaceSettingsFields.length);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveWorkspaceSettingsFieldKeys() {
|
|
51
|
-
return workspaceSettingsFields.map((field) => field.key);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export {
|
|
55
|
-
defineField,
|
|
56
|
-
resetWorkspaceSettingsFields,
|
|
57
|
-
resolveWorkspaceSettingsFieldKeys,
|
|
58
|
-
workspaceSettingsFields
|
|
59
|
-
};
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
-
import {
|
|
4
|
-
normalizeObjectInput,
|
|
5
|
-
createCursorListValidator,
|
|
6
|
-
normalizeSettingsFieldInput,
|
|
7
|
-
recordIdSchema
|
|
8
|
-
} from "@jskit-ai/kernel/shared/validators";
|
|
9
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
10
|
-
import { workspaceSettingsFields } from "./workspaceSettingsFields.js";
|
|
11
|
-
import { createWorkspaceRoleCatalog } from "../roles.js";
|
|
12
|
-
|
|
13
|
-
function buildCreateBodySchema() {
|
|
14
|
-
const properties = {};
|
|
15
|
-
for (const field of workspaceSettingsFields) {
|
|
16
|
-
properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return Type.Object(properties, {
|
|
20
|
-
additionalProperties: false,
|
|
21
|
-
messages: {
|
|
22
|
-
additionalProperties: "Unexpected field.",
|
|
23
|
-
default: "Invalid value."
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildSettingsOutputSchema() {
|
|
29
|
-
const properties = {};
|
|
30
|
-
for (const field of workspaceSettingsFields) {
|
|
31
|
-
properties[field.key] = field.outputSchema;
|
|
32
|
-
}
|
|
33
|
-
properties.invitesAvailable = Type.Boolean();
|
|
34
|
-
properties.invitesEffective = Type.Boolean();
|
|
35
|
-
|
|
36
|
-
return Type.Object(properties, { additionalProperties: false });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function buildResponseRecordSchema() {
|
|
40
|
-
return Type.Object(
|
|
41
|
-
{
|
|
42
|
-
workspace: Type.Object(
|
|
43
|
-
{
|
|
44
|
-
id: recordIdSchema,
|
|
45
|
-
slug: Type.String({ minLength: 1 }),
|
|
46
|
-
ownerUserId: recordIdSchema
|
|
47
|
-
},
|
|
48
|
-
{ additionalProperties: false }
|
|
49
|
-
),
|
|
50
|
-
settings: buildSettingsOutputSchema(),
|
|
51
|
-
roleCatalog: Type.Object(
|
|
52
|
-
{
|
|
53
|
-
collaborationEnabled: Type.Boolean(),
|
|
54
|
-
defaultInviteRole: Type.String(),
|
|
55
|
-
roles: Type.Array(Type.Object({}, { additionalProperties: true })),
|
|
56
|
-
assignableRoleIds: Type.Array(Type.String({ minLength: 1 }))
|
|
57
|
-
},
|
|
58
|
-
{ additionalProperties: true }
|
|
59
|
-
)
|
|
60
|
-
},
|
|
61
|
-
{ additionalProperties: false }
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function normalizeInput(payload = {}) {
|
|
66
|
-
return normalizeSettingsFieldInput(payload, workspaceSettingsFields);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function normalizeOutput(payload = {}) {
|
|
70
|
-
const source = normalizeObjectInput(payload);
|
|
71
|
-
const workspace = normalizeObjectInput(source.workspace);
|
|
72
|
-
const settings = normalizeObjectInput(source.settings);
|
|
73
|
-
const normalizedSettings = {};
|
|
74
|
-
|
|
75
|
-
for (const field of workspaceSettingsFields) {
|
|
76
|
-
const rawValue = Object.hasOwn(settings, field.key)
|
|
77
|
-
? settings[field.key]
|
|
78
|
-
: field.resolveDefault({
|
|
79
|
-
workspace,
|
|
80
|
-
settings
|
|
81
|
-
});
|
|
82
|
-
normalizedSettings[field.key] = field.normalizeOutput(rawValue, {
|
|
83
|
-
workspace,
|
|
84
|
-
settings
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const invitesEnabled = normalizedSettings.invitesEnabled !== false;
|
|
89
|
-
const invitesAvailable = settings.invitesAvailable !== false;
|
|
90
|
-
const invitesEffective =
|
|
91
|
-
typeof settings.invitesEffective === "boolean" ? settings.invitesEffective : invitesEnabled;
|
|
92
|
-
normalizedSettings.invitesEnabled = invitesEnabled;
|
|
93
|
-
normalizedSettings.invitesAvailable = invitesAvailable;
|
|
94
|
-
normalizedSettings.invitesEffective = invitesEffective;
|
|
95
|
-
const roleCatalog = normalizeObjectInput(source.roleCatalog);
|
|
96
|
-
const hasRoleCatalog =
|
|
97
|
-
Array.isArray(roleCatalog.roles) &&
|
|
98
|
-
roleCatalog.roles.length > 0 &&
|
|
99
|
-
Array.isArray(roleCatalog.assignableRoleIds);
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
workspace: {
|
|
103
|
-
id: normalizeRecordId(workspace.id, { fallback: "" }),
|
|
104
|
-
slug: normalizeText(workspace.slug),
|
|
105
|
-
ownerUserId: normalizeRecordId(workspace.ownerUserId, { fallback: "" })
|
|
106
|
-
},
|
|
107
|
-
settings: normalizedSettings,
|
|
108
|
-
roleCatalog: hasRoleCatalog ? roleCatalog : createWorkspaceRoleCatalog()
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const responseRecordValidator = Object.freeze({
|
|
113
|
-
get schema() {
|
|
114
|
-
return buildResponseRecordSchema();
|
|
115
|
-
},
|
|
116
|
-
normalize: normalizeOutput
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const resource = {
|
|
120
|
-
resource: "workspaceSettings",
|
|
121
|
-
messages: {
|
|
122
|
-
validation: "Fix invalid workspace settings values and try again.",
|
|
123
|
-
saveSuccess: "Workspace settings updated.",
|
|
124
|
-
saveError: "Unable to update workspace settings.",
|
|
125
|
-
apiValidation: "Validation failed."
|
|
126
|
-
},
|
|
127
|
-
operations: {
|
|
128
|
-
view: {
|
|
129
|
-
method: "GET",
|
|
130
|
-
outputValidator: responseRecordValidator
|
|
131
|
-
},
|
|
132
|
-
list: {
|
|
133
|
-
method: "GET",
|
|
134
|
-
outputValidator: createCursorListValidator(responseRecordValidator)
|
|
135
|
-
},
|
|
136
|
-
create: {
|
|
137
|
-
method: "POST",
|
|
138
|
-
bodyValidator: {
|
|
139
|
-
get schema() {
|
|
140
|
-
return buildCreateBodySchema();
|
|
141
|
-
},
|
|
142
|
-
normalize: normalizeInput
|
|
143
|
-
},
|
|
144
|
-
outputValidator: responseRecordValidator
|
|
145
|
-
},
|
|
146
|
-
replace: {
|
|
147
|
-
method: "PUT",
|
|
148
|
-
bodyValidator: {
|
|
149
|
-
get schema() {
|
|
150
|
-
return buildCreateBodySchema();
|
|
151
|
-
},
|
|
152
|
-
normalize: normalizeInput
|
|
153
|
-
},
|
|
154
|
-
outputValidator: responseRecordValidator
|
|
155
|
-
},
|
|
156
|
-
patch: {
|
|
157
|
-
method: "PATCH",
|
|
158
|
-
bodyValidator: {
|
|
159
|
-
get schema() {
|
|
160
|
-
return Type.Partial(buildCreateBodySchema(), { additionalProperties: false });
|
|
161
|
-
},
|
|
162
|
-
normalize: normalizeInput
|
|
163
|
-
},
|
|
164
|
-
outputValidator: responseRecordValidator
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
export { resource as workspaceSettingsResource };
|
package/src/shared/roles.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
hasPermission,
|
|
3
|
-
normalizePermissionList
|
|
4
|
-
} from "@jskit-ai/kernel/shared/support";
|
|
5
|
-
|
|
6
|
-
const OWNER_ROLE_ID = "owner";
|
|
7
|
-
const ADMIN_ROLE_ID = "admin";
|
|
8
|
-
const MEMBER_ROLE_ID = "member";
|
|
9
|
-
|
|
10
|
-
function asRecord(value) {
|
|
11
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
12
|
-
return {};
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeRoleId(value) {
|
|
18
|
-
return String(value || "")
|
|
19
|
-
.trim()
|
|
20
|
-
.toLowerCase();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function resolveInheritedRolePermissions(roleSid, configuredRoles = {}, seenRoleIds = new Set()) {
|
|
24
|
-
if (seenRoleIds.has(roleSid)) {
|
|
25
|
-
throw new TypeError(`roleCatalog role "${roleSid}" has circular inheritance.`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const source = asRecord(configuredRoles[roleSid]);
|
|
29
|
-
const inheritedRoleId = normalizeRoleId(source.inherits);
|
|
30
|
-
const directPermissions = normalizePermissionList(source.permissions);
|
|
31
|
-
if (!inheritedRoleId) {
|
|
32
|
-
return directPermissions;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!Object.hasOwn(configuredRoles, inheritedRoleId)) {
|
|
36
|
-
throw new TypeError(`roleCatalog role "${roleSid}" inherits unknown role "${inheritedRoleId}".`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const nextSeenRoleIds = new Set(seenRoleIds);
|
|
40
|
-
nextSeenRoleIds.add(roleSid);
|
|
41
|
-
|
|
42
|
-
return normalizePermissionList([
|
|
43
|
-
...resolveInheritedRolePermissions(inheritedRoleId, configuredRoles, nextSeenRoleIds),
|
|
44
|
-
...directPermissions
|
|
45
|
-
]);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function createRoleDescriptor(roleSid, configuredDefinition, configuredRoles = {}) {
|
|
49
|
-
const source = asRecord(configuredDefinition);
|
|
50
|
-
const assignable = roleSid === OWNER_ROLE_ID ? false : source.assignable === true;
|
|
51
|
-
const permissions = resolveInheritedRolePermissions(roleSid, configuredRoles);
|
|
52
|
-
|
|
53
|
-
return Object.freeze({
|
|
54
|
-
id: roleSid,
|
|
55
|
-
assignable,
|
|
56
|
-
permissions: Object.freeze([...permissions])
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function listConfiguredRoleIds(appConfig = {}) {
|
|
61
|
-
const configuredRoles = normalizeConfiguredRoles(appConfig);
|
|
62
|
-
return Object.freeze(Object.keys(configuredRoles));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function resolveConfiguredDefaultInviteRole(appConfig = {}) {
|
|
66
|
-
return normalizeRoleId(appConfig?.roleCatalog?.workspace?.defaultInviteRole);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function normalizeConfiguredRoles(appConfig = {}) {
|
|
70
|
-
const roleCatalog = asRecord(appConfig?.roleCatalog);
|
|
71
|
-
const configuredRoles = asRecord(roleCatalog.roles);
|
|
72
|
-
const normalizedRoles = {};
|
|
73
|
-
|
|
74
|
-
for (const [roleSid, roleDefinition] of Object.entries(configuredRoles)) {
|
|
75
|
-
const normalizedRoleId = normalizeRoleId(roleSid);
|
|
76
|
-
if (!normalizedRoleId) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
normalizedRoles[normalizedRoleId] = roleDefinition;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return normalizedRoles;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function createWorkspaceRoleCatalog(appConfig = {}) {
|
|
86
|
-
const configuredRoles = normalizeConfiguredRoles(appConfig);
|
|
87
|
-
const roleIds = listConfiguredRoleIds(appConfig);
|
|
88
|
-
const roles = roleIds.map((roleSid) => createRoleDescriptor(roleSid, configuredRoles[roleSid], configuredRoles));
|
|
89
|
-
const assignableRoleIds = roles.filter((role) => role.assignable).map((role) => role.id);
|
|
90
|
-
const configuredDefaultInviteRole = resolveConfiguredDefaultInviteRole(appConfig);
|
|
91
|
-
const defaultInviteRole = assignableRoleIds.includes(configuredDefaultInviteRole)
|
|
92
|
-
? configuredDefaultInviteRole
|
|
93
|
-
: assignableRoleIds[0] || "";
|
|
94
|
-
|
|
95
|
-
return Object.freeze({
|
|
96
|
-
collaborationEnabled: assignableRoleIds.length > 0 && Boolean(defaultInviteRole),
|
|
97
|
-
defaultInviteRole,
|
|
98
|
-
roles: Object.freeze(
|
|
99
|
-
roles.map((role) =>
|
|
100
|
-
Object.freeze({
|
|
101
|
-
id: role.id,
|
|
102
|
-
assignable: role.assignable,
|
|
103
|
-
permissions: Object.freeze([...role.permissions])
|
|
104
|
-
})
|
|
105
|
-
)
|
|
106
|
-
),
|
|
107
|
-
assignableRoleIds: Object.freeze([...assignableRoleIds])
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function cloneWorkspaceRoleCatalog(roleCatalog = null) {
|
|
112
|
-
const source = asRecord(roleCatalog);
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
collaborationEnabled: source.collaborationEnabled === true,
|
|
116
|
-
defaultInviteRole: String(source.defaultInviteRole || ""),
|
|
117
|
-
roles: Array.isArray(source.roles)
|
|
118
|
-
? source.roles.map((role) => ({
|
|
119
|
-
id: normalizeRoleId(role?.id),
|
|
120
|
-
assignable: role?.assignable === true,
|
|
121
|
-
permissions: Array.isArray(role?.permissions) ? [...role.permissions] : []
|
|
122
|
-
}))
|
|
123
|
-
: [],
|
|
124
|
-
assignableRoleIds: Array.isArray(source.assignableRoleIds) ? [...source.assignableRoleIds] : []
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function listRoleDescriptors(appConfig = {}) {
|
|
129
|
-
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
130
|
-
return roleCatalog.roles.map((role) => ({
|
|
131
|
-
id: role.id,
|
|
132
|
-
assignable: role.assignable,
|
|
133
|
-
permissions: [...role.permissions]
|
|
134
|
-
}));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function resolveRolePermissions(roleSid, appConfig = {}) {
|
|
138
|
-
const normalizedRoleId = normalizeRoleId(roleSid);
|
|
139
|
-
if (!normalizedRoleId) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
144
|
-
const role = roleCatalog.roles.find((entry) => entry.id === normalizedRoleId);
|
|
145
|
-
if (!role) {
|
|
146
|
-
return [];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return [...role.permissions];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export {
|
|
153
|
-
OWNER_ROLE_ID,
|
|
154
|
-
ADMIN_ROLE_ID,
|
|
155
|
-
MEMBER_ROLE_ID,
|
|
156
|
-
resolveRolePermissions,
|
|
157
|
-
listRoleDescriptors,
|
|
158
|
-
createWorkspaceRoleCatalog,
|
|
159
|
-
cloneWorkspaceRoleCatalog,
|
|
160
|
-
hasPermission
|
|
161
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
2
|
-
import { splitPathQueryAndHash } from "@jskit-ai/kernel/shared/support";
|
|
3
|
-
|
|
4
|
-
const USERS_PUBLIC_API_BASE_PATH = "/api";
|
|
5
|
-
const USERS_WORKSPACE_API_BASE_PATH = "/api/w/:workspaceSlug";
|
|
6
|
-
|
|
7
|
-
function normalizeApiRelativePath(relativePath = "/") {
|
|
8
|
-
const { pathname, queryString, hash } = splitPathQueryAndHash(relativePath);
|
|
9
|
-
const normalizedPath = normalizePathname(pathname || "/") || "/";
|
|
10
|
-
const normalizedQueryString = String(queryString || "").trim().replace(/^\?+/, "");
|
|
11
|
-
const normalizedHash = String(hash || "").trim();
|
|
12
|
-
const querySuffix = normalizedQueryString ? `?${normalizedQueryString}` : "";
|
|
13
|
-
return `${normalizedPath}${querySuffix}${normalizedHash}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function normalizeSurfaceWorkspaceRequirement(value = false) {
|
|
17
|
-
return value === true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function resolveApiBasePath({ surfaceRequiresWorkspace = false, relativePath = "/" } = {}) {
|
|
21
|
-
const basePath = normalizeSurfaceWorkspaceRequirement(surfaceRequiresWorkspace)
|
|
22
|
-
? USERS_WORKSPACE_API_BASE_PATH
|
|
23
|
-
: USERS_PUBLIC_API_BASE_PATH;
|
|
24
|
-
const normalizedRelativePath = normalizeApiRelativePath(relativePath);
|
|
25
|
-
|
|
26
|
-
if (normalizedRelativePath === "/") {
|
|
27
|
-
return basePath;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (normalizedRelativePath.startsWith("/?") || normalizedRelativePath.startsWith("/#")) {
|
|
31
|
-
return `${basePath}${normalizedRelativePath.slice(1)}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return `${basePath}${normalizedRelativePath}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
USERS_PUBLIC_API_BASE_PATH,
|
|
39
|
-
USERS_WORKSPACE_API_BASE_PATH,
|
|
40
|
-
normalizeApiRelativePath,
|
|
41
|
-
normalizeSurfaceWorkspaceRequirement,
|
|
42
|
-
resolveApiBasePath
|
|
43
|
-
};
|