@jskit-ai/users-core 0.1.65 → 0.1.66

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 (56) hide show
  1. package/package.descriptor.mjs +14 -65
  2. package/package.json +10 -10
  3. package/src/server/UsersCoreServiceProvider.js +18 -2
  4. package/src/server/accountNotifications/accountNotificationsActions.js +3 -5
  5. package/src/server/accountNotifications/accountNotificationsService.js +3 -2
  6. package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +12 -11
  7. package/src/server/accountPreferences/accountPreferencesActions.js +3 -5
  8. package/src/server/accountPreferences/accountPreferencesService.js +3 -2
  9. package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +12 -11
  10. package/src/server/accountProfile/accountProfileActions.js +15 -32
  11. package/src/server/accountProfile/accountProfileService.js +9 -8
  12. package/src/server/accountProfile/bootAccountProfileRoutes.js +25 -19
  13. package/src/server/accountSecurity/accountSecurityActions.js +21 -16
  14. package/src/server/accountSecurity/accountSecurityService.js +16 -6
  15. package/src/server/accountSecurity/bootAccountSecurityRoutes.js +52 -40
  16. package/src/server/common/formatters/accountSettingsResponseFormatter.js +8 -18
  17. package/src/server/common/registerCommonRepositories.js +5 -2
  18. package/src/server/common/repositories/userProfilesRepository.js +227 -88
  19. package/src/server/common/repositories/userSettingsRepository.js +108 -100
  20. package/src/server/common/support/accountSettingsJsonApiTransport.js +10 -0
  21. package/src/server/usersBootstrapContributor.js +13 -32
  22. package/src/shared/resources/accountSettingsSchemas.js +83 -0
  23. package/src/shared/resources/userProfileResource.js +146 -126
  24. package/src/shared/resources/userSettingsResource.js +376 -353
  25. package/templates/packages/users/package.descriptor.mjs +4 -5
  26. package/templates/packages/users/package.json +0 -1
  27. package/templates/packages/users/src/server/UsersProvider.js +23 -24
  28. package/templates/packages/users/src/server/actions.js +26 -28
  29. package/templates/packages/users/src/server/registerRoutes.js +29 -15
  30. package/templates/packages/users/src/server/repository.js +35 -28
  31. package/templates/packages/users/src/server/service.js +20 -15
  32. package/templates/packages/users/src/shared/userResource.js +55 -68
  33. package/templates/packages/users-workspace/package.descriptor.mjs +4 -5
  34. package/templates/packages/users-workspace/src/server/UsersProvider.js +23 -24
  35. package/templates/packages/users-workspace/src/server/actions.js +28 -28
  36. package/templates/packages/users-workspace/src/server/registerRoutes.js +34 -16
  37. package/test/accountSecurityService.test.js +32 -0
  38. package/test/providerLifecycle.test.js +63 -0
  39. package/test/registerCommonRepositories.test.js +28 -8
  40. package/test/repositoryContracts.test.js +177 -28
  41. package/test/resourcesCanonical.test.js +18 -11
  42. package/test/userSettingsInternalResource.test.js +8 -0
  43. package/test/userSettingsResource.test.js +24 -7
  44. package/test/usersBootstrapContributor.test.js +40 -1
  45. package/test/usersPackageScaffoldContract.test.js +70 -3
  46. package/test/usersRouteRequestInputValidator.test.js +92 -23
  47. package/test/usersRouteResources.test.js +28 -18
  48. package/src/server/common/resources/userProfilesResource.js +0 -203
  49. package/src/server/common/validators/authenticatedUserValidator.js +0 -43
  50. package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
  51. package/src/shared/resources/userSettingsFields.js +0 -76
  52. package/templates/packages/main/src/shared/resources/userSettingsFields.js +0 -138
  53. package/templates/packages/users/src/server/actionIds.js +0 -6
  54. package/templates/packages/users/src/server/listConfig.js +0 -16
  55. package/test/settingsFieldRegistriesSingleton.test.js +0 -14
  56. package/test-support/registerDefaultSettingsFields.js +0 -2
@@ -1,405 +1,428 @@
1
- import { Type } from "typebox";
1
+ import { createSchema } from "json-rest-schema";
2
+ import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
3
+ import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
4
  import {
3
- createCursorListValidator,
4
- normalizeObjectInput,
5
- normalizeSettingsFieldInput
6
- } from "@jskit-ai/kernel/shared/validators";
7
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
+ createSchemaDefinition
6
+ } from "@jskit-ai/resource-core/shared/resource";
7
+ import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
8
8
  import { createOperationMessages } from "../operationMessages.js";
9
- import { userProfileResource } from "./userProfileResource.js";
9
+ import { DEFAULT_USER_SETTINGS } from "../settings.js";
10
10
  import {
11
- USER_SETTINGS_SECTIONS,
12
- userSettingsFields
13
- } from "./userSettingsFields.js";
14
-
15
- function pickPatchBody(schema, keys = []) {
16
- const properties = {};
17
- for (const key of keys) {
18
- if (!Object.hasOwn(schema.properties, key)) {
19
- throw new Error(`pickPatchBody requires patch field "${key}".`);
20
- }
21
-
22
- properties[key] = schema.properties[key];
23
- }
24
-
25
- return Type.Object(properties, {
26
- additionalProperties: false,
27
- minProperties: 1
28
- });
29
- }
30
-
31
- function listFieldsBySection(section) {
32
- return userSettingsFields.filter((field) => field.section === section);
33
- }
34
-
35
- function buildCreateBodySchema() {
36
- const properties = {};
37
- for (const field of userSettingsFields) {
38
- properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
39
- }
40
-
41
- return Type.Object(properties, { additionalProperties: false });
42
- }
43
-
44
- function buildSectionOutputSchema(section) {
45
- const properties = {};
46
- for (const field of listFieldsBySection(section)) {
47
- properties[field.key] = field.outputSchema;
48
- }
49
-
50
- return Type.Object(properties, { additionalProperties: false });
51
- }
52
-
53
- function normalizeInput(payload = {}) {
54
- return normalizeSettingsFieldInput(payload, userSettingsFields);
55
- }
56
-
57
- function normalizeSectionOutput(section, sectionSource = {}, settings = {}) {
58
- const normalized = {};
59
- for (const field of listFieldsBySection(section)) {
60
- const rawValue = Object.hasOwn(sectionSource, field.key)
61
- ? sectionSource[field.key]
62
- : field.resolveDefault({
63
- settings
64
- });
65
- normalized[field.key] = field.normalizeOutput(rawValue, {
66
- settings
67
- });
68
- }
69
- return normalized;
11
+ accountSecurityStatusSchema,
12
+ userProfileOutputSchema
13
+ } from "./accountSettingsSchemas.js";
14
+
15
+ const USER_SETTINGS_PREFERENCE_KEYS = deepFreeze([
16
+ "theme",
17
+ "locale",
18
+ "timeZone",
19
+ "dateFormat",
20
+ "numberFormat",
21
+ "currencyCode",
22
+ "avatarSize"
23
+ ]);
24
+
25
+ const USER_SETTINGS_NOTIFICATION_KEYS = deepFreeze([
26
+ "productUpdates",
27
+ "accountActivity",
28
+ "securityAlerts"
29
+ ]);
30
+
31
+ const USER_SETTINGS_ALL_KEYS = deepFreeze([
32
+ ...USER_SETTINGS_PREFERENCE_KEYS,
33
+ ...USER_SETTINGS_NOTIFICATION_KEYS
34
+ ]);
35
+
36
+ const USER_SETTINGS_BOOTSTRAP_KEYS = USER_SETTINGS_ALL_KEYS;
37
+
38
+ function normalizePositiveInteger(value) {
39
+ return Number(value);
70
40
  }
71
41
 
72
- function buildUserSettingsOutputSchema() {
73
- return Type.Object(
74
- {
75
- profile: userProfileResource.operations.view.outputValidator.schema,
76
- security: Type.Object({}, { additionalProperties: true }),
77
- preferences: buildSectionOutputSchema(USER_SETTINGS_SECTIONS.PREFERENCES),
78
- notifications: buildSectionOutputSchema(USER_SETTINGS_SECTIONS.NOTIFICATIONS)
79
- },
80
- { additionalProperties: true }
81
- );
82
- }
83
-
84
- const userSettingsOutputValidator = Object.freeze({
85
- get schema() {
86
- return buildUserSettingsOutputSchema();
42
+ const userSettingsBodySchema = createSchema({
43
+ theme: { type: "string", required: true, minLength: 1, maxLength: 32 },
44
+ locale: { type: "string", required: true, minLength: 1, maxLength: 24, lowercase: true },
45
+ timeZone: { type: "string", required: true, minLength: 1, maxLength: 64 },
46
+ dateFormat: { type: "string", required: true, minLength: 1, maxLength: 32 },
47
+ numberFormat: { type: "string", required: true, minLength: 1, maxLength: 32 },
48
+ currencyCode: {
49
+ type: "string",
50
+ required: true,
51
+ minLength: 3,
52
+ maxLength: 3,
53
+ uppercase: true,
54
+ pattern: "^[A-Z]{3}$"
87
55
  },
88
- normalize(payload = {}) {
89
- const source = normalizeObjectInput(payload);
90
- const preferencesSource = normalizeObjectInput(source.preferences);
91
- const notificationsSource = normalizeObjectInput(source.notifications);
92
-
93
- return {
94
- profile: normalizeObjectInput(source.profile),
95
- security: normalizeObjectInput(source.security),
96
- preferences: normalizeSectionOutput(
97
- USER_SETTINGS_SECTIONS.PREFERENCES,
98
- preferencesSource,
99
- preferencesSource
100
- ),
101
- notifications: normalizeSectionOutput(
102
- USER_SETTINGS_SECTIONS.NOTIFICATIONS,
103
- notificationsSource,
104
- notificationsSource
105
- )
106
- };
56
+ avatarSize: { type: "number", required: true, min: 1 },
57
+ productUpdates: {
58
+ type: "boolean",
59
+ required: true,
60
+ strictBoolean: true,
61
+ messages: {
62
+ default: "productUpdates must be a boolean."
63
+ }
64
+ },
65
+ accountActivity: {
66
+ type: "boolean",
67
+ required: true,
68
+ strictBoolean: true,
69
+ messages: {
70
+ default: "accountActivity must be a boolean."
71
+ }
72
+ },
73
+ securityAlerts: {
74
+ type: "boolean",
75
+ required: true,
76
+ strictBoolean: true,
77
+ messages: {
78
+ default: "securityAlerts must be a boolean."
79
+ }
107
80
  }
108
81
  });
109
82
 
110
- function buildUserSettingsCreateBodySchema() {
111
- return buildCreateBodySchema();
112
- }
113
-
114
- function buildUserSettingsPatchBodySchema() {
115
- return Type.Partial(buildUserSettingsCreateBodySchema(), {
116
- additionalProperties: false,
117
- minProperties: 1
118
- });
119
- }
120
-
121
- function buildPreferencesUpdateBodySchema() {
122
- return pickPatchBody(
123
- buildUserSettingsPatchBodySchema(),
124
- listFieldsBySection(USER_SETTINGS_SECTIONS.PREFERENCES).map((field) => field.key)
125
- );
126
- }
127
-
128
- function buildNotificationsUpdateBodySchema() {
129
- return pickPatchBody(
130
- buildUserSettingsPatchBodySchema(),
131
- listFieldsBySection(USER_SETTINGS_SECTIONS.NOTIFICATIONS).map((field) => field.key)
132
- );
133
- }
134
-
135
- const preferencesUpdateBodyValidator = Object.freeze({
136
- get schema() {
137
- return buildPreferencesUpdateBodySchema();
83
+ const userSettingsPreferencesSchema = createSchema({
84
+ theme: { type: "string", required: true, minLength: 1, maxLength: 32 },
85
+ locale: { type: "string", required: true, minLength: 1, maxLength: 24, lowercase: true },
86
+ timeZone: { type: "string", required: true, minLength: 1, maxLength: 64 },
87
+ dateFormat: { type: "string", required: true, minLength: 1, maxLength: 32 },
88
+ numberFormat: { type: "string", required: true, minLength: 1, maxLength: 32 },
89
+ currencyCode: {
90
+ type: "string",
91
+ required: true,
92
+ minLength: 3,
93
+ maxLength: 3,
94
+ uppercase: true,
95
+ pattern: "^[A-Z]{3}$"
138
96
  },
139
- normalize: normalizeInput
97
+ avatarSize: { type: "number", required: true, min: 1 }
140
98
  });
141
99
 
142
- const notificationsUpdateBodyValidator = Object.freeze({
143
- get schema() {
144
- return buildNotificationsUpdateBodySchema();
100
+ const userSettingsNotificationsSchema = createSchema({
101
+ productUpdates: {
102
+ type: "boolean",
103
+ required: true,
104
+ strictBoolean: true,
105
+ messages: {
106
+ default: "productUpdates must be a boolean."
107
+ }
108
+ },
109
+ accountActivity: {
110
+ type: "boolean",
111
+ required: true,
112
+ strictBoolean: true,
113
+ messages: {
114
+ default: "accountActivity must be a boolean."
115
+ }
145
116
  },
146
- normalize: normalizeInput
117
+ securityAlerts: {
118
+ type: "boolean",
119
+ required: true,
120
+ strictBoolean: true,
121
+ messages: {
122
+ default: "securityAlerts must be a boolean."
123
+ }
124
+ }
147
125
  });
148
126
 
149
- function normalizeOAuthProviderParams(payload = {}) {
150
- const source = normalizeObjectInput(payload);
151
- if (!Object.hasOwn(source, "provider")) {
152
- return {};
127
+ const userSettingsOutputSchema = createSchema({
128
+ profile: {
129
+ type: "object",
130
+ required: true,
131
+ schema: userProfileOutputSchema
132
+ },
133
+ security: {
134
+ type: "object",
135
+ required: true,
136
+ schema: accountSecurityStatusSchema
137
+ },
138
+ preferences: {
139
+ type: "object",
140
+ required: true,
141
+ schema: userSettingsPreferencesSchema
142
+ },
143
+ notifications: {
144
+ type: "object",
145
+ required: true,
146
+ schema: userSettingsNotificationsSchema
153
147
  }
148
+ });
154
149
 
155
- return {
156
- provider: normalizeText(source.provider)
157
- };
158
- }
150
+ const userSettingsOutputValidator = createSchemaDefinition(userSettingsOutputSchema, "replace");
159
151
 
160
- function normalizeOAuthProviderQuery(payload = {}) {
161
- const source = normalizeObjectInput(payload);
162
- if (!Object.hasOwn(source, "returnTo")) {
163
- return {};
152
+ const passwordMethodToggleOutputSchema = createSchema({
153
+ securityStatus: {
154
+ type: "object",
155
+ required: true,
156
+ schema: accountSecurityStatusSchema
157
+ },
158
+ settings: {
159
+ type: "object",
160
+ required: true,
161
+ schema: userSettingsOutputSchema
164
162
  }
163
+ });
165
164
 
166
- const returnTo = normalizeText(source.returnTo);
167
- if (!returnTo) {
168
- return {};
165
+ const oauthUnlinkOutputSchema = createSchema({
166
+ securityStatus: {
167
+ type: "object",
168
+ required: true,
169
+ schema: accountSecurityStatusSchema
169
170
  }
170
-
171
- return {
172
- returnTo
173
- };
174
- }
175
-
176
- const settingsActionOutputValidator = Object.freeze({
177
- schema: Type.Object({}, { additionalProperties: true }),
178
- normalize: normalizeObjectInput
179
171
  });
180
172
 
181
- const passwordChangeOutputValidator = Object.freeze({
182
- schema: Type.Object(
183
- {
184
- ok: Type.Boolean(),
185
- message: Type.String()
186
- },
187
- { additionalProperties: false }
188
- ),
189
- normalize: normalizeObjectInput
173
+ const passwordChangeBodySchema = createSchema({
174
+ currentPassword: {
175
+ type: "string",
176
+ required: false,
177
+ minLength: 1,
178
+ messages: {
179
+ default: "Current password is invalid."
180
+ }
181
+ },
182
+ newPassword: {
183
+ type: "string",
184
+ required: true,
185
+ minLength: 8,
186
+ messages: {
187
+ required: "New password is required.",
188
+ minLength: "New password must be at least 8 characters.",
189
+ default: "New password must be at least 8 characters."
190
+ }
191
+ },
192
+ confirmPassword: {
193
+ type: "string",
194
+ required: true,
195
+ minLength: 1,
196
+ messages: {
197
+ required: "Confirm password is required.",
198
+ minLength: "Confirm password is required.",
199
+ default: "Confirm password is required."
200
+ }
201
+ }
190
202
  });
191
203
 
192
- const passwordChangeBodyValidator = Object.freeze({
193
- schema: Type.Object(
194
- {
195
- currentPassword: Type.Optional(
196
- Type.String({
197
- minLength: 1,
198
- messages: {
199
- default: "Current password is invalid."
200
- }
201
- })
202
- ),
203
- newPassword: Type.String({
204
- minLength: 8,
205
- messages: {
206
- required: "New password is required.",
207
- minLength: "New password must be at least 8 characters.",
208
- default: "New password must be at least 8 characters."
209
- }
210
- }),
211
- confirmPassword: Type.String({
212
- minLength: 1,
213
- messages: {
214
- required: "Confirm password is required.",
215
- minLength: "Confirm password is required.",
216
- default: "Confirm password is required."
217
- }
218
- })
219
- },
220
- {
221
- additionalProperties: false,
222
- messages: {
223
- additionalProperties: "Unexpected field."
224
- }
225
- }
226
- ),
227
- normalize: normalizeObjectInput
204
+ const passwordChangeOutputSchema = createSchema({
205
+ ok: { type: "boolean", required: true },
206
+ message: { type: "string", required: true, minLength: 1 }
228
207
  });
229
208
 
230
- const passwordMethodToggleBodyValidator = Object.freeze({
231
- schema: Type.Object(
232
- {
233
- enabled: Type.Boolean({
234
- messages: {
235
- required: "enabled is required.",
236
- default: "enabled must be a boolean."
237
- }
238
- })
239
- },
240
- {
241
- additionalProperties: false,
242
- messages: {
243
- additionalProperties: "Unexpected field."
244
- }
209
+ const passwordMethodToggleBodySchema = createSchema({
210
+ enabled: {
211
+ type: "boolean",
212
+ required: true,
213
+ strictBoolean: true,
214
+ messages: {
215
+ required: "enabled is required.",
216
+ default: "enabled must be a boolean."
245
217
  }
246
- ),
247
- normalize: normalizeObjectInput
218
+ }
248
219
  });
249
220
 
250
- const oauthProviderParamsValidator = Object.freeze({
251
- schema: Type.Object(
252
- {
253
- provider: Type.String({
254
- minLength: 2,
255
- maxLength: 64,
256
- messages: {
257
- required: "OAuth provider is required.",
258
- default: "OAuth provider is invalid."
259
- }
260
- })
261
- },
262
- {
263
- additionalProperties: false,
264
- messages: {
265
- additionalProperties: "Unexpected field."
266
- }
221
+ const oauthProviderParamsSchema = createSchema({
222
+ provider: {
223
+ type: "string",
224
+ required: true,
225
+ minLength: 2,
226
+ maxLength: 64,
227
+ messages: {
228
+ required: "OAuth provider is required.",
229
+ default: "OAuth provider is invalid."
267
230
  }
268
- ),
269
- normalize: normalizeOAuthProviderParams
231
+ }
270
232
  });
271
233
 
272
- const oauthProviderQueryValidator = Object.freeze({
273
- schema: Type.Object(
274
- {
275
- returnTo: Type.Optional(
276
- Type.String({
277
- minLength: 1,
278
- messages: {
279
- default: "Return path is invalid."
280
- }
281
- })
282
- )
283
- },
284
- {
285
- additionalProperties: false,
286
- messages: {
287
- additionalProperties: "Unexpected field."
288
- }
234
+ const oauthProviderQuerySchema = createSchema({
235
+ returnTo: {
236
+ type: "string",
237
+ required: false,
238
+ minLength: 1,
239
+ messages: {
240
+ default: "Return path is invalid."
289
241
  }
290
- ),
291
- normalize: normalizeOAuthProviderQuery
242
+ }
292
243
  });
293
244
 
294
- const oauthLinkStartOutputValidator = Object.freeze({
295
- schema: Type.Object(
296
- {
297
- provider: Type.String({ minLength: 2, maxLength: 64 }),
298
- returnTo: Type.String({ minLength: 1 }),
299
- url: Type.String({ minLength: 1 })
300
- },
301
- { additionalProperties: false }
302
- ),
303
- normalize: normalizeObjectInput
245
+ const oauthLinkStartOutputSchema = createSchema({
246
+ provider: { type: "string", required: true, minLength: 2, maxLength: 64 },
247
+ returnTo: { type: "string", required: true, minLength: 1 },
248
+ url: { type: "string", required: true, minLength: 1 }
304
249
  });
305
250
 
306
- const emptyBodyValidator = Object.freeze({
307
- schema: Type.Object({}, { additionalProperties: false }),
308
- normalize: normalizeObjectInput
251
+ const logoutOtherSessionsOutputSchema = createSchema({
252
+ ok: { type: "boolean", required: true }
309
253
  });
310
254
 
311
255
  const USER_SETTINGS_OPERATION_MESSAGES = createOperationMessages();
312
256
 
313
- const userSettingsResource = Object.freeze({
257
+ const userSettingsResource = defineCrudResource({
314
258
  namespace: "userSettings",
315
- operations: Object.freeze({
316
- view: Object.freeze({
317
- method: "GET",
318
- messages: USER_SETTINGS_OPERATION_MESSAGES,
319
- outputValidator: userSettingsOutputValidator
320
- }),
321
- list: Object.freeze({
322
- method: "GET",
323
- messages: USER_SETTINGS_OPERATION_MESSAGES,
324
- outputValidator: createCursorListValidator(userSettingsOutputValidator)
325
- }),
326
- create: Object.freeze({
327
- method: "POST",
328
- messages: USER_SETTINGS_OPERATION_MESSAGES,
329
- bodyValidator: Object.freeze({
330
- get schema() {
331
- return buildUserSettingsCreateBodySchema();
332
- },
333
- normalize: normalizeInput
334
- }),
335
- outputValidator: userSettingsOutputValidator
336
- }),
337
- replace: Object.freeze({
338
- method: "PUT",
339
- messages: USER_SETTINGS_OPERATION_MESSAGES,
340
- bodyValidator: Object.freeze({
341
- get schema() {
342
- return buildUserSettingsCreateBodySchema();
343
- },
344
- normalize: normalizeInput
345
- }),
346
- outputValidator: userSettingsOutputValidator
347
- }),
348
- patch: Object.freeze({
349
- method: "PATCH",
350
- messages: USER_SETTINGS_OPERATION_MESSAGES,
351
- bodyValidator: Object.freeze({
352
- get schema() {
353
- return buildUserSettingsPatchBodySchema();
354
- },
355
- normalize: normalizeInput
356
- }),
357
- outputValidator: userSettingsOutputValidator
358
- }),
359
- preferencesUpdate: Object.freeze({
259
+ tableName: "user_settings",
260
+ idProperty: "user_id",
261
+ searchSchema: {
262
+ id: { type: "id", actualField: "id" }
263
+ },
264
+ schema: {
265
+ id: {
266
+ type: "id",
267
+ primary: true,
268
+ required: true,
269
+ search: true,
270
+ storage: { column: "user_id" }
271
+ },
272
+ theme: {
273
+ type: "string",
274
+ required: true,
275
+ max: 32,
276
+ defaultTo: DEFAULT_USER_SETTINGS.theme,
277
+ setter: (value) => normalizeText(value)
278
+ },
279
+ locale: {
280
+ type: "string",
281
+ required: true,
282
+ max: 24,
283
+ defaultTo: DEFAULT_USER_SETTINGS.locale,
284
+ setter: (value) => normalizeLowerText(value)
285
+ },
286
+ timeZone: {
287
+ type: "string",
288
+ required: true,
289
+ max: 64,
290
+ defaultTo: DEFAULT_USER_SETTINGS.timeZone,
291
+ storage: { column: "time_zone" },
292
+ setter: (value) => normalizeText(value)
293
+ },
294
+ dateFormat: {
295
+ type: "string",
296
+ required: true,
297
+ max: 32,
298
+ defaultTo: DEFAULT_USER_SETTINGS.dateFormat,
299
+ storage: { column: "date_format" },
300
+ setter: (value) => normalizeText(value)
301
+ },
302
+ numberFormat: {
303
+ type: "string",
304
+ required: true,
305
+ max: 32,
306
+ defaultTo: DEFAULT_USER_SETTINGS.numberFormat,
307
+ storage: { column: "number_format" },
308
+ setter: (value) => normalizeText(value)
309
+ },
310
+ currencyCode: {
311
+ type: "string",
312
+ required: true,
313
+ minLength: 3,
314
+ maxLength: 3,
315
+ defaultTo: DEFAULT_USER_SETTINGS.currencyCode,
316
+ storage: { column: "currency_code" },
317
+ setter: (value) => normalizeText(value).toUpperCase()
318
+ },
319
+ avatarSize: {
320
+ type: "number",
321
+ required: true,
322
+ min: 1,
323
+ defaultTo: DEFAULT_USER_SETTINGS.avatarSize,
324
+ storage: { column: "avatar_size" },
325
+ setter: (value) => normalizePositiveInteger(value)
326
+ },
327
+ passwordSignInEnabled: {
328
+ type: "boolean",
329
+ required: true,
330
+ defaultTo: DEFAULT_USER_SETTINGS.passwordSignInEnabled,
331
+ storage: { column: "password_sign_in_enabled" }
332
+ },
333
+ passwordSetupRequired: {
334
+ type: "boolean",
335
+ required: true,
336
+ defaultTo: DEFAULT_USER_SETTINGS.passwordSetupRequired,
337
+ storage: { column: "password_setup_required" }
338
+ },
339
+ productUpdates: {
340
+ type: "boolean",
341
+ required: true,
342
+ defaultTo: DEFAULT_USER_SETTINGS.productUpdates,
343
+ storage: { column: "notify_product_updates" }
344
+ },
345
+ accountActivity: {
346
+ type: "boolean",
347
+ required: true,
348
+ defaultTo: DEFAULT_USER_SETTINGS.accountActivity,
349
+ storage: { column: "notify_account_activity" }
350
+ },
351
+ securityAlerts: {
352
+ type: "boolean",
353
+ required: true,
354
+ defaultTo: DEFAULT_USER_SETTINGS.securityAlerts,
355
+ storage: { column: "notify_security_alerts" }
356
+ },
357
+ createdAt: {
358
+ type: "dateTime",
359
+ default: "now()",
360
+ storage: {
361
+ column: "created_at",
362
+ writeSerializer: "datetime-utc"
363
+ }
364
+ },
365
+ updatedAt: {
366
+ type: "dateTime",
367
+ default: "now()",
368
+ storage: {
369
+ column: "updated_at",
370
+ writeSerializer: "datetime-utc"
371
+ }
372
+ }
373
+ },
374
+ messages: USER_SETTINGS_OPERATION_MESSAGES,
375
+ crudOperations: ["view", "list", "create", "replace", "patch"],
376
+ crud: {
377
+ output: userSettingsOutputValidator,
378
+ body: userSettingsBodySchema
379
+ },
380
+ operations: {
381
+ preferencesUpdate: {
360
382
  method: "PATCH",
361
- messages: USER_SETTINGS_OPERATION_MESSAGES,
362
- bodyValidator: preferencesUpdateBodyValidator,
363
- outputValidator: userSettingsOutputValidator
364
- }),
365
- notificationsUpdate: Object.freeze({
383
+ body: userSettingsPreferencesSchema,
384
+ output: userSettingsOutputValidator
385
+ },
386
+ notificationsUpdate: {
366
387
  method: "PATCH",
367
- messages: USER_SETTINGS_OPERATION_MESSAGES,
368
- bodyValidator: notificationsUpdateBodyValidator,
369
- outputValidator: userSettingsOutputValidator
370
- }),
371
- passwordChange: Object.freeze({
388
+ body: userSettingsNotificationsSchema,
389
+ output: userSettingsOutputValidator
390
+ },
391
+ passwordChange: {
372
392
  method: "POST",
373
- messages: USER_SETTINGS_OPERATION_MESSAGES,
374
- bodyValidator: passwordChangeBodyValidator,
375
- outputValidator: passwordChangeOutputValidator
376
- }),
377
- passwordMethodToggle: Object.freeze({
393
+ body: passwordChangeBodySchema,
394
+ output: passwordChangeOutputSchema
395
+ },
396
+ passwordMethodToggle: {
378
397
  method: "PATCH",
379
- messages: USER_SETTINGS_OPERATION_MESSAGES,
380
- bodyValidator: passwordMethodToggleBodyValidator,
381
- outputValidator: settingsActionOutputValidator
382
- }),
383
- oauthLinkStart: Object.freeze({
398
+ body: passwordMethodToggleBodySchema,
399
+ output: passwordMethodToggleOutputSchema
400
+ },
401
+ oauthLinkStart: {
384
402
  method: "GET",
385
- messages: USER_SETTINGS_OPERATION_MESSAGES,
386
- paramsValidator: oauthProviderParamsValidator,
387
- queryValidator: oauthProviderQueryValidator,
388
- outputValidator: oauthLinkStartOutputValidator
389
- }),
390
- oauthUnlink: Object.freeze({
403
+ params: oauthProviderParamsSchema,
404
+ query: oauthProviderQuerySchema,
405
+ output: oauthLinkStartOutputSchema
406
+ },
407
+ oauthUnlink: {
391
408
  method: "DELETE",
392
- messages: USER_SETTINGS_OPERATION_MESSAGES,
393
- paramsValidator: oauthProviderParamsValidator,
394
- outputValidator: settingsActionOutputValidator
395
- }),
396
- logoutOtherSessions: Object.freeze({
409
+ params: oauthProviderParamsSchema,
410
+ output: oauthUnlinkOutputSchema
411
+ },
412
+ logoutOtherSessions: {
397
413
  method: "POST",
398
- messages: USER_SETTINGS_OPERATION_MESSAGES,
399
- bodyValidator: emptyBodyValidator,
400
- outputValidator: settingsActionOutputValidator
401
- })
402
- })
414
+ body: createSchema({}),
415
+ output: logoutOtherSessionsOutputSchema
416
+ }
417
+ }
403
418
  });
404
419
 
405
- export { userSettingsResource };
420
+ export {
421
+ USER_SETTINGS_ALL_KEYS,
422
+ USER_SETTINGS_BOOTSTRAP_KEYS,
423
+ USER_SETTINGS_NOTIFICATION_KEYS,
424
+ USER_SETTINGS_PREFERENCE_KEYS,
425
+ userSettingsOutputSchema,
426
+ userSettingsOutputValidator,
427
+ userSettingsResource
428
+ };