@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
@@ -9,6 +9,8 @@ export default Object.freeze({
9
9
  "@jskit-ai/crud-core",
10
10
  "@jskit-ai/database-runtime",
11
11
  "@jskit-ai/http-runtime",
12
+ "@jskit-ai/json-rest-api-core",
13
+ "@jskit-ai/resource-crud-core",
12
14
  "@jskit-ai/users-core"
13
15
  ],
14
16
  capabilities: {
@@ -18,7 +20,8 @@ export default Object.freeze({
18
20
  requires: [
19
21
  "runtime.actions",
20
22
  "runtime.database",
21
- "auth.policy"
23
+ "auth.policy",
24
+ "json-rest-api.core"
22
25
  ]
23
26
  },
24
27
  runtime: {
@@ -34,10 +37,6 @@ export default Object.freeze({
34
37
  metadata: {
35
38
  apiSummary: {
36
39
  surfaces: [
37
- {
38
- subpath: "./server/actionIds",
39
- summary: "App-local CRUD public action identifiers."
40
- },
41
40
  {
42
41
  subpath: "./shared",
43
42
  summary: "App-local CRUD shared resource."
@@ -4,7 +4,6 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
7
- "./server/actionIds": "./src/server/actionIds.js",
8
7
  "./shared": "./src/shared/index.js"
9
8
  }
10
9
  }
@@ -1,17 +1,17 @@
1
1
  import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
2
2
  import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
3
3
  import {
4
- createCrudLookupResolver,
5
- createCrudLookup
6
- } from "@jskit-ai/crud-core/server/lookups";
4
+ INTERNAL_JSON_REST_API,
5
+ addResourceIfMissing,
6
+ createJsonRestResourceScopeOptions
7
+ } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
7
8
  import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
9
+ import { toDatabaseDateTimeUtc } from "@jskit-ai/database-runtime/shared";
8
10
  import { createRepository } from "./repository.js";
9
- import {
10
- createService,
11
- serviceEvents
12
- } from "./service.js";
11
+ import { createService } from "./service.js";
13
12
  import { createActions } from "./actions.js";
14
13
  import { registerRoutes } from "./registerRoutes.js";
14
+ import { resource } from "../shared/userResource.js";
15
15
 
16
16
  const CRUD_MODULE_CONFIG = Object.freeze({
17
17
  namespace: "users",
@@ -29,25 +29,17 @@ function resolveCrudPolicyFromApp(app) {
29
29
  class UsersProvider {
30
30
  static id = "crud.users";
31
31
 
32
- static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main"];
32
+ static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main", "json-rest-api.core"];
33
33
 
34
34
  register(app) {
35
- if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
36
- throw new Error("UsersProvider requires application singleton()/service()/actions().");
37
- }
38
-
39
35
  const crudPolicy = resolveCrudPolicyFromApp(app);
40
36
 
41
37
  app.singleton("repository.users", (scope) => {
38
+ const api = scope.make(INTERNAL_JSON_REST_API);
42
39
  const knex = scope.make("jskit.database.knex");
43
- return createRepository(knex, {
44
- resolveLookup: createCrudLookupResolver(scope)
45
- });
46
- });
47
-
48
- app.singleton("lookup.users", (scope) => {
49
- return createCrudLookup(scope.make("repository.users"), {
50
- ownershipFilter: crudPolicy.ownershipFilter
40
+ return createRepository({
41
+ api,
42
+ knex
51
43
  });
52
44
  });
53
45
 
@@ -57,9 +49,6 @@ class UsersProvider {
57
49
  return createService({
58
50
  usersRepository: scope.make("repository.users")
59
51
  });
60
- },
61
- {
62
- events: serviceEvents
63
52
  }
64
53
  );
65
54
 
@@ -78,8 +67,18 @@ class UsersProvider {
78
67
  );
79
68
  }
80
69
 
81
- boot(app) {
70
+ async boot(app) {
82
71
  const crudPolicy = resolveCrudPolicyFromApp(app);
72
+ const api = app.make(INTERNAL_JSON_REST_API);
73
+ await addResourceIfMissing(
74
+ api,
75
+ "users",
76
+ createJsonRestResourceScopeOptions(resource, {
77
+ writeSerializers: {
78
+ "datetime-utc": toDatabaseDateTimeUtc
79
+ }
80
+ })
81
+ );
83
82
  registerRoutes(app, {
84
83
  routeOwnershipFilter: crudPolicy.ownershipFilter,
85
84
  routeSurface: crudPolicy.surfaceId,
@@ -1,67 +1,65 @@
1
- import { recordIdParamsValidator } from "@jskit-ai/kernel/shared/validators";
1
+ import {
2
+ composeSchemaDefinitions,
3
+ recordIdParamsValidator
4
+ } from "@jskit-ai/kernel/shared/validators";
2
5
  import {
3
6
  createCrudCursorPaginationQueryValidator,
4
7
  listSearchQueryValidator
5
8
  } from "@jskit-ai/crud-core/server/listQueryValidators";
6
9
  import { resource } from "../shared/userResource.js";
7
- import { actionIds } from "./actionIds.js";
8
- import { LIST_CONFIG } from "./listConfig.js";
9
10
 
10
- const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator(LIST_CONFIG);
11
+ const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator({
12
+ orderBy: resource.defaultSort
13
+ });
11
14
  const authenticatedPermission = Object.freeze({
12
15
  require: "authenticated"
13
16
  });
14
17
 
15
- function requireActionSurface(surface = "") {
16
- const normalizedSurface = String(surface || "").trim().toLowerCase();
17
- if (!normalizedSurface) {
18
- throw new TypeError("createActions requires a non-empty surface.");
19
- }
20
-
21
- return normalizedSurface;
22
- }
23
-
24
- function createActions({ surface = "" } = {}) {
25
- const actionSurface = requireActionSurface(surface);
26
-
18
+ function createActions({ surface } = {}) {
27
19
  return Object.freeze([
28
20
  {
29
- id: actionIds.list,
21
+ id: "crud.users.list",
30
22
  version: 1,
31
23
  kind: "query",
32
24
  channels: ["api", "automation", "internal"],
33
- surfaces: [actionSurface],
25
+ surfaces: [surface],
34
26
  permission: authenticatedPermission,
35
- inputValidator: [listCursorPaginationQueryValidator, listSearchQueryValidator],
36
- outputValidator: resource.operations.list.outputValidator,
27
+ input: composeSchemaDefinitions([
28
+ listCursorPaginationQueryValidator,
29
+ listSearchQueryValidator
30
+ ]),
31
+ output: null,
37
32
  idempotency: "none",
38
33
  audit: {
39
- actionName: actionIds.list
34
+ actionName: "crud.users.list"
40
35
  },
41
36
  observability: {},
42
37
  async execute(input, context, deps) {
43
- return deps.usersService.listRecords(input, {
38
+ const { workspaceSlug, ...query } = input || {};
39
+ return deps.usersService.queryDocuments(query, {
44
40
  context,
45
41
  visibilityContext: context?.visibilityContext
46
42
  });
47
43
  }
48
44
  },
49
45
  {
50
- id: actionIds.view,
46
+ id: "crud.users.view",
51
47
  version: 1,
52
48
  kind: "query",
53
49
  channels: ["api", "automation", "internal"],
54
- surfaces: [actionSurface],
50
+ surfaces: [surface],
55
51
  permission: authenticatedPermission,
56
- inputValidator: recordIdParamsValidator,
57
- outputValidator: resource.operations.view.outputValidator,
52
+ input: composeSchemaDefinitions([
53
+ recordIdParamsValidator
54
+ ]),
55
+ output: null,
58
56
  idempotency: "none",
59
57
  audit: {
60
- actionName: actionIds.view
58
+ actionName: "crud.users.view"
61
59
  },
62
60
  observability: {},
63
61
  async execute(input, context, deps) {
64
- return deps.usersService.getRecord(input.recordId, {
62
+ return deps.usersService.getDocumentById(input.recordId, {
65
63
  context,
66
64
  visibilityContext: context?.visibilityContext
67
65
  });
@@ -1,4 +1,4 @@
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 { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
3
3
  import {
4
4
  createCrudCursorPaginationQueryValidator,
@@ -6,12 +6,31 @@ import {
6
6
  } from "@jskit-ai/crud-core/server/listQueryValidators";
7
7
  import { resolveScopedApiBasePath } from "@jskit-ai/kernel/shared/surface";
8
8
  import { checkRouteVisibility } from "@jskit-ai/kernel/shared/support/visibility";
9
- import { recordIdParamsValidator } from "@jskit-ai/kernel/shared/validators";
10
- import { actionIds } from "./actionIds.js";
11
- import { LIST_CONFIG } from "./listConfig.js";
9
+ import {
10
+ composeSchemaDefinitions,
11
+ recordIdParamsValidator
12
+ } from "@jskit-ai/kernel/shared/validators";
12
13
  import { resource } from "../shared/userResource.js";
13
14
 
14
- const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator(LIST_CONFIG);
15
+ const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator({
16
+ orderBy: resource.defaultSort
17
+ });
18
+ const listRouteQueryValidator = composeSchemaDefinitions([
19
+ listCursorPaginationQueryValidator,
20
+ listSearchQueryValidator
21
+ ]);
22
+ const RESOURCE_ROUTE_CONTRACT_TYPE = resource.namespace;
23
+ const listRouteContract = createJsonApiResourceRouteContract({
24
+ type: RESOURCE_ROUTE_CONTRACT_TYPE,
25
+ query: listRouteQueryValidator,
26
+ output: resource.operations.view.output,
27
+ outputKind: "collection"
28
+ });
29
+ const viewRouteContract = createJsonApiResourceRouteContract({
30
+ type: RESOURCE_ROUTE_CONTRACT_TYPE,
31
+ output: resource.operations.view.output,
32
+ outputKind: "record"
33
+ });
15
34
 
16
35
  function registerRoutes(
17
36
  app,
@@ -40,14 +59,11 @@ function registerRoutes(
40
59
  tags: ["crud"],
41
60
  summary: "List users."
42
61
  },
43
- queryValidator: [listCursorPaginationQueryValidator, listSearchQueryValidator],
44
- responseValidators: withStandardErrorResponses({
45
- 200: resource.operations.list.outputValidator
46
- })
62
+ ...listRouteContract
47
63
  },
48
64
  async function (request, reply) {
49
65
  const response = await request.executeAction({
50
- actionId: actionIds.list,
66
+ actionId: "crud.users.list",
51
67
  input: {
52
68
  ...(request.input.query || {})
53
69
  }
@@ -67,14 +83,12 @@ function registerRoutes(
67
83
  tags: ["crud"],
68
84
  summary: "View a user."
69
85
  },
70
- paramsValidator: recordIdParamsValidator,
71
- responseValidators: withStandardErrorResponses({
72
- 200: resource.operations.view.outputValidator
73
- })
86
+ ...viewRouteContract,
87
+ params: recordIdParamsValidator
74
88
  },
75
89
  async function (request, reply) {
76
90
  const response = await request.executeAction({
77
- actionId: actionIds.view,
91
+ actionId: "crud.users.view",
78
92
  input: {
79
93
  recordId: request.input.params.recordId
80
94
  }
@@ -1,40 +1,47 @@
1
- import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
1
+ import { createWithTransaction } from "@jskit-ai/database-runtime/shared";
2
+ import {
3
+ buildJsonRestQueryParams,
4
+ createJsonRestContext,
5
+ returnNullWhenJsonRestResourceMissing
6
+ } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
2
7
  import { resource } from "../shared/userResource.js";
3
- import { LIST_CONFIG } from "./listConfig.js";
4
8
 
5
- const REPOSITORY_CONFIG = Object.freeze({
6
- context: "users repository",
7
- list: LIST_CONFIG
8
- });
9
+ const RESOURCE_TYPE = resource.namespace;
9
10
 
10
- function createRepository(knex, options = {}) {
11
- const resourceRuntime = createCrudResourceRuntime(resource, knex, {
12
- ...options,
13
- ...REPOSITORY_CONFIG
14
- });
15
-
16
- async function list(query = {}, callOptions = {}) {
17
- return resourceRuntime.list(query, callOptions);
18
- }
19
-
20
- async function findById(recordId, callOptions = {}) {
21
- return resourceRuntime.findById(recordId, callOptions);
22
- }
11
+ function createRepository({ api, knex } = {}) {
12
+ const withTransaction = createWithTransaction(knex);
23
13
 
24
- async function listByIds(ids = [], callOptions = {}) {
25
- return resourceRuntime.listByIds(ids, callOptions);
14
+ async function queryDocuments(query = {}, options = {}) {
15
+ return api.resources.users.query(
16
+ {
17
+ queryParams: buildJsonRestQueryParams(RESOURCE_TYPE, query),
18
+ transaction: options?.trx || null,
19
+ simplified: false
20
+ },
21
+ createJsonRestContext(options?.context || null)
22
+ );
26
23
  }
27
24
 
28
- async function listByForeignIds(ids = [], foreignKey = "", callOptions = {}) {
29
- return resourceRuntime.listByForeignIds(ids, foreignKey, callOptions);
25
+ async function getDocumentById(recordId, options = {}) {
26
+ return returnNullWhenJsonRestResourceMissing(() =>
27
+ api.resources.users.get(
28
+ {
29
+ id: recordId,
30
+ queryParams: buildJsonRestQueryParams(RESOURCE_TYPE, {}, {
31
+ include: options?.include
32
+ }),
33
+ transaction: options?.trx || null,
34
+ simplified: false
35
+ },
36
+ createJsonRestContext(options?.context || null)
37
+ )
38
+ );
30
39
  }
31
40
 
32
41
  return Object.freeze({
33
- withTransaction: resourceRuntime.withTransaction,
34
- list,
35
- findById,
36
- listByIds,
37
- listByForeignIds
42
+ withTransaction,
43
+ queryDocuments,
44
+ getDocumentById
38
45
  });
39
46
  }
40
47
 
@@ -1,14 +1,12 @@
1
- import {
2
- createCrudServiceRuntime,
3
- crudServiceListRecords,
4
- crudServiceGetRecord
5
- } from "@jskit-ai/crud-core/server/serviceMethods";
6
- import { resource } from "../shared/userResource.js";
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+ import { returnJsonApiDocument } from "@jskit-ai/http-runtime/shared";
7
3
 
8
- const serviceRuntime = createCrudServiceRuntime(resource, {
9
- context: "usersService"
10
- });
11
- const serviceEvents = Object.freeze({});
4
+ function return404IfNotFound(document = null) {
5
+ if (!document) {
6
+ throw new AppError(404, "Document not found.");
7
+ }
8
+ return document;
9
+ }
12
10
 
13
11
  function createService({ usersRepository } = {}) {
14
12
  if (!usersRepository) {
@@ -16,13 +14,20 @@ function createService({ usersRepository } = {}) {
16
14
  }
17
15
 
18
16
  return Object.freeze({
19
- listRecords(query = {}, options = {}) {
20
- return crudServiceListRecords(serviceRuntime, usersRepository, {}, query, options);
17
+ async queryDocuments(query = {}, options = {}) {
18
+ return returnJsonApiDocument(await usersRepository.queryDocuments(query, {
19
+ trx: options?.trx || null,
20
+ context: options?.context || null
21
+ }));
21
22
  },
22
- getRecord(recordId, options = {}) {
23
- return crudServiceGetRecord(serviceRuntime, usersRepository, {}, recordId, options);
23
+ async getDocumentById(recordId, options = {}) {
24
+ return returnJsonApiDocument(return404IfNotFound(await usersRepository.getDocumentById(recordId, {
25
+ trx: options?.trx || null,
26
+ context: options?.context || null,
27
+ include: options?.include
28
+ })));
24
29
  }
25
30
  });
26
31
  }
27
32
 
28
- export { createService, serviceEvents };
33
+ export { createService };
@@ -1,74 +1,61 @@
1
- import { Type } from "typebox";
2
- import { toIsoString } from "@jskit-ai/database-runtime/shared";
3
- import {
4
- createCursorListValidator,
5
- normalizeObjectInput,
6
- recordIdSchema
7
- } from "@jskit-ai/kernel/shared/validators";
8
- import {
9
- normalizeIfPresent,
10
- normalizeRecordId,
11
- normalizeText
12
- } from "@jskit-ai/kernel/shared/support/normalize";
1
+ import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
13
2
 
14
- const recordOutputSchema = Type.Object(
15
- {
16
- id: recordIdSchema,
17
- name: Type.String({ minLength: 1 }),
18
- email: Type.String(),
19
- username: Type.String(),
20
- createdAt: Type.String({ format: "date-time", minLength: 1 })
21
- },
22
- { additionalProperties: false }
23
- );
24
-
25
- const createBodySchema = Type.Object({}, { additionalProperties: false });
26
-
27
- const recordOutputValidator = Object.freeze({
28
- schema: recordOutputSchema,
29
- normalize(payload = {}) {
30
- const source = normalizeObjectInput(payload);
31
-
32
- return {
33
- id: normalizeIfPresent(source.id, normalizeRecordId),
34
- name: normalizeText(source.name || source.email || source.username || source.id),
35
- email: normalizeText(source.email),
36
- username: normalizeText(source.username),
37
- createdAt: normalizeIfPresent(source.createdAt, toIsoString)
38
- };
39
- }
40
- });
41
-
42
- const createBodyValidator = Object.freeze({
43
- schema: createBodySchema,
44
- normalize: normalizeObjectInput
45
- });
46
-
47
- const resource = Object.freeze({
3
+ const resource = defineCrudResource({
48
4
  namespace: "users",
49
5
  tableName: "users",
50
- idColumn: "id",
51
- operations: Object.freeze({
52
- list: Object.freeze({
53
- method: "GET",
54
- outputValidator: createCursorListValidator(recordOutputValidator)
55
- }),
56
- view: Object.freeze({
57
- method: "GET",
58
- outputValidator: recordOutputValidator
59
- }),
60
- create: Object.freeze({
61
- method: "POST",
62
- bodyValidator: createBodyValidator,
63
- outputValidator: recordOutputValidator
64
- })
65
- }),
66
- fieldMeta: Object.freeze([
67
- Object.freeze({
68
- key: "name",
69
- repository: { column: "display_name" }
70
- })
71
- ])
6
+ crudOperations: ["list", "view", "create"],
7
+ schema: {
8
+ name: {
9
+ type: "string",
10
+ required: true,
11
+ minLength: 1,
12
+ maxLength: 160,
13
+ actualField: "display_name",
14
+ search: true,
15
+ operations: {
16
+ output: { required: false }
17
+ }
18
+ },
19
+ email: {
20
+ type: "string",
21
+ required: true,
22
+ search: true,
23
+ operations: {
24
+ output: { required: false }
25
+ }
26
+ },
27
+ username: {
28
+ type: "string",
29
+ required: true,
30
+ search: true,
31
+ operations: {
32
+ output: { required: false }
33
+ }
34
+ },
35
+ createdAt: {
36
+ type: "dateTime",
37
+ required: true,
38
+ storage: { writeSerializer: "datetime-utc" },
39
+ operations: {
40
+ output: { required: true }
41
+ }
42
+ }
43
+ },
44
+ searchSchema: {
45
+ id: {
46
+ type: "id",
47
+ actualField: "id"
48
+ },
49
+ q: {
50
+ type: "string",
51
+ oneOf: ["name", "email", "username"],
52
+ filterOperator: "like",
53
+ splitBy: " ",
54
+ matchAll: true
55
+ }
56
+ },
57
+ defaultSort: ["name", "email"],
58
+ autofilter: "public"
72
59
  });
73
60
 
74
61
  export { resource };
@@ -9,6 +9,8 @@ export default Object.freeze({
9
9
  "@jskit-ai/crud-core",
10
10
  "@jskit-ai/database-runtime",
11
11
  "@jskit-ai/http-runtime",
12
+ "@jskit-ai/json-rest-api-core",
13
+ "@jskit-ai/resource-crud-core",
12
14
  "@jskit-ai/users-core",
13
15
  "@jskit-ai/workspaces-core"
14
16
  ],
@@ -19,7 +21,8 @@ export default Object.freeze({
19
21
  requires: [
20
22
  "runtime.actions",
21
23
  "runtime.database",
22
- "auth.policy"
24
+ "auth.policy",
25
+ "json-rest-api.core"
23
26
  ]
24
27
  },
25
28
  runtime: {
@@ -35,10 +38,6 @@ export default Object.freeze({
35
38
  metadata: {
36
39
  apiSummary: {
37
40
  surfaces: [
38
- {
39
- subpath: "./server/actionIds",
40
- summary: "App-local CRUD public action identifiers."
41
- },
42
41
  {
43
42
  subpath: "./shared",
44
43
  summary: "App-local CRUD shared resource."
@@ -1,17 +1,17 @@
1
1
  import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
2
2
  import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
3
3
  import {
4
- createCrudLookupResolver,
5
- createCrudLookup
6
- } from "@jskit-ai/crud-core/server/lookups";
4
+ INTERNAL_JSON_REST_API,
5
+ addResourceIfMissing,
6
+ createJsonRestResourceScopeOptions
7
+ } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
7
8
  import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
9
+ import { toDatabaseDateTimeUtc } from "@jskit-ai/database-runtime/shared";
8
10
  import { createRepository } from "./repository.js";
9
- import {
10
- createService,
11
- serviceEvents
12
- } from "./service.js";
11
+ import { createService } from "./service.js";
13
12
  import { createActions } from "./actions.js";
14
13
  import { registerRoutes } from "./registerRoutes.js";
14
+ import { resource } from "../shared/userResource.js";
15
15
 
16
16
  const CRUD_MODULE_CONFIG = Object.freeze({
17
17
  namespace: "users",
@@ -29,25 +29,17 @@ function resolveCrudPolicyFromApp(app) {
29
29
  class UsersProvider {
30
30
  static id = "crud.users";
31
31
 
32
- static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main"];
32
+ static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main", "json-rest-api.core"];
33
33
 
34
34
  register(app) {
35
- if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
36
- throw new Error("UsersProvider requires application singleton()/service()/actions().");
37
- }
38
-
39
35
  const crudPolicy = resolveCrudPolicyFromApp(app);
40
36
 
41
37
  app.singleton("repository.users", (scope) => {
38
+ const api = scope.make(INTERNAL_JSON_REST_API);
42
39
  const knex = scope.make("jskit.database.knex");
43
- return createRepository(knex, {
44
- resolveLookup: createCrudLookupResolver(scope)
45
- });
46
- });
47
-
48
- app.singleton("lookup.users", (scope) => {
49
- return createCrudLookup(scope.make("repository.users"), {
50
- ownershipFilter: crudPolicy.ownershipFilter
40
+ return createRepository({
41
+ api,
42
+ knex
51
43
  });
52
44
  });
53
45
 
@@ -57,9 +49,6 @@ class UsersProvider {
57
49
  return createService({
58
50
  usersRepository: scope.make("repository.users")
59
51
  });
60
- },
61
- {
62
- events: serviceEvents
63
52
  }
64
53
  );
65
54
 
@@ -78,8 +67,18 @@ class UsersProvider {
78
67
  );
79
68
  }
80
69
 
81
- boot(app) {
70
+ async boot(app) {
82
71
  const crudPolicy = resolveCrudPolicyFromApp(app);
72
+ const api = app.make(INTERNAL_JSON_REST_API);
73
+ await addResourceIfMissing(
74
+ api,
75
+ "users",
76
+ createJsonRestResourceScopeOptions(resource, {
77
+ writeSerializers: {
78
+ "datetime-utc": toDatabaseDateTimeUtc
79
+ }
80
+ })
81
+ );
83
82
  registerRoutes(app, {
84
83
  routeOwnershipFilter: crudPolicy.ownershipFilter,
85
84
  routeSurface: crudPolicy.surfaceId,