@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.
Files changed (148) hide show
  1. package/package.descriptor.mjs +464 -0
  2. package/package.json +35 -0
  3. package/src/server/UsersCoreServiceProvider.js +74 -0
  4. package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
  5. package/src/server/accountNotifications/accountNotificationsService.js +41 -0
  6. package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
  7. package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
  8. package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
  9. package/src/server/accountPreferences/accountPreferencesService.js +41 -0
  10. package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
  11. package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
  12. package/src/server/accountProfile/accountProfileActions.js +137 -0
  13. package/src/server/accountProfile/accountProfileService.js +124 -0
  14. package/src/server/accountProfile/avatarService.js +141 -0
  15. package/src/server/accountProfile/avatarStorageService.js +132 -0
  16. package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
  17. package/src/server/accountProfile/registerAccountProfile.js +62 -0
  18. package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
  19. package/src/server/accountSecurity/accountSecurityActions.js +144 -0
  20. package/src/server/accountSecurity/accountSecurityService.js +103 -0
  21. package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
  22. package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
  23. package/src/server/common/README.md +21 -0
  24. package/src/server/common/contributors/README.md +11 -0
  25. package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
  26. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
  27. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
  28. package/src/server/common/diTokens.js +21 -0
  29. package/src/server/common/formatters/README.md +11 -0
  30. package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
  31. package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
  32. package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
  33. package/src/server/common/formatters/workspaceFormatter.js +46 -0
  34. package/src/server/common/registerCommonRepositories.js +45 -0
  35. package/src/server/common/registerSharedApi.js +9 -0
  36. package/src/server/common/repositories/README.md +24 -0
  37. package/src/server/common/repositories/repositoryUtils.js +50 -0
  38. package/src/server/common/repositories/userProfilesRepository.js +251 -0
  39. package/src/server/common/repositories/userSettingsRepository.js +179 -0
  40. package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
  41. package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
  42. package/src/server/common/repositories/workspacesRepository.js +183 -0
  43. package/src/server/common/routes/README.md +11 -0
  44. package/src/server/common/services/README.md +12 -0
  45. package/src/server/common/services/accountContextService.js +31 -0
  46. package/src/server/common/services/authProfileSyncService.js +128 -0
  47. package/src/server/common/services/workspaceContextService.js +270 -0
  48. package/src/server/common/support/deepFreeze.js +17 -0
  49. package/src/server/common/support/realtimeServiceEvents.js +94 -0
  50. package/src/server/common/support/resolveActionUser.js +11 -0
  51. package/src/server/common/support/workspaceRoutePaths.js +17 -0
  52. package/src/server/common/validators/README.md +11 -0
  53. package/src/server/common/validators/authenticatedUserValidator.js +42 -0
  54. package/src/server/common/validators/routeParamsValidator.js +62 -0
  55. package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
  56. package/src/server/consoleSettings/consoleService.js +36 -0
  57. package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
  58. package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
  59. package/src/server/consoleSettings/consoleSettingsService.js +40 -0
  60. package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
  61. package/src/server/registerWorkspaceBootstrap.js +36 -0
  62. package/src/server/registerWorkspaceCore.js +95 -0
  63. package/src/server/support/resolveWorkspace.js +16 -0
  64. package/src/server/support/workspaceActionSurfaces.js +135 -0
  65. package/src/server/support/workspaceInvitationsPolicy.js +45 -0
  66. package/src/server/support/workspaceRouteInput.js +22 -0
  67. package/src/server/workspaceBootstrapContributor.js +401 -0
  68. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
  69. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
  70. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
  71. package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
  72. package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
  73. package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
  74. package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
  75. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
  76. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
  77. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
  78. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
  79. package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
  80. package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
  81. package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
  82. package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
  83. package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
  84. package/src/shared/events/usersEvents.js +19 -0
  85. package/src/shared/index.js +91 -0
  86. package/src/shared/operationMessages.js +16 -0
  87. package/src/shared/resources/consoleSettingsFields.js +55 -0
  88. package/src/shared/resources/consoleSettingsResource.js +139 -0
  89. package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
  90. package/src/shared/resources/userProfileResource.js +148 -0
  91. package/src/shared/resources/userSettingsFields.js +71 -0
  92. package/src/shared/resources/userSettingsResource.js +416 -0
  93. package/src/shared/resources/workspaceMembersResource.js +352 -0
  94. package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
  95. package/src/shared/resources/workspaceResource.js +149 -0
  96. package/src/shared/resources/workspaceSettingsFields.js +60 -0
  97. package/src/shared/resources/workspaceSettingsResource.js +178 -0
  98. package/src/shared/roles.js +136 -0
  99. package/src/shared/settings.js +31 -0
  100. package/src/shared/support/usersApiPaths.js +34 -0
  101. package/src/shared/support/usersVisibility.js +45 -0
  102. package/src/shared/support/workspacePathModel.js +145 -0
  103. package/src/shared/tenancyMode.js +35 -0
  104. package/src/shared/tenancyProfile.js +73 -0
  105. package/templates/config/workspaceRoles.js +30 -0
  106. package/templates/migrations/users_core_console_owner.cjs +39 -0
  107. package/templates/migrations/users_core_initial.cjs +118 -0
  108. package/templates/migrations/users_core_profile_username.cjs +98 -0
  109. package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
  110. package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
  111. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
  112. package/test/authProfileSyncService.test.js +119 -0
  113. package/test/avatarService.test.js +114 -0
  114. package/test/avatarStorageService.test.js +61 -0
  115. package/test/consoleService.test.js +57 -0
  116. package/test/consoleSettingsService.test.js +86 -0
  117. package/test/exportsContract.test.js +38 -0
  118. package/test/registerAvatarMultipartSupport.test.js +64 -0
  119. package/test/registerServiceRealtimeEvents.test.js +160 -0
  120. package/test/registerWorkspaceDirectory.test.js +26 -0
  121. package/test/registerWorkspaceSettings.test.js +44 -0
  122. package/test/resourcesCanonical.test.js +90 -0
  123. package/test/roles.test.js +74 -0
  124. package/test/settingsFieldRegistriesSingleton.test.js +24 -0
  125. package/test/tenancyProfile.test.js +67 -0
  126. package/test/userSettingsResource.test.js +31 -0
  127. package/test/usersApiPaths.test.js +31 -0
  128. package/test/usersRouteRequestInputValidator.test.js +556 -0
  129. package/test/usersRouteResources.test.js +113 -0
  130. package/test/usersRouteValidators.test.js +49 -0
  131. package/test/usersVisibility.test.js +22 -0
  132. package/test/workspaceActionContextContributor.test.js +251 -0
  133. package/test/workspaceActionSurfaces.test.js +105 -0
  134. package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
  135. package/test/workspaceBootstrapContributor.test.js +466 -0
  136. package/test/workspaceInvitationsPolicy.test.js +71 -0
  137. package/test/workspaceInvitesRepository.test.js +111 -0
  138. package/test/workspaceMembersService.test.js +400 -0
  139. package/test/workspacePathModel.test.js +93 -0
  140. package/test/workspacePendingInvitationsResource.test.js +38 -0
  141. package/test/workspacePendingInvitationsService.test.js +151 -0
  142. package/test/workspaceRouteVisibilityResolver.test.js +83 -0
  143. package/test/workspaceService.test.js +480 -0
  144. package/test/workspaceSettingsActions.test.js +42 -0
  145. package/test/workspaceSettingsRepository.test.js +156 -0
  146. package/test/workspaceSettingsResource.test.js +156 -0
  147. package/test/workspaceSettingsService.test.js +120 -0
  148. 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
+ };