@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,148 @@
1
+ import { Type } from "typebox";
2
+ import {
3
+ createCursorListValidator,
4
+ normalizeObjectInput
5
+ } from "@jskit-ai/kernel/shared/validators";
6
+ import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
7
+ import { createOperationMessages } from "../operationMessages.js";
8
+
9
+ function normalizeProfileInput(payload = {}) {
10
+ const source = normalizeObjectInput(payload);
11
+ const normalized = {};
12
+
13
+ if (Object.hasOwn(source, "displayName")) {
14
+ normalized.displayName = normalizeText(source.displayName);
15
+ }
16
+
17
+ return normalized;
18
+ }
19
+
20
+ const userProfileOutputSchema = Type.Object(
21
+ {
22
+ displayName: Type.String(),
23
+ email: Type.String(),
24
+ emailManagedBy: Type.Optional(Type.String()),
25
+ emailChangeFlow: Type.Optional(Type.String()),
26
+ avatar: Type.Optional(Type.Object({}, { additionalProperties: true }))
27
+ },
28
+ { additionalProperties: true }
29
+ );
30
+
31
+ const userProfileOutputValidator = Object.freeze({
32
+ schema: userProfileOutputSchema,
33
+ normalize: normalizeObjectInput
34
+ });
35
+
36
+ const userProfileCreateBodySchema = Type.Object(
37
+ {
38
+ displayName: Type.String({ minLength: 1, maxLength: 120 })
39
+ },
40
+ { additionalProperties: false }
41
+ );
42
+
43
+ const userProfilePatchBodySchema = Type.Partial(userProfileCreateBodySchema, {
44
+ additionalProperties: false,
45
+ minProperties: 1
46
+ });
47
+
48
+ const avatarUploadBodyValidator = Object.freeze({
49
+ schema: Type.Object(
50
+ {
51
+ mimeType: Type.Optional(
52
+ Type.String({
53
+ minLength: 1,
54
+ messages: {
55
+ default: "Avatar mimeType is invalid."
56
+ }
57
+ })
58
+ ),
59
+ fileName: Type.Optional(
60
+ Type.String({
61
+ minLength: 1,
62
+ messages: {
63
+ default: "Avatar fileName is invalid."
64
+ }
65
+ })
66
+ ),
67
+ uploadDimension: Type.Optional(
68
+ Type.String({
69
+ minLength: 1,
70
+ messages: {
71
+ default: "Avatar uploadDimension is invalid."
72
+ }
73
+ })
74
+ )
75
+ },
76
+ { additionalProperties: true }
77
+ ),
78
+ normalize: normalizeObjectInput
79
+ });
80
+
81
+ const avatarDeleteBodyValidator = Object.freeze({
82
+ schema: Type.Object({}, { additionalProperties: false }),
83
+ normalize: normalizeObjectInput
84
+ });
85
+
86
+ const avatarOperationOutputValidator = Object.freeze({
87
+ schema: Type.Object({}, { additionalProperties: true }),
88
+ normalize: normalizeObjectInput
89
+ });
90
+
91
+ const USER_PROFILE_OPERATION_MESSAGES = createOperationMessages();
92
+
93
+ const userProfileResource = Object.freeze({
94
+ resource: "userProfile",
95
+ operations: Object.freeze({
96
+ view: Object.freeze({
97
+ method: "GET",
98
+ messages: USER_PROFILE_OPERATION_MESSAGES,
99
+ outputValidator: userProfileOutputValidator
100
+ }),
101
+ list: Object.freeze({
102
+ method: "GET",
103
+ messages: USER_PROFILE_OPERATION_MESSAGES,
104
+ outputValidator: createCursorListValidator(userProfileOutputValidator)
105
+ }),
106
+ create: Object.freeze({
107
+ method: "POST",
108
+ messages: USER_PROFILE_OPERATION_MESSAGES,
109
+ bodyValidator: Object.freeze({
110
+ schema: userProfileCreateBodySchema,
111
+ normalize: normalizeProfileInput
112
+ }),
113
+ outputValidator: userProfileOutputValidator
114
+ }),
115
+ replace: Object.freeze({
116
+ method: "PUT",
117
+ messages: USER_PROFILE_OPERATION_MESSAGES,
118
+ bodyValidator: Object.freeze({
119
+ schema: userProfileCreateBodySchema,
120
+ normalize: normalizeProfileInput
121
+ }),
122
+ outputValidator: userProfileOutputValidator
123
+ }),
124
+ patch: Object.freeze({
125
+ method: "PATCH",
126
+ messages: USER_PROFILE_OPERATION_MESSAGES,
127
+ bodyValidator: Object.freeze({
128
+ schema: userProfilePatchBodySchema,
129
+ normalize: normalizeProfileInput
130
+ }),
131
+ outputValidator: userProfileOutputValidator
132
+ }),
133
+ avatarUpload: Object.freeze({
134
+ method: "POST",
135
+ messages: USER_PROFILE_OPERATION_MESSAGES,
136
+ bodyValidator: avatarUploadBodyValidator,
137
+ outputValidator: avatarOperationOutputValidator
138
+ }),
139
+ avatarDelete: Object.freeze({
140
+ method: "DELETE",
141
+ messages: USER_PROFILE_OPERATION_MESSAGES,
142
+ bodyValidator: avatarDeleteBodyValidator,
143
+ outputValidator: avatarOperationOutputValidator
144
+ })
145
+ })
146
+ });
147
+
148
+ export { userProfileResource };
@@ -0,0 +1,71 @@
1
+ import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
2
+ import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
3
+
4
+ const USER_SETTINGS_SECTIONS = Object.freeze({
5
+ PREFERENCES: "preferences",
6
+ NOTIFICATIONS: "notifications"
7
+ });
8
+
9
+ const USER_SETTINGS_SECTION_VALUES = Object.freeze(Object.values(USER_SETTINGS_SECTIONS));
10
+
11
+ const USER_SETTINGS_FIELDS_REGISTRY_KEY = Symbol.for("jskit.users-core.userSettingsFields");
12
+ const userSettingsFields = resolveGlobalArrayRegistry(USER_SETTINGS_FIELDS_REGISTRY_KEY);
13
+
14
+ function defineField(field = {}) {
15
+ const key = normalizeText(field.key);
16
+ if (!key) {
17
+ throw new TypeError("userSettingsFields.defineField requires field.key.");
18
+ }
19
+ if (userSettingsFields.some((entry) => entry.key === key)) {
20
+ throw new Error(`userSettingsFields.defineField duplicate key: ${key}`);
21
+ }
22
+ if (!field.inputSchema || typeof field.inputSchema !== "object") {
23
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires inputSchema.`);
24
+ }
25
+ if (!field.outputSchema || typeof field.outputSchema !== "object") {
26
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires outputSchema.`);
27
+ }
28
+ const dbColumn = normalizeText(field.dbColumn);
29
+ if (!dbColumn) {
30
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires dbColumn.`);
31
+ }
32
+ const section = normalizeLowerText(field.section);
33
+ if (!USER_SETTINGS_SECTION_VALUES.includes(section)) {
34
+ throw new TypeError(
35
+ `userSettingsFields.defineField("${key}") requires section to be one of: ${USER_SETTINGS_SECTION_VALUES.join(", ")}.`
36
+ );
37
+ }
38
+ if (typeof field.normalizeInput !== "function") {
39
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires normalizeInput.`);
40
+ }
41
+ if (typeof field.normalizeOutput !== "function") {
42
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires normalizeOutput.`);
43
+ }
44
+ if (typeof field.resolveDefault !== "function") {
45
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires resolveDefault.`);
46
+ }
47
+
48
+ userSettingsFields.push({
49
+ key,
50
+ section,
51
+ dbColumn,
52
+ required: field.required !== false,
53
+ includeInBootstrap: field.includeInBootstrap !== false,
54
+ inputSchema: field.inputSchema,
55
+ outputSchema: field.outputSchema,
56
+ normalizeInput: field.normalizeInput,
57
+ normalizeOutput: field.normalizeOutput,
58
+ resolveDefault: field.resolveDefault
59
+ });
60
+ }
61
+
62
+ function resetUserSettingsFields() {
63
+ userSettingsFields.splice(0, userSettingsFields.length);
64
+ }
65
+
66
+ export {
67
+ USER_SETTINGS_SECTIONS,
68
+ defineField,
69
+ resetUserSettingsFields,
70
+ userSettingsFields
71
+ };
@@ -0,0 +1,416 @@
1
+ import { Type } from "typebox";
2
+ import {
3
+ createCursorListValidator,
4
+ normalizeObjectInput
5
+ } from "@jskit-ai/kernel/shared/validators";
6
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
7
+ import { createOperationMessages } from "../operationMessages.js";
8
+ import { userProfileResource } from "./userProfileResource.js";
9
+ import {
10
+ USER_SETTINGS_SECTIONS,
11
+ userSettingsFields
12
+ } from "./userSettingsFields.js";
13
+
14
+ function pickPatchBody(schema, keys = []) {
15
+ const properties = {};
16
+ for (const key of keys) {
17
+ if (!Object.hasOwn(schema.properties, key)) {
18
+ throw new Error(`pickPatchBody requires patch field "${key}".`);
19
+ }
20
+
21
+ properties[key] = schema.properties[key];
22
+ }
23
+
24
+ return Type.Object(properties, {
25
+ additionalProperties: false,
26
+ minProperties: 1
27
+ });
28
+ }
29
+
30
+ function listFieldsBySection(section) {
31
+ return userSettingsFields.filter((field) => field.section === section);
32
+ }
33
+
34
+ function buildCreateBodySchema() {
35
+ const properties = {};
36
+ for (const field of userSettingsFields) {
37
+ properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
38
+ }
39
+
40
+ return Type.Object(properties, { additionalProperties: false });
41
+ }
42
+
43
+ function buildSectionOutputSchema(section) {
44
+ const properties = {};
45
+ for (const field of listFieldsBySection(section)) {
46
+ properties[field.key] = field.outputSchema;
47
+ }
48
+
49
+ return Type.Object(properties, { additionalProperties: false });
50
+ }
51
+
52
+ function normalizeUserSettingsInput(payload = {}) {
53
+ const source = normalizeObjectInput(payload);
54
+ const normalized = {};
55
+
56
+ for (const field of userSettingsFields) {
57
+ if (!Object.hasOwn(source, field.key)) {
58
+ continue;
59
+ }
60
+ normalized[field.key] = field.normalizeInput(source[field.key], {
61
+ payload: source
62
+ });
63
+ }
64
+
65
+ return normalized;
66
+ }
67
+
68
+ function normalizeSectionOutput(section, sectionSource = {}, settings = {}) {
69
+ const normalized = {};
70
+ for (const field of listFieldsBySection(section)) {
71
+ const rawValue = Object.hasOwn(sectionSource, field.key)
72
+ ? sectionSource[field.key]
73
+ : field.resolveDefault({
74
+ settings
75
+ });
76
+ normalized[field.key] = field.normalizeOutput(rawValue, {
77
+ settings
78
+ });
79
+ }
80
+ return normalized;
81
+ }
82
+
83
+ function buildUserSettingsOutputSchema() {
84
+ return Type.Object(
85
+ {
86
+ profile: userProfileResource.operations.view.outputValidator.schema,
87
+ security: Type.Object({}, { additionalProperties: true }),
88
+ preferences: buildSectionOutputSchema(USER_SETTINGS_SECTIONS.PREFERENCES),
89
+ notifications: buildSectionOutputSchema(USER_SETTINGS_SECTIONS.NOTIFICATIONS)
90
+ },
91
+ { additionalProperties: true }
92
+ );
93
+ }
94
+
95
+ const userSettingsOutputValidator = Object.freeze({
96
+ get schema() {
97
+ return buildUserSettingsOutputSchema();
98
+ },
99
+ normalize(payload = {}) {
100
+ const source = normalizeObjectInput(payload);
101
+ const preferencesSource = normalizeObjectInput(source.preferences);
102
+ const notificationsSource = normalizeObjectInput(source.notifications);
103
+
104
+ return {
105
+ profile: normalizeObjectInput(source.profile),
106
+ security: normalizeObjectInput(source.security),
107
+ preferences: normalizeSectionOutput(
108
+ USER_SETTINGS_SECTIONS.PREFERENCES,
109
+ preferencesSource,
110
+ preferencesSource
111
+ ),
112
+ notifications: normalizeSectionOutput(
113
+ USER_SETTINGS_SECTIONS.NOTIFICATIONS,
114
+ notificationsSource,
115
+ notificationsSource
116
+ )
117
+ };
118
+ }
119
+ });
120
+
121
+ function buildUserSettingsCreateBodySchema() {
122
+ return buildCreateBodySchema();
123
+ }
124
+
125
+ function buildUserSettingsPatchBodySchema() {
126
+ return Type.Partial(buildUserSettingsCreateBodySchema(), {
127
+ additionalProperties: false,
128
+ minProperties: 1
129
+ });
130
+ }
131
+
132
+ function buildPreferencesUpdateBodySchema() {
133
+ return pickPatchBody(
134
+ buildUserSettingsPatchBodySchema(),
135
+ listFieldsBySection(USER_SETTINGS_SECTIONS.PREFERENCES).map((field) => field.key)
136
+ );
137
+ }
138
+
139
+ function buildNotificationsUpdateBodySchema() {
140
+ return pickPatchBody(
141
+ buildUserSettingsPatchBodySchema(),
142
+ listFieldsBySection(USER_SETTINGS_SECTIONS.NOTIFICATIONS).map((field) => field.key)
143
+ );
144
+ }
145
+
146
+ const preferencesUpdateBodyValidator = Object.freeze({
147
+ get schema() {
148
+ return buildPreferencesUpdateBodySchema();
149
+ },
150
+ normalize: normalizeUserSettingsInput
151
+ });
152
+
153
+ const notificationsUpdateBodyValidator = Object.freeze({
154
+ get schema() {
155
+ return buildNotificationsUpdateBodySchema();
156
+ },
157
+ normalize: normalizeUserSettingsInput
158
+ });
159
+
160
+ function normalizeOAuthProviderParams(payload = {}) {
161
+ const source = normalizeObjectInput(payload);
162
+ if (!Object.hasOwn(source, "provider")) {
163
+ return {};
164
+ }
165
+
166
+ return {
167
+ provider: normalizeText(source.provider)
168
+ };
169
+ }
170
+
171
+ function normalizeOAuthProviderQuery(payload = {}) {
172
+ const source = normalizeObjectInput(payload);
173
+ if (!Object.hasOwn(source, "returnTo")) {
174
+ return {};
175
+ }
176
+
177
+ const returnTo = normalizeText(source.returnTo);
178
+ if (!returnTo) {
179
+ return {};
180
+ }
181
+
182
+ return {
183
+ returnTo
184
+ };
185
+ }
186
+
187
+ const settingsActionOutputValidator = Object.freeze({
188
+ schema: Type.Object({}, { additionalProperties: true }),
189
+ normalize: normalizeObjectInput
190
+ });
191
+
192
+ const passwordChangeOutputValidator = Object.freeze({
193
+ schema: Type.Object(
194
+ {
195
+ ok: Type.Boolean(),
196
+ message: Type.String()
197
+ },
198
+ { additionalProperties: false }
199
+ ),
200
+ normalize: normalizeObjectInput
201
+ });
202
+
203
+ const passwordChangeBodyValidator = Object.freeze({
204
+ schema: Type.Object(
205
+ {
206
+ currentPassword: Type.Optional(
207
+ Type.String({
208
+ minLength: 1,
209
+ messages: {
210
+ default: "Current password is invalid."
211
+ }
212
+ })
213
+ ),
214
+ newPassword: Type.String({
215
+ minLength: 8,
216
+ messages: {
217
+ required: "New password is required.",
218
+ minLength: "New password must be at least 8 characters.",
219
+ default: "New password must be at least 8 characters."
220
+ }
221
+ }),
222
+ confirmPassword: Type.String({
223
+ minLength: 1,
224
+ messages: {
225
+ required: "Confirm password is required.",
226
+ minLength: "Confirm password is required.",
227
+ default: "Confirm password is required."
228
+ }
229
+ })
230
+ },
231
+ {
232
+ additionalProperties: false,
233
+ messages: {
234
+ additionalProperties: "Unexpected field."
235
+ }
236
+ }
237
+ ),
238
+ normalize: normalizeObjectInput
239
+ });
240
+
241
+ const passwordMethodToggleBodyValidator = Object.freeze({
242
+ schema: Type.Object(
243
+ {
244
+ enabled: Type.Boolean({
245
+ messages: {
246
+ required: "enabled is required.",
247
+ default: "enabled must be a boolean."
248
+ }
249
+ })
250
+ },
251
+ {
252
+ additionalProperties: false,
253
+ messages: {
254
+ additionalProperties: "Unexpected field."
255
+ }
256
+ }
257
+ ),
258
+ normalize: normalizeObjectInput
259
+ });
260
+
261
+ const oauthProviderParamsValidator = Object.freeze({
262
+ schema: Type.Object(
263
+ {
264
+ provider: Type.String({
265
+ minLength: 2,
266
+ maxLength: 64,
267
+ messages: {
268
+ required: "OAuth provider is required.",
269
+ default: "OAuth provider is invalid."
270
+ }
271
+ })
272
+ },
273
+ {
274
+ additionalProperties: false,
275
+ messages: {
276
+ additionalProperties: "Unexpected field."
277
+ }
278
+ }
279
+ ),
280
+ normalize: normalizeOAuthProviderParams
281
+ });
282
+
283
+ const oauthProviderQueryValidator = Object.freeze({
284
+ schema: Type.Object(
285
+ {
286
+ returnTo: Type.Optional(
287
+ Type.String({
288
+ minLength: 1,
289
+ messages: {
290
+ default: "Return path is invalid."
291
+ }
292
+ })
293
+ )
294
+ },
295
+ {
296
+ additionalProperties: false,
297
+ messages: {
298
+ additionalProperties: "Unexpected field."
299
+ }
300
+ }
301
+ ),
302
+ normalize: normalizeOAuthProviderQuery
303
+ });
304
+
305
+ const oauthLinkStartOutputValidator = Object.freeze({
306
+ schema: Type.Object(
307
+ {
308
+ provider: Type.String({ minLength: 2, maxLength: 64 }),
309
+ returnTo: Type.String({ minLength: 1 }),
310
+ url: Type.String({ minLength: 1 })
311
+ },
312
+ { additionalProperties: false }
313
+ ),
314
+ normalize: normalizeObjectInput
315
+ });
316
+
317
+ const emptyBodyValidator = Object.freeze({
318
+ schema: Type.Object({}, { additionalProperties: false }),
319
+ normalize: normalizeObjectInput
320
+ });
321
+
322
+ const USER_SETTINGS_OPERATION_MESSAGES = createOperationMessages();
323
+
324
+ const userSettingsResource = Object.freeze({
325
+ resource: "userSettings",
326
+ operations: Object.freeze({
327
+ view: Object.freeze({
328
+ method: "GET",
329
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
330
+ outputValidator: userSettingsOutputValidator
331
+ }),
332
+ list: Object.freeze({
333
+ method: "GET",
334
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
335
+ outputValidator: createCursorListValidator(userSettingsOutputValidator)
336
+ }),
337
+ create: Object.freeze({
338
+ method: "POST",
339
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
340
+ bodyValidator: Object.freeze({
341
+ get schema() {
342
+ return buildUserSettingsCreateBodySchema();
343
+ },
344
+ normalize: normalizeUserSettingsInput
345
+ }),
346
+ outputValidator: userSettingsOutputValidator
347
+ }),
348
+ replace: Object.freeze({
349
+ method: "PUT",
350
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
351
+ bodyValidator: Object.freeze({
352
+ get schema() {
353
+ return buildUserSettingsCreateBodySchema();
354
+ },
355
+ normalize: normalizeUserSettingsInput
356
+ }),
357
+ outputValidator: userSettingsOutputValidator
358
+ }),
359
+ patch: Object.freeze({
360
+ method: "PATCH",
361
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
362
+ bodyValidator: Object.freeze({
363
+ get schema() {
364
+ return buildUserSettingsPatchBodySchema();
365
+ },
366
+ normalize: normalizeUserSettingsInput
367
+ }),
368
+ outputValidator: userSettingsOutputValidator
369
+ }),
370
+ preferencesUpdate: Object.freeze({
371
+ method: "PATCH",
372
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
373
+ bodyValidator: preferencesUpdateBodyValidator,
374
+ outputValidator: userSettingsOutputValidator
375
+ }),
376
+ notificationsUpdate: Object.freeze({
377
+ method: "PATCH",
378
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
379
+ bodyValidator: notificationsUpdateBodyValidator,
380
+ outputValidator: userSettingsOutputValidator
381
+ }),
382
+ passwordChange: Object.freeze({
383
+ method: "POST",
384
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
385
+ bodyValidator: passwordChangeBodyValidator,
386
+ outputValidator: passwordChangeOutputValidator
387
+ }),
388
+ passwordMethodToggle: Object.freeze({
389
+ method: "PATCH",
390
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
391
+ bodyValidator: passwordMethodToggleBodyValidator,
392
+ outputValidator: settingsActionOutputValidator
393
+ }),
394
+ oauthLinkStart: Object.freeze({
395
+ method: "GET",
396
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
397
+ paramsValidator: oauthProviderParamsValidator,
398
+ queryValidator: oauthProviderQueryValidator,
399
+ outputValidator: oauthLinkStartOutputValidator
400
+ }),
401
+ oauthUnlink: Object.freeze({
402
+ method: "DELETE",
403
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
404
+ paramsValidator: oauthProviderParamsValidator,
405
+ outputValidator: settingsActionOutputValidator
406
+ }),
407
+ logoutOtherSessions: Object.freeze({
408
+ method: "POST",
409
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
410
+ bodyValidator: emptyBodyValidator,
411
+ outputValidator: settingsActionOutputValidator
412
+ })
413
+ })
414
+ });
415
+
416
+ export { userSettingsResource };