@jskit-ai/users-core 0.1.4
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 +464 -0
- package/package.json +35 -0
- package/src/server/UsersCoreServiceProvider.js +74 -0
- package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
- package/src/server/accountNotifications/accountNotificationsService.js +41 -0
- package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
- package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
- package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
- package/src/server/accountPreferences/accountPreferencesService.js +41 -0
- package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
- package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
- package/src/server/accountProfile/accountProfileActions.js +137 -0
- package/src/server/accountProfile/accountProfileService.js +124 -0
- package/src/server/accountProfile/avatarService.js +141 -0
- package/src/server/accountProfile/avatarStorageService.js +132 -0
- package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
- package/src/server/accountProfile/registerAccountProfile.js +62 -0
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
- package/src/server/accountSecurity/accountSecurityActions.js +144 -0
- package/src/server/accountSecurity/accountSecurityService.js +103 -0
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
- package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
- package/src/server/common/README.md +21 -0
- package/src/server/common/contributors/README.md +11 -0
- package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
- package/src/server/common/diTokens.js +21 -0
- package/src/server/common/formatters/README.md +11 -0
- package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
- package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
- package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
- package/src/server/common/formatters/workspaceFormatter.js +46 -0
- package/src/server/common/registerCommonRepositories.js +45 -0
- package/src/server/common/registerSharedApi.js +9 -0
- package/src/server/common/repositories/README.md +24 -0
- package/src/server/common/repositories/repositoryUtils.js +50 -0
- package/src/server/common/repositories/userProfilesRepository.js +251 -0
- package/src/server/common/repositories/userSettingsRepository.js +179 -0
- package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
- package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
- package/src/server/common/repositories/workspacesRepository.js +183 -0
- package/src/server/common/routes/README.md +11 -0
- package/src/server/common/services/README.md +12 -0
- package/src/server/common/services/accountContextService.js +31 -0
- package/src/server/common/services/authProfileSyncService.js +128 -0
- package/src/server/common/services/workspaceContextService.js +270 -0
- package/src/server/common/support/deepFreeze.js +17 -0
- package/src/server/common/support/realtimeServiceEvents.js +94 -0
- package/src/server/common/support/resolveActionUser.js +11 -0
- package/src/server/common/support/workspaceRoutePaths.js +17 -0
- package/src/server/common/validators/README.md +11 -0
- package/src/server/common/validators/authenticatedUserValidator.js +42 -0
- package/src/server/common/validators/routeParamsValidator.js +62 -0
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
- package/src/server/consoleSettings/consoleService.js +36 -0
- package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
- package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
- package/src/server/consoleSettings/consoleSettingsService.js +40 -0
- package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
- package/src/server/registerWorkspaceBootstrap.js +36 -0
- package/src/server/registerWorkspaceCore.js +95 -0
- package/src/server/support/resolveWorkspace.js +16 -0
- package/src/server/support/workspaceActionSurfaces.js +135 -0
- package/src/server/support/workspaceInvitationsPolicy.js +45 -0
- package/src/server/support/workspaceRouteInput.js +22 -0
- package/src/server/workspaceBootstrapContributor.js +401 -0
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
- package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
- package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
- package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
- package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
- package/src/shared/events/usersEvents.js +19 -0
- package/src/shared/index.js +91 -0
- package/src/shared/operationMessages.js +16 -0
- package/src/shared/resources/consoleSettingsFields.js +55 -0
- package/src/shared/resources/consoleSettingsResource.js +139 -0
- package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
- package/src/shared/resources/userProfileResource.js +148 -0
- package/src/shared/resources/userSettingsFields.js +71 -0
- package/src/shared/resources/userSettingsResource.js +416 -0
- package/src/shared/resources/workspaceMembersResource.js +352 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
- package/src/shared/resources/workspaceResource.js +149 -0
- package/src/shared/resources/workspaceSettingsFields.js +60 -0
- package/src/shared/resources/workspaceSettingsResource.js +178 -0
- package/src/shared/roles.js +136 -0
- package/src/shared/settings.js +31 -0
- package/src/shared/support/usersApiPaths.js +34 -0
- package/src/shared/support/usersVisibility.js +45 -0
- package/src/shared/support/workspacePathModel.js +145 -0
- package/src/shared/tenancyMode.js +35 -0
- package/src/shared/tenancyProfile.js +73 -0
- package/templates/config/workspaceRoles.js +30 -0
- package/templates/migrations/users_core_console_owner.cjs +39 -0
- package/templates/migrations/users_core_initial.cjs +118 -0
- package/templates/migrations/users_core_profile_username.cjs +98 -0
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
- package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
- package/test/authProfileSyncService.test.js +119 -0
- package/test/avatarService.test.js +114 -0
- package/test/avatarStorageService.test.js +61 -0
- package/test/consoleService.test.js +57 -0
- package/test/consoleSettingsService.test.js +86 -0
- package/test/exportsContract.test.js +38 -0
- package/test/registerAvatarMultipartSupport.test.js +64 -0
- package/test/registerServiceRealtimeEvents.test.js +160 -0
- package/test/registerWorkspaceDirectory.test.js +26 -0
- package/test/registerWorkspaceSettings.test.js +44 -0
- package/test/resourcesCanonical.test.js +90 -0
- package/test/roles.test.js +74 -0
- package/test/settingsFieldRegistriesSingleton.test.js +24 -0
- package/test/tenancyProfile.test.js +67 -0
- package/test/userSettingsResource.test.js +31 -0
- package/test/usersApiPaths.test.js +31 -0
- package/test/usersRouteRequestInputValidator.test.js +556 -0
- package/test/usersRouteResources.test.js +113 -0
- package/test/usersRouteValidators.test.js +49 -0
- package/test/usersVisibility.test.js +22 -0
- package/test/workspaceActionContextContributor.test.js +251 -0
- package/test/workspaceActionSurfaces.test.js +105 -0
- package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
- package/test/workspaceBootstrapContributor.test.js +466 -0
- package/test/workspaceInvitationsPolicy.test.js +71 -0
- package/test/workspaceInvitesRepository.test.js +111 -0
- package/test/workspaceMembersService.test.js +400 -0
- package/test/workspacePathModel.test.js +93 -0
- package/test/workspacePendingInvitationsResource.test.js +38 -0
- package/test/workspacePendingInvitationsService.test.js +151 -0
- package/test/workspaceRouteVisibilityResolver.test.js +83 -0
- package/test/workspaceService.test.js +480 -0
- package/test/workspaceSettingsActions.test.js +42 -0
- package/test/workspaceSettingsRepository.test.js +156 -0
- package/test/workspaceSettingsResource.test.js +156 -0
- package/test/workspaceSettingsService.test.js +120 -0
- package/test-support/registerDefaultSettingsFields.js +3 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { Type } from "@fastify/type-provider-typebox";
|
|
2
|
+
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
4
|
+
import { normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
import { createOperationMessages } from "../operationMessages.js";
|
|
6
|
+
import { createWorkspaceRoleCatalog, OWNER_ROLE_ID } from "../roles.js";
|
|
7
|
+
|
|
8
|
+
const workspaceSummaryOutputSchema = Type.Object(
|
|
9
|
+
{
|
|
10
|
+
id: Type.Integer({ minimum: 1 }),
|
|
11
|
+
slug: Type.String({ minLength: 1 }),
|
|
12
|
+
name: Type.String({ minLength: 1 }),
|
|
13
|
+
ownerUserId: Type.Integer({ minimum: 1 }),
|
|
14
|
+
avatarUrl: Type.String(),
|
|
15
|
+
color: Type.String({ minLength: 1 })
|
|
16
|
+
},
|
|
17
|
+
{ additionalProperties: false }
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const memberSummaryOutputSchema = Type.Object(
|
|
21
|
+
{
|
|
22
|
+
userId: Type.Integer({ minimum: 1 }),
|
|
23
|
+
roleId: Type.String({ minLength: 1 }),
|
|
24
|
+
status: Type.String({ minLength: 1 }),
|
|
25
|
+
displayName: Type.String(),
|
|
26
|
+
email: Type.String({ minLength: 1 }),
|
|
27
|
+
isOwner: Type.Boolean()
|
|
28
|
+
},
|
|
29
|
+
{ additionalProperties: false }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const inviteSummaryOutputSchema = Type.Object(
|
|
33
|
+
{
|
|
34
|
+
id: Type.Integer({ minimum: 1 }),
|
|
35
|
+
email: Type.String({ minLength: 3, format: "email" }),
|
|
36
|
+
roleId: Type.String({ minLength: 1 }),
|
|
37
|
+
status: Type.String({ minLength: 1 }),
|
|
38
|
+
expiresAt: Type.String({ minLength: 1 }),
|
|
39
|
+
invitedByUserId: Type.Union([Type.Integer({ minimum: 1 }), Type.Null()])
|
|
40
|
+
},
|
|
41
|
+
{ additionalProperties: false }
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function normalizeWorkspaceAdminSummary(workspace) {
|
|
45
|
+
const source = normalizeObjectInput(workspace);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
id: Number(source.id),
|
|
49
|
+
slug: normalizeText(source.slug),
|
|
50
|
+
name: normalizeText(source.name),
|
|
51
|
+
ownerUserId: Number(source.ownerUserId),
|
|
52
|
+
avatarUrl: normalizeText(source.avatarUrl),
|
|
53
|
+
color: normalizeText(source.color)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeMemberSummary(member, workspace) {
|
|
58
|
+
const source = normalizeObjectInput(member);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
userId: Number(source.userId),
|
|
62
|
+
roleId: normalizeLowerText(source.roleId || "member") || "member",
|
|
63
|
+
status: normalizeLowerText(source.status || "active") || "active",
|
|
64
|
+
displayName: normalizeText(source.displayName),
|
|
65
|
+
email: normalizeLowerText(source.email),
|
|
66
|
+
isOwner:
|
|
67
|
+
Number(source.userId) === Number(workspace.ownerUserId) ||
|
|
68
|
+
normalizeLowerText(source.roleId) === OWNER_ROLE_ID
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeInviteSummary(invite) {
|
|
73
|
+
const source = normalizeObjectInput(invite);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
id: Number(source.id),
|
|
77
|
+
email: normalizeLowerText(source.email),
|
|
78
|
+
roleId: normalizeLowerText(source.roleId || "member") || "member",
|
|
79
|
+
status: normalizeLowerText(source.status || "pending") || "pending",
|
|
80
|
+
expiresAt: source.expiresAt,
|
|
81
|
+
invitedByUserId: source.invitedByUserId == null ? null : Number(source.invitedByUserId)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeWorkspaceOutputEnvelope(
|
|
86
|
+
payload = {},
|
|
87
|
+
{ itemsKey, normalizeItem, includeInviteTokenPreview = false } = {}
|
|
88
|
+
) {
|
|
89
|
+
const source = normalizeObjectInput(payload);
|
|
90
|
+
const workspace = normalizeWorkspaceAdminSummary(source.workspace);
|
|
91
|
+
const items = Array.isArray(source[itemsKey]) ? source[itemsKey] : [];
|
|
92
|
+
const roleCatalog = normalizeObjectInput(source.roleCatalog);
|
|
93
|
+
const hasRoleCatalog =
|
|
94
|
+
Array.isArray(roleCatalog.roles) &&
|
|
95
|
+
roleCatalog.roles.length > 0 &&
|
|
96
|
+
Array.isArray(roleCatalog.assignableRoleIds);
|
|
97
|
+
const normalized = {
|
|
98
|
+
workspace,
|
|
99
|
+
[itemsKey]: items.map((item) => normalizeItem(item, workspace)),
|
|
100
|
+
roleCatalog: hasRoleCatalog ? roleCatalog : createWorkspaceRoleCatalog()
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (includeInviteTokenPreview && Object.hasOwn(source, "inviteTokenPreview")) {
|
|
104
|
+
normalized.inviteTokenPreview = normalizeText(source.inviteTokenPreview);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return normalized;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeWorkspaceMembersOutput(payload = {}) {
|
|
111
|
+
return normalizeWorkspaceOutputEnvelope(payload, {
|
|
112
|
+
itemsKey: "members",
|
|
113
|
+
normalizeItem: normalizeMemberSummary
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeWorkspaceInvitesOutput(payload = {}) {
|
|
118
|
+
return normalizeWorkspaceOutputEnvelope(payload, {
|
|
119
|
+
itemsKey: "invites",
|
|
120
|
+
normalizeItem: normalizeInviteSummary,
|
|
121
|
+
includeInviteTokenPreview: true
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const workspaceRoleCatalogOutputValidator = Object.freeze({
|
|
126
|
+
schema: Type.Object(
|
|
127
|
+
{
|
|
128
|
+
collaborationEnabled: Type.Boolean(),
|
|
129
|
+
defaultInviteRole: Type.String(),
|
|
130
|
+
roles: Type.Array(Type.Object({}, { additionalProperties: true })),
|
|
131
|
+
assignableRoleIds: Type.Array(Type.String({ minLength: 1 }))
|
|
132
|
+
},
|
|
133
|
+
{ additionalProperties: true }
|
|
134
|
+
)
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const workspaceMembersOutputValidator = Object.freeze({
|
|
138
|
+
schema: Type.Object(
|
|
139
|
+
{
|
|
140
|
+
workspace: workspaceSummaryOutputSchema,
|
|
141
|
+
members: Type.Array(memberSummaryOutputSchema),
|
|
142
|
+
roleCatalog: workspaceRoleCatalogOutputValidator.schema
|
|
143
|
+
},
|
|
144
|
+
{ additionalProperties: false }
|
|
145
|
+
),
|
|
146
|
+
normalize: normalizeWorkspaceMembersOutput
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const workspaceInvitesOutputValidator = Object.freeze({
|
|
150
|
+
schema: Type.Object(
|
|
151
|
+
{
|
|
152
|
+
workspace: workspaceSummaryOutputSchema,
|
|
153
|
+
invites: Type.Array(inviteSummaryOutputSchema),
|
|
154
|
+
roleCatalog: workspaceRoleCatalogOutputValidator.schema,
|
|
155
|
+
inviteTokenPreview: Type.Optional(Type.String({ minLength: 1 }))
|
|
156
|
+
},
|
|
157
|
+
{ additionalProperties: false }
|
|
158
|
+
),
|
|
159
|
+
normalize: normalizeWorkspaceInvitesOutput
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const updateMemberRoleBodyValidator = Object.freeze({
|
|
163
|
+
schema: Type.Object(
|
|
164
|
+
{
|
|
165
|
+
roleId: Type.String({ minLength: 1 })
|
|
166
|
+
},
|
|
167
|
+
{ additionalProperties: false }
|
|
168
|
+
),
|
|
169
|
+
normalize(payload = {}) {
|
|
170
|
+
const source = normalizeObjectInput(payload);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
roleId: normalizeLowerText(source.roleId)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const updateMemberRoleInputValidator = Object.freeze({
|
|
179
|
+
schema: Type.Object(
|
|
180
|
+
{
|
|
181
|
+
memberUserId: Type.Integer({ minimum: 1 }),
|
|
182
|
+
roleId: Type.String({ minLength: 1 })
|
|
183
|
+
},
|
|
184
|
+
{ additionalProperties: false }
|
|
185
|
+
),
|
|
186
|
+
normalize(payload = {}) {
|
|
187
|
+
const source = normalizeObjectInput(payload);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
memberUserId: normalizePositiveInteger(source.memberUserId),
|
|
191
|
+
roleId: normalizeLowerText(source.roleId)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const removeMemberInputValidator = Object.freeze({
|
|
197
|
+
schema: Type.Object(
|
|
198
|
+
{
|
|
199
|
+
memberUserId: Type.Integer({ minimum: 1 })
|
|
200
|
+
},
|
|
201
|
+
{ additionalProperties: false }
|
|
202
|
+
),
|
|
203
|
+
normalize(payload = {}) {
|
|
204
|
+
const source = normalizeObjectInput(payload);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
memberUserId: normalizePositiveInteger(source.memberUserId)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const createInviteBodyValidator = Object.freeze({
|
|
213
|
+
schema: Type.Object(
|
|
214
|
+
{
|
|
215
|
+
email: Type.String({ minLength: 3, format: "email" }),
|
|
216
|
+
roleId: Type.String({ minLength: 1 })
|
|
217
|
+
},
|
|
218
|
+
{ additionalProperties: false }
|
|
219
|
+
),
|
|
220
|
+
normalize(payload = {}) {
|
|
221
|
+
const source = normalizeObjectInput(payload);
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
email: normalizeLowerText(source.email),
|
|
225
|
+
roleId: normalizeLowerText(source.roleId || "member") || "member"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const revokeInviteInputValidator = Object.freeze({
|
|
231
|
+
schema: Type.Object(
|
|
232
|
+
{
|
|
233
|
+
inviteId: Type.Integer({ minimum: 1 })
|
|
234
|
+
},
|
|
235
|
+
{ additionalProperties: false }
|
|
236
|
+
),
|
|
237
|
+
normalize(payload = {}) {
|
|
238
|
+
const source = normalizeObjectInput(payload);
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
inviteId: normalizePositiveInteger(source.inviteId)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const redeemInviteBodyValidator = Object.freeze({
|
|
247
|
+
schema: Type.Object(
|
|
248
|
+
{
|
|
249
|
+
token: Type.String({
|
|
250
|
+
minLength: 1,
|
|
251
|
+
messages: {
|
|
252
|
+
required: "Invite token is required.",
|
|
253
|
+
minLength: "Invite token is required.",
|
|
254
|
+
default: "Invite token is invalid."
|
|
255
|
+
}
|
|
256
|
+
}),
|
|
257
|
+
decision: Type.Union([Type.Literal("accept"), Type.Literal("refuse")], {
|
|
258
|
+
messages: {
|
|
259
|
+
required: "Decision is required.",
|
|
260
|
+
default: "Decision must be accept or refuse."
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
additionalProperties: false,
|
|
266
|
+
messages: {
|
|
267
|
+
additionalProperties: "Unexpected field."
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
normalize(payload = {}) {
|
|
272
|
+
const source = normalizeObjectInput(payload);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
token: normalizeText(source.token),
|
|
276
|
+
decision: normalizeLowerText(source.decision)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const redeemInviteOutputValidator = Object.freeze({
|
|
282
|
+
schema: Type.Object(
|
|
283
|
+
{
|
|
284
|
+
decision: Type.Union([Type.Literal("accepted"), Type.Literal("refused")])
|
|
285
|
+
},
|
|
286
|
+
{ additionalProperties: false }
|
|
287
|
+
),
|
|
288
|
+
normalize(payload = {}) {
|
|
289
|
+
const source = normalizeObjectInput(payload);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
decision: normalizeLowerText(source.decision)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const WORKSPACE_MEMBERS_MESSAGES = createOperationMessages();
|
|
298
|
+
|
|
299
|
+
const workspaceMembersResource = Object.freeze({
|
|
300
|
+
resource: "workspaceMembers",
|
|
301
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
302
|
+
operations: Object.freeze({
|
|
303
|
+
rolesList: Object.freeze({
|
|
304
|
+
method: "GET",
|
|
305
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
306
|
+
outputValidator: workspaceRoleCatalogOutputValidator
|
|
307
|
+
}),
|
|
308
|
+
membersList: Object.freeze({
|
|
309
|
+
method: "GET",
|
|
310
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
311
|
+
outputValidator: workspaceMembersOutputValidator
|
|
312
|
+
}),
|
|
313
|
+
updateMemberRole: Object.freeze({
|
|
314
|
+
method: "PATCH",
|
|
315
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
316
|
+
bodyValidator: updateMemberRoleBodyValidator,
|
|
317
|
+
inputValidator: updateMemberRoleInputValidator,
|
|
318
|
+
outputValidator: workspaceMembersOutputValidator
|
|
319
|
+
}),
|
|
320
|
+
removeMember: Object.freeze({
|
|
321
|
+
method: "DELETE",
|
|
322
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
323
|
+
inputValidator: removeMemberInputValidator,
|
|
324
|
+
outputValidator: workspaceMembersOutputValidator
|
|
325
|
+
}),
|
|
326
|
+
invitesList: Object.freeze({
|
|
327
|
+
method: "GET",
|
|
328
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
329
|
+
outputValidator: workspaceInvitesOutputValidator
|
|
330
|
+
}),
|
|
331
|
+
createInvite: Object.freeze({
|
|
332
|
+
method: "POST",
|
|
333
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
334
|
+
bodyValidator: createInviteBodyValidator,
|
|
335
|
+
outputValidator: workspaceInvitesOutputValidator
|
|
336
|
+
}),
|
|
337
|
+
revokeInvite: Object.freeze({
|
|
338
|
+
method: "DELETE",
|
|
339
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
340
|
+
inputValidator: revokeInviteInputValidator,
|
|
341
|
+
outputValidator: workspaceInvitesOutputValidator
|
|
342
|
+
}),
|
|
343
|
+
redeemInvite: Object.freeze({
|
|
344
|
+
method: "POST",
|
|
345
|
+
messages: WORKSPACE_MEMBERS_MESSAGES,
|
|
346
|
+
bodyValidator: redeemInviteBodyValidator,
|
|
347
|
+
outputValidator: redeemInviteOutputValidator
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
export { workspaceMembersResource };
|
|
@@ -0,0 +1,87 @@
|
|
|
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 } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
6
|
+
|
|
7
|
+
function normalizePendingInvite(invite) {
|
|
8
|
+
const id = Number(invite?.id);
|
|
9
|
+
const workspaceId = Number(invite?.workspaceId);
|
|
10
|
+
const tokenHash = normalizeText(invite?.tokenHash);
|
|
11
|
+
|
|
12
|
+
if (!Number.isInteger(id) || id < 1) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (!Number.isInteger(workspaceId) || workspaceId < 1) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if (!tokenHash) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
workspaceId,
|
|
25
|
+
workspaceSlug: normalizeText(invite?.workspaceSlug),
|
|
26
|
+
workspaceName: normalizeText(invite?.workspaceName || invite?.workspaceSlug),
|
|
27
|
+
workspaceAvatarUrl: normalizeText(invite?.workspaceAvatarUrl),
|
|
28
|
+
roleId: normalizeLowerText(invite?.roleId || "member") || "member",
|
|
29
|
+
status: normalizeLowerText(invite?.status || "pending") || "pending",
|
|
30
|
+
expiresAt: invite?.expiresAt || null,
|
|
31
|
+
token: encodeInviteTokenHash(tokenHash)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizePendingInviteList(invites) {
|
|
36
|
+
return (Array.isArray(invites) ? invites : []).map((invite) => normalizePendingInvite(invite)).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pendingInviteRecordValidator = Object.freeze({
|
|
40
|
+
schema: Type.Object(
|
|
41
|
+
{
|
|
42
|
+
id: Type.Integer({ minimum: 1 }),
|
|
43
|
+
workspaceId: Type.Integer({ minimum: 1 }),
|
|
44
|
+
workspaceSlug: Type.String({ minLength: 1 }),
|
|
45
|
+
workspaceName: Type.String({ minLength: 1 }),
|
|
46
|
+
workspaceAvatarUrl: Type.String(),
|
|
47
|
+
roleId: Type.String({ minLength: 1 }),
|
|
48
|
+
status: Type.String({ minLength: 1 }),
|
|
49
|
+
expiresAt: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
|
|
50
|
+
token: Type.String({ minLength: 1 })
|
|
51
|
+
},
|
|
52
|
+
{ additionalProperties: false }
|
|
53
|
+
),
|
|
54
|
+
normalize: normalizePendingInvite
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const pendingInvitationsListOutputValidator = Object.freeze({
|
|
58
|
+
schema: Type.Object(
|
|
59
|
+
{
|
|
60
|
+
pendingInvites: Type.Array(pendingInviteRecordValidator.schema)
|
|
61
|
+
},
|
|
62
|
+
{ additionalProperties: false }
|
|
63
|
+
),
|
|
64
|
+
normalize(payload = {}) {
|
|
65
|
+
const source = normalizeObjectInput(payload);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
pendingInvites: normalizePendingInviteList(source.pendingInvites)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const WORKSPACE_PENDING_INVITATIONS_MESSAGES = createOperationMessages();
|
|
74
|
+
|
|
75
|
+
const workspacePendingInvitationsResource = Object.freeze({
|
|
76
|
+
resource: "workspacePendingInvitations",
|
|
77
|
+
messages: WORKSPACE_PENDING_INVITATIONS_MESSAGES,
|
|
78
|
+
operations: Object.freeze({
|
|
79
|
+
list: Object.freeze({
|
|
80
|
+
method: "GET",
|
|
81
|
+
messages: WORKSPACE_PENDING_INVITATIONS_MESSAGES,
|
|
82
|
+
outputValidator: pendingInvitationsListOutputValidator
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export { workspacePendingInvitationsResource };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
+
import {
|
|
4
|
+
normalizeObjectInput,
|
|
5
|
+
createCursorListValidator
|
|
6
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
7
|
+
|
|
8
|
+
function normalizeWorkspaceInput(payload = {}) {
|
|
9
|
+
const source = normalizeObjectInput(payload);
|
|
10
|
+
const normalized = {};
|
|
11
|
+
|
|
12
|
+
if (Object.hasOwn(source, "slug")) {
|
|
13
|
+
normalized.slug = normalizeLowerText(source.slug);
|
|
14
|
+
}
|
|
15
|
+
if (Object.hasOwn(source, "name")) {
|
|
16
|
+
normalized.name = normalizeText(source.name);
|
|
17
|
+
}
|
|
18
|
+
if (Object.hasOwn(source, "ownerUserId")) {
|
|
19
|
+
normalized.ownerUserId = Number(source.ownerUserId);
|
|
20
|
+
}
|
|
21
|
+
if (Object.hasOwn(source, "avatarUrl")) {
|
|
22
|
+
normalized.avatarUrl = normalizeText(source.avatarUrl);
|
|
23
|
+
}
|
|
24
|
+
if (Object.hasOwn(source, "color")) {
|
|
25
|
+
const color = normalizeText(source.color);
|
|
26
|
+
normalized.color = /^#[0-9A-Fa-f]{6}$/.test(color) ? color.toUpperCase() : null;
|
|
27
|
+
}
|
|
28
|
+
if (Object.hasOwn(source, "isPersonal")) {
|
|
29
|
+
normalized.isPersonal = source.isPersonal === true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeWorkspaceOutput(payload = {}) {
|
|
36
|
+
const source = normalizeObjectInput(payload);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
id: Number(source.id),
|
|
40
|
+
slug: normalizeLowerText(source.slug),
|
|
41
|
+
name: normalizeText(source.name),
|
|
42
|
+
ownerUserId: Number(source.ownerUserId),
|
|
43
|
+
avatarUrl: normalizeText(source.avatarUrl),
|
|
44
|
+
color: normalizeText(source.color).toUpperCase()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeWorkspaceListItemOutput(payload = {}) {
|
|
49
|
+
const source = normalizeObjectInput(payload);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
id: Number(source.id),
|
|
53
|
+
slug: normalizeLowerText(source.slug),
|
|
54
|
+
name: normalizeText(source.name),
|
|
55
|
+
color: normalizeText(source.color).toUpperCase(),
|
|
56
|
+
avatarUrl: normalizeText(source.avatarUrl),
|
|
57
|
+
roleId: normalizeLowerText(source.roleId || "member") || "member",
|
|
58
|
+
isAccessible: source.isAccessible !== false
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const responseRecordSchema = Type.Object(
|
|
63
|
+
{
|
|
64
|
+
id: Type.Integer({ minimum: 1 }),
|
|
65
|
+
slug: Type.String({ minLength: 1 }),
|
|
66
|
+
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
67
|
+
ownerUserId: Type.Integer({ minimum: 1 }),
|
|
68
|
+
avatarUrl: Type.String(),
|
|
69
|
+
color: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" })
|
|
70
|
+
},
|
|
71
|
+
{ additionalProperties: false }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const listItemSchema = Type.Object(
|
|
75
|
+
{
|
|
76
|
+
id: Type.Integer({ minimum: 1 }),
|
|
77
|
+
slug: Type.String({ minLength: 1 }),
|
|
78
|
+
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
79
|
+
color: Type.String({ minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" }),
|
|
80
|
+
avatarUrl: Type.String(),
|
|
81
|
+
roleId: Type.String({ minLength: 1 }),
|
|
82
|
+
isAccessible: Type.Boolean()
|
|
83
|
+
},
|
|
84
|
+
{ additionalProperties: false }
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const createRequestBodySchema = Type.Object(
|
|
88
|
+
{
|
|
89
|
+
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
90
|
+
slug: Type.Optional(Type.String({ minLength: 1, maxLength: 120 }))
|
|
91
|
+
},
|
|
92
|
+
{ additionalProperties: false }
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const responseRecordValidator = Object.freeze({
|
|
96
|
+
schema: responseRecordSchema,
|
|
97
|
+
normalize: normalizeWorkspaceOutput
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const workspaceSummaryOutputValidator = Object.freeze({
|
|
101
|
+
schema: listItemSchema,
|
|
102
|
+
normalize: normalizeWorkspaceListItemOutput
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const resource = {
|
|
106
|
+
resource: "workspace",
|
|
107
|
+
messages: {
|
|
108
|
+
validation: "Fix invalid workspace values and try again.",
|
|
109
|
+
saveSuccess: "Workspace updated.",
|
|
110
|
+
saveError: "Unable to update workspace.",
|
|
111
|
+
apiValidation: "Validation failed."
|
|
112
|
+
},
|
|
113
|
+
operations: {
|
|
114
|
+
view: {
|
|
115
|
+
method: "GET",
|
|
116
|
+
outputValidator: responseRecordValidator
|
|
117
|
+
},
|
|
118
|
+
list: {
|
|
119
|
+
method: "GET",
|
|
120
|
+
outputValidator: createCursorListValidator(workspaceSummaryOutputValidator)
|
|
121
|
+
},
|
|
122
|
+
create: {
|
|
123
|
+
method: "POST",
|
|
124
|
+
bodyValidator: {
|
|
125
|
+
schema: createRequestBodySchema,
|
|
126
|
+
normalize: normalizeWorkspaceInput
|
|
127
|
+
},
|
|
128
|
+
outputValidator: responseRecordValidator
|
|
129
|
+
},
|
|
130
|
+
replace: {
|
|
131
|
+
method: "PUT",
|
|
132
|
+
bodyValidator: {
|
|
133
|
+
schema: createRequestBodySchema,
|
|
134
|
+
normalize: normalizeWorkspaceInput
|
|
135
|
+
},
|
|
136
|
+
outputValidator: responseRecordValidator
|
|
137
|
+
},
|
|
138
|
+
patch: {
|
|
139
|
+
method: "PATCH",
|
|
140
|
+
bodyValidator: {
|
|
141
|
+
schema: Type.Partial(createRequestBodySchema, { additionalProperties: false }),
|
|
142
|
+
normalize: normalizeWorkspaceInput
|
|
143
|
+
},
|
|
144
|
+
outputValidator: responseRecordValidator
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export { resource as workspaceResource };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
2
|
+
import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
|
|
3
|
+
|
|
4
|
+
const WORKSPACE_SETTINGS_FIELDS_REGISTRY_KEY = Symbol.for("jskit.users-core.workspaceSettingsFields");
|
|
5
|
+
const workspaceSettingsFields = resolveGlobalArrayRegistry(WORKSPACE_SETTINGS_FIELDS_REGISTRY_KEY);
|
|
6
|
+
|
|
7
|
+
function defineField(field = {}) {
|
|
8
|
+
const key = normalizeText(field.key);
|
|
9
|
+
if (!key) {
|
|
10
|
+
throw new TypeError("workspaceSettingsFields.defineField requires field.key.");
|
|
11
|
+
}
|
|
12
|
+
if (workspaceSettingsFields.some((entry) => entry.key === key)) {
|
|
13
|
+
throw new Error(`workspaceSettingsFields.defineField duplicate key: ${key}`);
|
|
14
|
+
}
|
|
15
|
+
if (!field.inputSchema || typeof field.inputSchema !== "object") {
|
|
16
|
+
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires inputSchema.`);
|
|
17
|
+
}
|
|
18
|
+
if (!field.outputSchema || typeof field.outputSchema !== "object") {
|
|
19
|
+
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires outputSchema.`);
|
|
20
|
+
}
|
|
21
|
+
const dbColumn = normalizeText(field.dbColumn);
|
|
22
|
+
if (!dbColumn) {
|
|
23
|
+
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires dbColumn.`);
|
|
24
|
+
}
|
|
25
|
+
if (typeof field.normalizeInput !== "function") {
|
|
26
|
+
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires normalizeInput.`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof field.normalizeOutput !== "function") {
|
|
29
|
+
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires normalizeOutput.`);
|
|
30
|
+
}
|
|
31
|
+
if (typeof field.resolveDefault !== "function") {
|
|
32
|
+
throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires resolveDefault.`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
workspaceSettingsFields.push({
|
|
36
|
+
key,
|
|
37
|
+
dbColumn,
|
|
38
|
+
required: field.required !== false,
|
|
39
|
+
inputSchema: field.inputSchema,
|
|
40
|
+
outputSchema: field.outputSchema,
|
|
41
|
+
normalizeInput: field.normalizeInput,
|
|
42
|
+
normalizeOutput: field.normalizeOutput,
|
|
43
|
+
resolveDefault: field.resolveDefault
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resetWorkspaceSettingsFields() {
|
|
48
|
+
workspaceSettingsFields.splice(0, workspaceSettingsFields.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveWorkspaceSettingsFieldKeys() {
|
|
52
|
+
return workspaceSettingsFields.map((field) => field.key);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
defineField,
|
|
57
|
+
resetWorkspaceSettingsFields,
|
|
58
|
+
resolveWorkspaceSettingsFieldKeys,
|
|
59
|
+
workspaceSettingsFields
|
|
60
|
+
};
|