@jskit-ai/users-core 0.1.64 → 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,13 +1,16 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-core",
4
- version: "0.1.64",
4
+ version: "0.1.66",
5
5
  kind: "runtime",
6
6
  description: "Users/account runtime plus HTTP routes for account features.",
7
7
  dependsOn: [
8
8
  "@jskit-ai/auth-core",
9
9
  "@jskit-ai/database-runtime",
10
10
  "@jskit-ai/http-runtime",
11
+ "@jskit-ai/json-rest-api-core",
12
+ "@jskit-ai/resource-core",
13
+ "@jskit-ai/resource-crud-core",
11
14
  "@jskit-ai/uploads-runtime",
12
15
  "@jskit-ai/storage-runtime"
13
16
  ],
@@ -19,6 +22,7 @@ export default Object.freeze({
19
22
  requires: [
20
23
  "runtime.actions",
21
24
  "runtime.database",
25
+ "json-rest-api.core",
22
26
  "runtime.storage",
23
27
  "runtime.uploads",
24
28
  "auth.provider",
@@ -128,15 +132,16 @@ export default Object.freeze({
128
132
  mutations: {
129
133
  dependencies: {
130
134
  runtime: {
131
- "@jskit-ai/auth-core": "0.1.53",
132
- "@jskit-ai/crud-core": "0.1.62",
133
- "@jskit-ai/database-runtime": "0.1.54",
134
- "@jskit-ai/http-runtime": "0.1.53",
135
- "@jskit-ai/kernel": "0.1.54",
135
+ "@jskit-ai/auth-core": "0.1.55",
136
+ "@jskit-ai/crud-core": "0.1.64",
137
+ "@jskit-ai/database-runtime": "0.1.56",
138
+ "@jskit-ai/http-runtime": "0.1.55",
139
+ "@jskit-ai/json-rest-api-core": "0.1.1",
140
+ "@jskit-ai/kernel": "0.1.56",
141
+ "@jskit-ai/resource-core": "0.1.1",
142
+ "@jskit-ai/resource-crud-core": "0.1.1",
136
143
  "@local/users": "file:packages/users",
137
- "@jskit-ai/uploads-runtime": "0.1.32",
138
- "@fastify/type-provider-typebox": "^6.1.0",
139
- typebox: "^1.0.81"
144
+ "@jskit-ai/uploads-runtime": "0.1.34"
140
145
  },
141
146
  dev: {}
142
147
  },
@@ -163,14 +168,6 @@ export default Object.freeze({
163
168
  category: "migration",
164
169
  id: "users-core-profile-username-schema"
165
170
  },
166
- {
167
- from: "templates/packages/main/src/shared/resources/userSettingsFields.js",
168
- to: "packages/main/src/shared/resources/userSettingsFields.js",
169
- preserveOnRemove: true,
170
- reason: "Install app-owned user settings field definitions.",
171
- category: "users-core",
172
- id: "users-core-app-owned-user-settings-fields"
173
- },
174
171
  {
175
172
  from: "templates/packages/users/package.json",
176
173
  to: "packages/users/package.json",
@@ -232,15 +229,6 @@ export default Object.freeze({
232
229
  in: ["personal", "workspaces"]
233
230
  }
234
231
  },
235
- {
236
- from: "templates/packages/users/src/server/actionIds.js",
237
- to: "packages/users/src/server/actionIds.js",
238
- ownership: "app",
239
- preserveOnRemove: true,
240
- reason: "Install app-owned users CRUD action IDs.",
241
- category: "users-core",
242
- id: "users-core-users-action-ids"
243
- },
244
232
  {
245
233
  from: "templates/packages/users/src/server/actions.js",
246
234
  to: "packages/users/src/server/actions.js",
@@ -267,15 +255,6 @@ export default Object.freeze({
267
255
  in: ["personal", "workspaces"]
268
256
  }
269
257
  },
270
- {
271
- from: "templates/packages/users/src/server/listConfig.js",
272
- to: "packages/users/src/server/listConfig.js",
273
- ownership: "app",
274
- preserveOnRemove: true,
275
- reason: "Install app-owned users CRUD list configuration.",
276
- category: "users-core",
277
- id: "users-core-users-list-config"
278
- },
279
258
  {
280
259
  from: "templates/packages/users/src/server/registerRoutes.js",
281
260
  to: "packages/users/src/server/registerRoutes.js",
@@ -349,36 +328,6 @@ export default Object.freeze({
349
328
  category: "runtime-config",
350
329
  id: "users-core-auth-profile-mode"
351
330
  },
352
- {
353
- op: "append-text",
354
- file: "packages/main/src/shared/index.js",
355
- position: "top",
356
- skipIfContains: "import \"./resources/userSettingsFields.js\";",
357
- value: "import \"./resources/userSettingsFields.js\";\n",
358
- reason: "Load app-owned user settings field definitions inside the main shared module.",
359
- category: "users-core",
360
- id: "users-core-main-shared-user-settings-field-import"
361
- },
362
- {
363
- op: "append-text",
364
- file: "src/main.js",
365
- position: "top",
366
- skipIfContains: "import \"@local/main/shared\";",
367
- value: "import \"@local/main/shared\";\n",
368
- reason: "Ensure client runtime loads app-owned shared settings field registration.",
369
- category: "users-core",
370
- id: "users-core-client-import-main-shared"
371
- },
372
- {
373
- op: "append-text",
374
- file: "server.js",
375
- position: "top",
376
- skipIfContains: "import \"@local/main/shared\";",
377
- value: "import \"@local/main/shared\";\n",
378
- reason: "Ensure server runtime loads app-owned shared settings field registration.",
379
- category: "users-core",
380
- id: "users-core-server-import-main-shared"
381
- }
382
331
  ]
383
332
  }
384
333
  });
package/package.json CHANGED
@@ -1,24 +1,24 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-core",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "exports": {
9
9
  "./server/profileSyncLifecycleContributorRegistry": "./src/server/profileSyncLifecycleContributorRegistry.js",
10
- "./shared/settings": "./src/shared/settings.js",
11
10
  "./shared/resources/userProfileResource": "./src/shared/resources/userProfileResource.js",
12
- "./shared/resources/userSettingsFields": "./src/shared/resources/userSettingsFields.js",
13
11
  "./shared/resources/userSettingsResource": "./src/shared/resources/userSettingsResource.js"
14
12
  },
15
13
  "dependencies": {
16
- "@jskit-ai/auth-core": "0.1.53",
17
- "@jskit-ai/database-runtime": "0.1.54",
18
- "@jskit-ai/http-runtime": "0.1.53",
19
- "@jskit-ai/kernel": "0.1.54",
20
- "@jskit-ai/uploads-runtime": "0.1.32",
21
- "@fastify/type-provider-typebox": "^6.1.0",
22
- "typebox": "^1.0.81"
14
+ "@jskit-ai/auth-core": "0.1.55",
15
+ "@jskit-ai/database-runtime": "0.1.56",
16
+ "@jskit-ai/http-runtime": "0.1.55",
17
+ "@jskit-ai/json-rest-api-core": "0.1.1",
18
+ "@jskit-ai/kernel": "0.1.56",
19
+ "@jskit-ai/resource-crud-core": "0.1.1",
20
+ "@jskit-ai/resource-core": "0.1.1",
21
+ "@jskit-ai/uploads-runtime": "0.1.34",
22
+ "json-rest-schema": "1.x.x"
23
23
  }
24
24
  }
@@ -1,4 +1,10 @@
1
1
  import { USERS_SHARED_API } from "../shared/index.js";
2
+ import {
3
+ INTERNAL_JSON_REST_API,
4
+ addResourceIfMissing,
5
+ createJsonRestResourceScopeOptions
6
+ } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
7
+ import { toDatabaseDateTimeUtc } from "@jskit-ai/database-runtime/shared";
2
8
  import { bootAccountProfileRoutes } from "./accountProfile/bootAccountProfileRoutes.js";
3
9
  import { bootAccountPreferencesRoutes } from "./accountPreferences/bootAccountPreferencesRoutes.js";
4
10
  import { bootAccountNotificationsRoutes } from "./accountNotifications/bootAccountNotificationsRoutes.js";
@@ -11,13 +17,15 @@ import { registerAccountPreferences } from "./accountPreferences/registerAccount
11
17
  import { registerAccountNotifications } from "./accountNotifications/registerAccountNotifications.js";
12
18
  import { registerAccountProfile } from "./accountProfile/registerAccountProfile.js";
13
19
  import { registerAccountSecurity } from "./accountSecurity/registerAccountSecurity.js";
20
+ import { userProfileResource } from "../shared/resources/userProfileResource.js";
21
+ import { userSettingsResource } from "../shared/resources/userSettingsResource.js";
14
22
 
15
23
  class UsersCoreServiceProvider {
16
24
  static id = "users.core";
17
25
 
18
- static dependsOn = ["runtime.server", "runtime.actions", "runtime.database", "runtime.storage", "auth.provider", "runtime.uploads"];
26
+ static dependsOn = ["runtime.server", "runtime.actions", "runtime.database", "runtime.storage", "auth.provider", "runtime.uploads", "json-rest-api.core"];
19
27
 
20
- register(app) {
28
+ async register(app) {
21
29
  registerSharedApi(app, USERS_SHARED_API);
22
30
  registerCommonRepositories(app);
23
31
  registerUsersCore(app);
@@ -30,6 +38,14 @@ class UsersCoreServiceProvider {
30
38
  }
31
39
 
32
40
  async boot(app) {
41
+ const api = app.make(INTERNAL_JSON_REST_API);
42
+ const scopeOptions = {
43
+ writeSerializers: {
44
+ "datetime-utc": toDatabaseDateTimeUtc
45
+ }
46
+ };
47
+ await addResourceIfMissing(api, "userProfiles", createJsonRestResourceScopeOptions(userProfileResource, scopeOptions));
48
+ await addResourceIfMissing(api, "userSettings", createJsonRestResourceScopeOptions(userSettingsResource, scopeOptions));
33
49
  bootAccountProfileRoutes(app);
34
50
  bootAccountPreferencesRoutes(app);
35
51
  bootAccountNotificationsRoutes(app);
@@ -14,10 +14,8 @@ const accountNotificationsActions = Object.freeze([
14
14
  permission: {
15
15
  require: "authenticated"
16
16
  },
17
- inputValidator: {
18
- payload: userSettingsResource.operations.notificationsUpdate.bodyValidator
19
- },
20
- outputValidator: userSettingsResource.operations.view.outputValidator,
17
+ input: userSettingsResource.operations.notificationsUpdate.body,
18
+ output: null,
21
19
  idempotency: "optional",
22
20
  audit: {
23
21
  actionName: "settings.notifications.update"
@@ -27,7 +25,7 @@ const accountNotificationsActions = Object.freeze([
27
25
  return deps.accountNotificationsService.updateNotifications(
28
26
  resolveRequest(context),
29
27
  resolveActionUser(context, input),
30
- input.payload,
28
+ input,
31
29
  {
32
30
  context
33
31
  }
@@ -1,4 +1,5 @@
1
1
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+ import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
2
3
  import {
3
4
  resolveUserProfile,
4
5
  resolveSecurityStatus
@@ -25,12 +26,12 @@ function createService({
25
26
  const settings = await userSettingsRepository.updateNotifications(profile.id, payload);
26
27
  const securityStatus = await resolveSecurityStatus(authService, request);
27
28
 
28
- return accountSettingsResponseFormatter({
29
+ return returnJsonApiData(accountSettingsResponseFormatter({
29
30
  profile,
30
31
  settings,
31
32
  securityStatus,
32
33
  authService
33
- });
34
+ }));
34
35
  }
35
36
 
36
37
  return Object.freeze({
@@ -1,5 +1,6 @@
1
- import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
1
+ import { createJsonApiResourceRouteContract } from "@jskit-ai/http-runtime/shared/validators/jsonApiRouteTransport";
2
2
  import { userSettingsResource } from "../../shared/resources/userSettingsResource.js";
3
+ import { resolveAccountSettingsResourceId } from "../common/support/accountSettingsJsonApiTransport.js";
3
4
 
4
5
  function bootAccountNotificationsRoutes(app) {
5
6
  if (!app || typeof app.make !== "function") {
@@ -17,20 +18,20 @@ function bootAccountNotificationsRoutes(app) {
17
18
  tags: ["settings"],
18
19
  summary: "Update notification settings"
19
20
  },
20
- bodyValidator: userSettingsResource.operations.notificationsUpdate.bodyValidator,
21
- responseValidators: withStandardErrorResponses(
22
- {
23
- 200: userSettingsResource.operations.view.outputValidator
24
- },
25
- { includeValidation400: true }
26
- )
21
+ ...createJsonApiResourceRouteContract({
22
+ requestType: "user-notification-settings",
23
+ responseType: "user-settings",
24
+ body: userSettingsResource.operations.notificationsUpdate.body,
25
+ output: userSettingsResource.operations.view.output,
26
+ outputKind: "record",
27
+ getRecordId: resolveAccountSettingsResourceId,
28
+ includeValidation400: true
29
+ })
27
30
  },
28
31
  async function (request, reply) {
29
32
  const response = await request.executeAction({
30
33
  actionId: "settings.notifications.update",
31
- input: {
32
- payload: request.input.body
33
- }
34
+ input: request.input.body
34
35
  });
35
36
  reply.code(200).send(response);
36
37
  }
@@ -14,10 +14,8 @@ const accountPreferencesActions = Object.freeze([
14
14
  permission: {
15
15
  require: "authenticated"
16
16
  },
17
- inputValidator: {
18
- payload: userSettingsResource.operations.preferencesUpdate.bodyValidator
19
- },
20
- outputValidator: userSettingsResource.operations.view.outputValidator,
17
+ input: userSettingsResource.operations.preferencesUpdate.body,
18
+ output: null,
21
19
  idempotency: "optional",
22
20
  audit: {
23
21
  actionName: "settings.preferences.update"
@@ -27,7 +25,7 @@ const accountPreferencesActions = Object.freeze([
27
25
  return deps.accountPreferencesService.updatePreferences(
28
26
  resolveRequest(context),
29
27
  resolveActionUser(context, input),
30
- input.payload,
28
+ input,
31
29
  {
32
30
  context
33
31
  }
@@ -1,4 +1,5 @@
1
1
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+ import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
2
3
  import {
3
4
  resolveUserProfile,
4
5
  resolveSecurityStatus
@@ -25,12 +26,12 @@ function createService({
25
26
  const settings = await userSettingsRepository.updatePreferences(profile.id, payload);
26
27
  const securityStatus = await resolveSecurityStatus(authService, request);
27
28
 
28
- return accountSettingsResponseFormatter({
29
+ return returnJsonApiData(accountSettingsResponseFormatter({
29
30
  profile,
30
31
  settings,
31
32
  securityStatus,
32
33
  authService
33
- });
34
+ }));
34
35
  }
35
36
 
36
37
  return Object.freeze({
@@ -1,5 +1,6 @@
1
- import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
1
+ import { createJsonApiResourceRouteContract } from "@jskit-ai/http-runtime/shared/validators/jsonApiRouteTransport";
2
2
  import { userSettingsResource } from "../../shared/resources/userSettingsResource.js";
3
+ import { resolveAccountSettingsResourceId } from "../common/support/accountSettingsJsonApiTransport.js";
3
4
 
4
5
  function bootAccountPreferencesRoutes(app) {
5
6
  if (!app || typeof app.make !== "function") {
@@ -17,20 +18,20 @@ function bootAccountPreferencesRoutes(app) {
17
18
  tags: ["settings"],
18
19
  summary: "Update user preferences"
19
20
  },
20
- bodyValidator: userSettingsResource.operations.preferencesUpdate.bodyValidator,
21
- responseValidators: withStandardErrorResponses(
22
- {
23
- 200: userSettingsResource.operations.view.outputValidator
24
- },
25
- { includeValidation400: true }
26
- )
21
+ ...createJsonApiResourceRouteContract({
22
+ requestType: "user-preferences",
23
+ responseType: "user-settings",
24
+ body: userSettingsResource.operations.preferencesUpdate.body,
25
+ output: userSettingsResource.operations.view.output,
26
+ outputKind: "record",
27
+ getRecordId: resolveAccountSettingsResourceId,
28
+ includeValidation400: true
29
+ })
27
30
  },
28
31
  async function (request, reply) {
29
32
  const response = await request.executeAction({
30
33
  actionId: "settings.preferences.update",
31
- input: {
32
- payload: request.input.body
33
- }
34
+ input: request.input.body
34
35
  });
35
36
  reply.code(200).send(response);
36
37
  }
@@ -1,32 +1,17 @@
1
1
  import {
2
- EMPTY_INPUT_VALIDATOR,
2
+ emptyInputValidator,
3
3
  resolveRequest
4
4
  } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
5
- import { Type } from "typebox";
6
- import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
7
- import { userSettingsResource } from "../../shared/resources/userSettingsResource.js";
5
+ import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
8
6
  import { userProfileResource } from "../../shared/resources/userProfileResource.js";
9
7
  import { resolveActionUser } from "../common/support/resolveActionUser.js";
10
8
 
11
- const settingsProfileUpdateOutputValidator = Object.freeze({
12
- schema: Type.Object(
13
- {
14
- settings: userSettingsResource.operations.view.outputValidator.schema,
15
- session: Type.Union([Type.Object({}, { additionalProperties: true }), Type.Null()])
16
- },
17
- { additionalProperties: false }
18
- ),
19
- normalize(payload = {}) {
20
- const source = normalizeObjectInput(payload);
21
-
22
- return {
23
- settings: userSettingsResource.operations.view.outputValidator.normalize(source.settings),
24
- session: source.session && typeof source.session === "object" ? source.session : null
25
- };
26
- }
9
+ const settingsProfileUpdateInputValidator = deepFreeze({
10
+ schema: userProfileResource.operations.patch.body.schema,
11
+ mode: userProfileResource.operations.patch.body.mode
27
12
  });
28
13
 
29
- const accountProfileActions = Object.freeze([
14
+ const accountProfileActions = deepFreeze([
30
15
  {
31
16
  id: "settings.read",
32
17
  version: 1,
@@ -36,8 +21,8 @@ const accountProfileActions = Object.freeze([
36
21
  permission: {
37
22
  require: "authenticated"
38
23
  },
39
- inputValidator: EMPTY_INPUT_VALIDATOR,
40
- outputValidator: userSettingsResource.operations.view.outputValidator,
24
+ input: emptyInputValidator,
25
+ output: null,
41
26
  idempotency: "none",
42
27
  audit: {
43
28
  actionName: "settings.read"
@@ -58,10 +43,8 @@ const accountProfileActions = Object.freeze([
58
43
  permission: {
59
44
  require: "authenticated"
60
45
  },
61
- inputValidator: {
62
- payload: userProfileResource.operations.patch.bodyValidator
63
- },
64
- outputValidator: settingsProfileUpdateOutputValidator,
46
+ input: settingsProfileUpdateInputValidator,
47
+ output: null,
65
48
  idempotency: "optional",
66
49
  audit: {
67
50
  actionName: "settings.profile.update"
@@ -71,7 +54,7 @@ const accountProfileActions = Object.freeze([
71
54
  return deps.accountProfileService.updateProfile(
72
55
  resolveRequest(context),
73
56
  resolveActionUser(context, input),
74
- input.payload,
57
+ input,
75
58
  {
76
59
  context
77
60
  }
@@ -87,8 +70,8 @@ const accountProfileActions = Object.freeze([
87
70
  permission: {
88
71
  require: "authenticated"
89
72
  },
90
- inputValidator: userProfileResource.operations.avatarUpload.bodyValidator,
91
- outputValidator: userProfileResource.operations.avatarUpload.outputValidator,
73
+ input: userProfileResource.operations.avatarUpload.body,
74
+ output: null,
92
75
  idempotency: "none",
93
76
  audit: {
94
77
  actionName: "settings.profile.avatar.upload"
@@ -121,8 +104,8 @@ const accountProfileActions = Object.freeze([
121
104
  permission: {
122
105
  require: "authenticated"
123
106
  },
124
- inputValidator: userProfileResource.operations.avatarDelete.bodyValidator,
125
- outputValidator: userProfileResource.operations.avatarDelete.outputValidator,
107
+ input: userProfileResource.operations.avatarDelete.body,
108
+ output: null,
126
109
  idempotency: "none",
127
110
  audit: {
128
111
  actionName: "settings.profile.avatar.delete"
@@ -1,4 +1,5 @@
1
1
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+ import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
2
3
  import {
3
4
  resolveUserProfile,
4
5
  resolveSecurityStatus
@@ -26,12 +27,12 @@ function createService({
26
27
  const settings = await userSettingsRepository.ensureForUserId(profile.id);
27
28
  const securityStatus = await resolveSecurityStatus(authService, request);
28
29
 
29
- return accountSettingsResponseFormatter({
30
+ return returnJsonApiData(accountSettingsResponseFormatter({
30
31
  profile,
31
32
  settings,
32
33
  securityStatus,
33
34
  authService
34
- });
35
+ }));
35
36
  }
36
37
 
37
38
  async function updateProfile(request, user, payload = {}, options = {}) {
@@ -57,12 +58,12 @@ function createService({
57
58
 
58
59
  return {
59
60
  session,
60
- settings: accountSettingsResponseFormatter({
61
+ response: returnJsonApiData(accountSettingsResponseFormatter({
61
62
  profile: updatedProfile,
62
63
  settings,
63
64
  securityStatus,
64
65
  authService
65
- })
66
+ }))
66
67
  };
67
68
  }
68
69
 
@@ -78,12 +79,12 @@ function createService({
78
79
  const settings = await userSettingsRepository.ensureForUserId(profile.id);
79
80
  const securityStatus = await resolveSecurityStatus(authService, request);
80
81
 
81
- return accountSettingsResponseFormatter({
82
+ return returnJsonApiData(accountSettingsResponseFormatter({
82
83
  profile,
83
84
  settings,
84
85
  securityStatus,
85
86
  authService
86
- });
87
+ }));
87
88
  }
88
89
 
89
90
  async function deleteAvatar(request, user, options = {}) {
@@ -93,12 +94,12 @@ function createService({
93
94
  const settings = await userSettingsRepository.ensureForUserId(profile.id);
94
95
  const securityStatus = await resolveSecurityStatus(authService, request);
95
96
 
96
- return accountSettingsResponseFormatter({
97
+ return returnJsonApiData(accountSettingsResponseFormatter({
97
98
  profile,
98
99
  settings,
99
100
  securityStatus,
100
101
  authService
101
- });
102
+ }));
102
103
  }
103
104
 
104
105
  async function readAvatar(_request, user, options = {}) {
@@ -1,8 +1,17 @@
1
1
  import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
2
+ import { createJsonApiResourceRouteContract } from "@jskit-ai/http-runtime/shared/validators/jsonApiRouteTransport";
2
3
  import { DEFAULT_IMAGE_UPLOAD_MAX_BYTES } from "@jskit-ai/uploads-runtime/shared";
3
4
  import { readSingleMultipartFile } from "@jskit-ai/uploads-runtime/server/multipart/readSingleMultipartFile";
4
5
  import { userSettingsResource } from "../../shared/resources/userSettingsResource.js";
5
6
  import { userProfileResource } from "../../shared/resources/userProfileResource.js";
7
+ import { resolveAccountSettingsResourceId } from "../common/support/accountSettingsJsonApiTransport.js";
8
+
9
+ const USER_SETTINGS_RESOURCE_TRANSPORT = Object.freeze({
10
+ responseType: "user-settings",
11
+ output: userSettingsResource.operations.view.output,
12
+ outputKind: "record",
13
+ getRecordId: resolveAccountSettingsResourceId
14
+ });
6
15
 
7
16
  function bootAccountProfileRoutes(app) {
8
17
  if (!app || typeof app.make !== "function") {
@@ -10,8 +19,6 @@ function bootAccountProfileRoutes(app) {
10
19
  }
11
20
 
12
21
  const router = app.make("jskit.http.router");
13
- const authService = app.make("authService");
14
- const accountProfileService = app.make("users.accountProfile.service");
15
22
 
16
23
  router.register(
17
24
  "GET",
@@ -22,8 +29,8 @@ function bootAccountProfileRoutes(app) {
22
29
  tags: ["settings"],
23
30
  summary: "Get authenticated user's settings"
24
31
  },
25
- responseValidators: withStandardErrorResponses({
26
- 200: userSettingsResource.operations.view.outputValidator
32
+ ...createJsonApiResourceRouteContract({
33
+ ...USER_SETTINGS_RESOURCE_TRANSPORT
27
34
  })
28
35
  },
29
36
  async function (request, reply) {
@@ -43,27 +50,25 @@ function bootAccountProfileRoutes(app) {
43
50
  tags: ["settings"],
44
51
  summary: "Update profile settings"
45
52
  },
46
- bodyValidator: userProfileResource.operations.patch.bodyValidator,
47
- responseValidators: withStandardErrorResponses(
48
- {
49
- 200: userSettingsResource.operations.view.outputValidator
50
- },
51
- { includeValidation400: true }
52
- )
53
+ ...createJsonApiResourceRouteContract({
54
+ requestType: "user-profiles",
55
+ body: userProfileResource.operations.patch.body,
56
+ includeValidation400: true,
57
+ ...USER_SETTINGS_RESOURCE_TRANSPORT
58
+ })
53
59
  },
54
60
  async function (request, reply) {
55
61
  const result = await request.executeAction({
56
62
  actionId: "settings.profile.update",
57
- input: {
58
- payload: request.input.body
59
- }
63
+ input: request.input.body
60
64
  });
61
65
 
66
+ const authService = app.make("authService");
62
67
  if (result?.session && typeof authService.writeSessionCookies === "function") {
63
68
  authService.writeSessionCookies(reply, result.session);
64
69
  }
65
70
 
66
- reply.code(200).send(result?.settings || result);
71
+ reply.code(200).send(result?.response || result);
67
72
  }
68
73
  );
69
74
 
@@ -78,6 +83,7 @@ function bootAccountProfileRoutes(app) {
78
83
  }
79
84
  },
80
85
  async function (request, reply) {
86
+ const accountProfileService = app.make("users.accountProfile.service");
81
87
  const avatar = await accountProfileService.readAvatar(request, request.user, {
82
88
  context: {
83
89
  actor: request.user
@@ -106,9 +112,9 @@ function bootAccountProfileRoutes(app) {
106
112
  consumes: ["multipart/form-data"]
107
113
  }
108
114
  },
109
- responseValidators: withStandardErrorResponses(
115
+ responses: withStandardErrorResponses(
110
116
  {
111
- 200: userProfileResource.operations.avatarUpload.outputValidator
117
+ 200: userProfileResource.operations.avatarUpload.output
112
118
  },
113
119
  { includeValidation400: true }
114
120
  )
@@ -146,8 +152,8 @@ function bootAccountProfileRoutes(app) {
146
152
  tags: ["settings"],
147
153
  summary: "Delete profile avatar and fallback to gravatar"
148
154
  },
149
- responseValidators: withStandardErrorResponses({
150
- 200: userProfileResource.operations.avatarDelete.outputValidator
155
+ ...createJsonApiResourceRouteContract({
156
+ ...USER_SETTINGS_RESOURCE_TRANSPORT
151
157
  })
152
158
  },
153
159
  async function (request, reply) {