@jskit-ai/users-core 0.1.172 → 0.1.173

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 (58) hide show
  1. package/package.json +41 -253
  2. package/patterns/user-administration-server/PATTERN.md +64 -0
  3. package/patterns/user-administration-server/example/packages/users/package.json +42 -0
  4. package/patterns/user-administration-server/example/packages/users/src/server/UsersFeature.js +13 -0
  5. package/patterns/user-administration-server/example/packages/users/src/shared/index.js +1 -0
  6. package/patterns/user-administration-server/example/packages/users-workspace/package.json +43 -0
  7. package/patterns/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +22 -0
  8. package/patterns/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +56 -0
  9. package/src/server/UsersExtensionsProvider.js +14 -0
  10. package/src/server/UsersFeature.js +119 -0
  11. package/src/server/UsersIdentityProvider.js +57 -0
  12. package/src/server/accountNotifications/accountNotificationsActions.js +17 -5
  13. package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +4 -6
  14. package/src/server/accountPreferences/accountPreferencesActions.js +17 -5
  15. package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +4 -6
  16. package/src/server/accountProfile/accountProfileActions.js +28 -14
  17. package/src/server/accountProfile/bootAccountProfileRoutes.js +11 -10
  18. package/src/server/accountSecurity/accountSecurityActions.js +27 -17
  19. package/src/server/accountSecurity/bootAccountSecurityRoutes.js +4 -7
  20. package/src/server/common/services/authProfileSyncService.js +11 -4
  21. package/src/server/common/support/realtimeServiceEvents.js +8 -11
  22. package/src/server/usersBootstrapContributor.js +4 -28
  23. package/src/server/usersExtensions.js +65 -0
  24. package/test/authProfileSyncService.test.js +2 -2
  25. package/test/exportsContract.test.js +6 -1
  26. package/test/featureRuntime.test.js +78 -0
  27. package/test/usersBootstrapContributor.test.js +1 -43
  28. package/test/usersPackageScaffoldContract.test.js +58 -204
  29. package/test/usersRouteRequestInputValidator.test.js +19 -43
  30. package/src/server/UsersCoreServiceProvider.js +0 -56
  31. package/src/server/accountNotifications/registerAccountNotifications.js +0 -37
  32. package/src/server/accountPreferences/registerAccountPreferences.js +0 -37
  33. package/src/server/accountProfile/registerAccountProfile.js +0 -56
  34. package/src/server/accountSecurity/registerAccountSecurity.js +0 -29
  35. package/src/server/common/registerCommonRepositories.js +0 -23
  36. package/src/server/common/registerSharedApi.js +0 -9
  37. package/src/server/profileSyncLifecycleContributorRegistry.js +0 -56
  38. package/src/server/registerUsersBootstrap.js +0 -20
  39. package/src/server/registerUsersCore.js +0 -22
  40. package/templates/packages/users/package.json +0 -82
  41. package/templates/packages/users/src/server/UsersProvider.js +0 -90
  42. package/templates/packages/users/src/server/actions.js +0 -68
  43. package/templates/packages/users/src/server/registerRoutes.js +0 -101
  44. package/templates/packages/users/src/server/repository.js +0 -51
  45. package/templates/packages/users/src/server/service.js +0 -32
  46. package/templates/packages/users-workspace/package.json +0 -83
  47. package/templates/packages/users-workspace/src/server/UsersProvider.js +0 -91
  48. package/templates/packages/users-workspace/src/server/actions.js +0 -77
  49. package/templates/packages/users-workspace/src/server/registerRoutes.js +0 -111
  50. package/test/providerLifecycle.test.js +0 -63
  51. package/test/registerCommonRepositories.test.js +0 -57
  52. package/test/registerServiceRealtimeEvents.test.js +0 -57
  53. package/test/registerUsersCore.test.js +0 -53
  54. /package/{templates/migrations → migrations}/users_core_generic_initial.cjs +0 -0
  55. /package/{templates/migrations → migrations}/users_core_profile_updated_at.cjs +0 -0
  56. /package/{templates/migrations → migrations}/users_core_profile_username.cjs +0 -0
  57. /package/{templates → patterns/user-administration-server/example}/packages/users/src/shared/userResource.js +0 -0
  58. /package/{templates/packages/users → patterns/user-administration-server/example/packages/users-workspace}/src/shared/index.js +0 -0
@@ -5,223 +5,77 @@ import test from "node:test";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import packageJson from "../package.json" with { type: "json" };
7
7
 
8
- const packageMetadata = packageJson.jskit;
9
- import crudCorePackage from "../../crud-core/package.json" with { type: "json" };
10
- import resourceCrudCorePackage from "../../resource-crud-core/package.json" with { type: "json" };
11
-
12
- const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
13
- const PACKAGE_ROOT = path.resolve(TEST_DIRECTORY, "..");
14
-
15
- function readFileMutationById(id) {
16
- return packageMetadata.mutations.files.find((entry) => entry.id === id) || null;
17
- }
18
-
19
- test("users-core installs the app-local users package scaffold", () => {
20
- assert.equal(packageMetadata.mutations.dependencies.runtime["@local/users"], "file:packages/users");
21
- assert.equal(packageMetadata.mutations.dependencies.runtime["@jskit-ai/crud-core"], crudCorePackage.version);
22
- assert.equal(
23
- packageMetadata.mutations.dependencies.runtime["@jskit-ai/resource-crud-core"],
24
- resourceCrudCorePackage.version
25
- );
26
-
27
- const expectedFileIds = [
28
- "users-core-users-package-json-base",
29
- "users-core-users-package-json-workspace",
30
- "users-core-users-provider-base",
31
- "users-core-users-provider-workspace",
32
- "users-core-users-actions-base",
33
- "users-core-users-actions-workspace",
34
- "users-core-users-routes-base",
35
- "users-core-users-routes-workspace",
36
- "users-core-users-repository",
37
- "users-core-users-service",
38
- "users-core-users-shared-index",
39
- "users-core-users-resource"
40
- ];
41
-
42
- for (const fileId of expectedFileIds) {
43
- const mutation = readFileMutationById(fileId);
44
- assert.ok(mutation, `Missing users-core scaffold file mutation ${fileId}.`);
45
- assert.equal(mutation.ownership, "app", `${fileId} must remain app-owned.`);
46
- assert.equal(mutation.preserveOnRemove, true, `${fileId} must remain preserved on remove.`);
47
- assert.ok(mutation.to.startsWith("packages/users/"), `${fileId} must target packages/users.`);
48
- assert.ok(mutation.from, `${fileId} must define a template source.`);
49
- assert.ok(mutation.reason, `${fileId} must document why it exists.`);
50
- }
8
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ const patternRoot = path.join(packageRoot, "patterns", "user-administration-server");
10
+
11
+ test("users-core publishes agent-readable administration patterns and package-owned migrations", async () => {
12
+ assert.equal(packageJson.jskit?.mutations, undefined);
13
+ assert.deepEqual(packageJson.jskit?.migrations, { directories: ["migrations"] });
14
+
15
+ const pattern = await readFile(path.join(patternRoot, "PATTERN.md"), "utf8");
16
+ assert.match(pattern, /defineCrudJsonApiFeature\(\)/);
17
+ assert.match(pattern, /Workspace operations are scoped before repository access/);
18
+ assert.match(pattern, /null\/cleared fields/);
19
+ assert.doesNotMatch(pattern, /createCrudJsonApiModule/);
51
20
  });
52
21
 
53
- test("users-core installs all users profile schema migrations", async () => {
54
- const expectedMigrationIds = [
55
- "users-core-generic-initial-schema",
56
- "users-core-profile-username-schema",
57
- "users-core-profile-updated-at-schema"
58
- ];
59
-
60
- for (const migrationId of expectedMigrationIds) {
61
- const mutation = readFileMutationById(migrationId);
62
- assert.ok(mutation, `Missing users-core migration mutation ${migrationId}.`);
63
- assert.equal(mutation.op, "install-migration", `${migrationId} must install a migration.`);
64
- assert.equal(mutation.toDir, "migrations", `${migrationId} must target app migrations.`);
65
- assert.equal(mutation.category, "migration", `${migrationId} must be categorized as a migration.`);
66
- assert.ok(mutation.from, `${migrationId} must define a migration template source.`);
67
- assert.ok(mutation.reason, `${migrationId} must document why it exists.`);
68
- }
69
-
70
- const updatedAtMigrationSource = await readFile(
71
- path.join(PACKAGE_ROOT, "templates/migrations/users_core_profile_updated_at.cjs"),
22
+ test("global user administration example is one declarative CRUD feature", async () => {
23
+ const packageManifest = JSON.parse(await readFile(
24
+ path.join(patternRoot, "example", "packages", "users", "package.json"),
72
25
  "utf8"
73
- );
74
- const initialMigrationSource = await readFile(
75
- path.join(PACKAGE_ROOT, "templates/migrations/users_core_generic_initial.cjs"),
26
+ ));
27
+ const feature = await readFile(
28
+ path.join(patternRoot, "example", "packages", "users", "src", "server", "UsersFeature.js"),
76
29
  "utf8"
77
30
  );
78
- const usersTableSection = initialMigrationSource.split("const hasUserSettingsTable")[0] || "";
79
31
 
80
- assert.doesNotMatch(usersTableSection, /table\.timestamp\("updated_at"/);
81
- assert.match(updatedAtMigrationSource, /hasColumn\("users", "updated_at"\)/);
82
- assert.match(updatedAtMigrationSource, /COALESCE\(\?\?, CURRENT_TIMESTAMP\)/);
32
+ assert.equal(packageManifest.name, "@app/users");
33
+ assert.equal(packageManifest.jskit?.mutations, undefined);
34
+ assert.deepEqual(packageManifest.jskit?.capabilities?.provides, ["app.users"]);
35
+ assert.match(feature, /defineCrudJsonApiFeature/);
36
+ assert.match(feature, /surface: "home"/);
37
+ assert.match(feature, /ownershipFilter: "public"/);
38
+ assert.doesNotMatch(feature, /\.make\(|\.singleton\(|containerToken|repositoryToken|serviceToken/);
83
39
  });
84
40
 
85
- test("users-core base users package templates stay aligned with non-workspace apps", async () => {
86
- const packageManifestSource = await readFile(
87
- path.join(PACKAGE_ROOT, "templates/packages/users/package.json"),
41
+ test("workspace user administration example makes route and action scope explicit", async () => {
42
+ const packageManifest = JSON.parse(await readFile(
43
+ path.join(patternRoot, "example", "packages", "users-workspace", "package.json"),
88
44
  "utf8"
89
- );
90
- const providerSource = await readFile(
91
- path.join(PACKAGE_ROOT, "templates/packages/users/src/server/UsersProvider.js"),
92
- "utf8"
93
- );
94
- const actionsSource = await readFile(
95
- path.join(PACKAGE_ROOT, "templates/packages/users/src/server/actions.js"),
96
- "utf8"
97
- );
98
- const repositorySource = await readFile(
99
- path.join(PACKAGE_ROOT, "templates/packages/users/src/server/repository.js"),
100
- "utf8"
101
- );
102
- const serviceSource = await readFile(
103
- path.join(PACKAGE_ROOT, "templates/packages/users/src/server/service.js"),
104
- "utf8"
105
- );
106
- const routesSource = await readFile(
107
- path.join(PACKAGE_ROOT, "templates/packages/users/src/server/registerRoutes.js"),
45
+ ));
46
+ const feature = await readFile(
47
+ path.join(
48
+ patternRoot,
49
+ "example",
50
+ "packages",
51
+ "users-workspace",
52
+ "src",
53
+ "server",
54
+ "UsersWorkspaceFeature.js"
55
+ ),
108
56
  "utf8"
109
57
  );
110
58
 
111
- assert.doesNotMatch(packageManifestSource, /@jskit-ai\/workspaces-core/);
112
- assert.match(packageManifestSource, /@jskit-ai\/json-rest-api-core/);
113
- assert.match(packageManifestSource, /json-rest-api\.core/);
114
- assert.match(packageManifestSource, /"scaffoldShape": "users-core-crud-v1"/);
115
- assert.match(packageManifestSource, /"tableName": "users"/);
116
- assert.match(packageManifestSource, /"provenance": "users-core-template"/);
117
- assert.doesNotMatch(packageManifestSource, /server\/actionIds/);
118
- assert.match(providerSource, /surface: "home"/);
119
- assert.doesNotMatch(providerSource, /routeSurfaceRequiresWorkspace/);
120
- assert.doesNotMatch(providerSource, /createCrudLookup/);
121
- assert.doesNotMatch(providerSource, /lookup\.users/);
122
- assert.doesNotMatch(providerSource, /normalizeRecordId/);
123
- assert.doesNotMatch(providerSource, /requires application singleton\(\)\/service\(\)\/actions\(\)\./);
124
- assert.match(providerSource, /createJsonRestResourceScopeOptions/);
125
- assert.match(providerSource, /addResourceIfMissing\(\s*api,\s*"users",\s*createJsonRestResourceScopeOptions\(resource,/s);
126
- assert.match(repositorySource, /api\.resources\.users\.query\(/);
127
- assert.match(repositorySource, /api\.resources\.users\.get\(/);
128
- assert.match(repositorySource, /async function queryDocuments\(query = \{\}, options = \{\}\)/);
129
- assert.match(repositorySource, /async function getDocumentById\(recordId, query = \{\}, options = \{\}\)/);
130
- assert.match(repositorySource, /buildJsonRestQueryParams\(RESOURCE_TYPE, query\)/);
131
- assert.match(repositorySource, /returnBadRequestWhenJsonRestFieldsetInvalid/);
132
- assert.match(repositorySource, /returnNullWhenJsonRestResourceMissing/);
133
- assert.doesNotMatch(actionsSource, /workspaceSlugParamsValidator/);
134
- assert.doesNotMatch(actionsSource, /delete query\.workspaceSlug/);
135
- assert.doesNotMatch(actionsSource, /requireActionSurface/);
136
- assert.match(actionsSource, /createStandardCrudListQueryValidators\(\{ resource \}\)/);
137
- assert.match(actionsSource, /createStandardCrudViewQueryValidators\(\)/);
138
- assert.match(actionsSource, /const \{ recordId, \.\.\.query \} = input \|\| \{\};/);
139
- assert.match(actionsSource, /usersService\.getDocumentById\(recordId, query,/);
140
- assert.match(actionsSource, /output: null/);
141
- assert.match(actionsSource, /usersService\.queryDocuments/);
142
- assert.match(actionsSource, /usersService\.getDocumentById/);
143
- assert.doesNotMatch(actionsSource, /from "\.\/actionIds\.js"/);
144
- assert.match(actionsSource, /id: "crud\.users\.list"/);
145
- assert.match(actionsSource, /id: "crud\.users\.view"/);
146
- assert.doesNotMatch(serviceSource, /serviceEvents/);
147
- assert.match(serviceSource, /throw new TypeError\("createService requires usersRepository\."\);/);
148
- assert.match(serviceSource, /return404IfNotFound/);
149
- assert.match(serviceSource, /throw new AppError\(404, "Document not found\."\);/);
150
- assert.match(serviceSource, /returnJsonApiDocument/);
151
- assert.doesNotMatch(routesSource, /workspaceRouteInput/);
152
- assert.match(routesSource, /createJsonApiResourceRouteContract/);
153
- assert.doesNotMatch(routesSource, /wrapResponse/);
154
- assert.match(routesSource, /routeBase: "\/"/);
155
- assert.match(routesSource, /orderBy: resource\.defaultSort/);
59
+ assert.equal(packageManifest.name, "@app/users-workspace");
60
+ assert.equal(packageManifest.jskit?.mutations, undefined);
61
+ assert.deepEqual(packageManifest.jskit?.capabilities?.provides, ["app.users-workspace"]);
62
+ assert.match(feature, /ownershipFilter: "workspace"/);
63
+ assert.match(feature, /routeBase: "\/w\/:workspaceSlug"/);
64
+ assert.match(feature, /actionInputValidator: workspaceSlugParamsValidator/);
65
+ assert.match(feature, /inputKeys: \["workspaceSlug"\]/);
66
+ assert.match(feature, /buildWorkspaceInputFromRouteParams/);
67
+ assert.doesNotMatch(feature, /\.make\(|\.singleton\(|containerToken|repositoryToken|serviceToken/);
156
68
  });
157
69
 
158
- test("users-core workspace users package templates stay aligned with workspace apps", async () => {
159
- const packageManifestSource = await readFile(
160
- path.join(PACKAGE_ROOT, "templates/packages/users-workspace/package.json"),
161
- "utf8"
162
- );
163
- const providerSource = await readFile(
164
- path.join(PACKAGE_ROOT, "templates/packages/users-workspace/src/server/UsersProvider.js"),
165
- "utf8"
166
- );
167
- const actionsSource = await readFile(
168
- path.join(PACKAGE_ROOT, "templates/packages/users-workspace/src/server/actions.js"),
169
- "utf8"
170
- );
171
- const routesSource = await readFile(
172
- path.join(PACKAGE_ROOT, "templates/packages/users-workspace/src/server/registerRoutes.js"),
173
- "utf8"
174
- );
175
- const serviceSource = await readFile(
176
- path.join(PACKAGE_ROOT, "templates/packages/users/src/server/service.js"),
177
- "utf8"
178
- );
179
-
180
- assert.match(packageManifestSource, /@jskit-ai\/workspaces-core/);
181
- assert.match(packageManifestSource, /@jskit-ai\/json-rest-api-core/);
182
- assert.match(packageManifestSource, /json-rest-api\.core/);
183
- assert.match(packageManifestSource, /"scaffoldShape": "users-core-crud-v1"/);
184
- assert.match(packageManifestSource, /"tableName": "users"/);
185
- assert.match(packageManifestSource, /"provenance": "users-core-template"/);
186
- assert.doesNotMatch(packageManifestSource, /server\/actionIds/);
187
- assert.match(providerSource, /surface: "admin"/);
188
- assert.match(providerSource, /routeSurfaceRequiresWorkspace/);
189
- assert.doesNotMatch(providerSource, /createCrudLookup/);
190
- assert.doesNotMatch(providerSource, /lookup\.users/);
191
- assert.doesNotMatch(providerSource, /normalizeRecordId/);
192
- assert.doesNotMatch(providerSource, /requires application singleton\(\)\/service\(\)\/actions\(\)\./);
193
- assert.match(providerSource, /createJsonRestResourceScopeOptions/);
194
- assert.match(providerSource, /addResourceIfMissing\(\s*api,\s*"users",\s*createJsonRestResourceScopeOptions\(resource,/s);
195
- assert.match(actionsSource, /workspaceSlugParamsValidator/);
196
- assert.match(actionsSource, /delete query\.workspaceSlug/);
197
- assert.doesNotMatch(actionsSource, /requireActionSurface/);
198
- assert.match(actionsSource, /createStandardCrudListQueryValidators\(\{ resource \}\)/);
199
- assert.match(actionsSource, /createStandardCrudViewQueryValidators\(\)/);
200
- assert.match(actionsSource, /const \{ workspaceSlug, recordId, \.\.\.query \} = input \|\| \{\};/);
201
- assert.match(actionsSource, /usersService\.getDocumentById\(recordId, query,/);
202
- assert.match(actionsSource, /usersService\.queryDocuments/);
203
- assert.match(actionsSource, /usersService\.getDocumentById/);
204
- assert.doesNotMatch(actionsSource, /from "\.\/actionIds\.js"/);
205
- assert.match(actionsSource, /id: "crud\.users\.list"/);
206
- assert.match(actionsSource, /id: "crud\.users\.view"/);
207
- assert.match(routesSource, /buildWorkspaceInputFromRouteParams/);
208
- assert.match(routesSource, /createJsonApiResourceRouteContract/);
209
- assert.doesNotMatch(routesSource, /wrapResponse/);
210
- assert.match(routesSource, /routeBase: routeSurfaceRequiresWorkspace === true \? "\/w\/:workspaceSlug" : "\/"/);
211
- assert.doesNotMatch(serviceSource, /serviceEvents/);
212
- assert.match(serviceSource, /throw new TypeError\("createService requires usersRepository\."\);/);
213
- assert.match(serviceSource, /returnJsonApiDocument/);
214
- });
215
-
216
- test("users-core local users resource scaffold stays read-only and canonical", async () => {
217
- const resourceModule = await import(
218
- pathToFileURL(path.join(PACKAGE_ROOT, "templates/packages/users/src/shared/userResource.js")).href
219
- );
220
- const resource = resourceModule?.resource;
70
+ test("both user administration variants stay read-only and canonical", async () => {
71
+ for (const packageName of ["users", "users-workspace"]) {
72
+ const resourceModule = await import(pathToFileURL(
73
+ path.join(patternRoot, "example", "packages", packageName, "src", "shared", "userResource.js")
74
+ ).href);
75
+ const resource = resourceModule.resource;
221
76
 
222
- assert.equal(typeof resource, "object");
223
- assert.deepEqual(Object.keys(resource.operations), ["list", "view"]);
224
- assert.equal(Object.hasOwn(resource.operations, "create"), false);
225
- assert.equal(resource.schema.updatedAt?.type, "dateTime");
226
- assert.equal(resource.schema.updatedAt?.storage?.writeSerializer, "datetime-utc");
77
+ assert.deepEqual(Object.keys(resource.operations), ["list", "view"]);
78
+ assert.equal(Object.hasOwn(resource.operations, "create"), false);
79
+ assert.equal(resource.schema.updatedAt?.storage?.writeSerializer, "datetime-utc");
80
+ }
227
81
  });
@@ -1,10 +1,13 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { UsersCoreServiceProvider } from "../src/server/UsersCoreServiceProvider.js";
4
- import { INTERNAL_JSON_REST_API } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
3
+ import { readSingleMultipartFile } from "@jskit-ai/uploads-runtime/server/multipart/readSingleMultipartFile";
5
4
  import { createRouter } from "../../kernel/server/http/lib/router.js";
6
5
  import { validateOperationSection } from "../../http-runtime/src/shared/validators/operationValidation.js";
7
6
  import { userProfileResource } from "../src/shared/resources/userProfileResource.js";
7
+ import { registerAccountProfileRoutes } from "../src/server/accountProfile/bootAccountProfileRoutes.js";
8
+ import { registerAccountPreferencesRoutes } from "../src/server/accountPreferences/bootAccountPreferencesRoutes.js";
9
+ import { registerAccountNotificationsRoutes } from "../src/server/accountNotifications/bootAccountNotificationsRoutes.js";
10
+ import { registerAccountSecurityRoutes } from "../src/server/accountSecurity/bootAccountSecurityRoutes.js";
8
11
 
9
12
  function createReplyDouble() {
10
13
  return {
@@ -34,49 +37,22 @@ async function registerRoutes({
34
37
  authService = {}
35
38
  } = {}) {
36
39
  const router = createRouter();
37
- const internalApi = {
38
- resources: {},
39
- async addResource(scopeName) {
40
- this.resources[scopeName] = {};
40
+ const accountProfileService = {
41
+ async readAvatar() {
42
+ return {
43
+ mimeType: "image/png",
44
+ buffer: Buffer.from([])
45
+ };
41
46
  }
42
47
  };
43
-
44
- const bindings = new Map([
45
- ["jskit.http.router", router],
46
- ["authService", authService],
47
- [INTERNAL_JSON_REST_API, internalApi],
48
- [
49
- "users.accountProfile.service",
50
- {
51
- async readAvatar() {
52
- return {
53
- mimeType: "image/png",
54
- buffer: Buffer.from([])
55
- };
56
- }
57
- }
58
- ],
59
- ["actionExecutor", {}]
60
- ]);
61
-
62
- const app = {
63
- has(token) {
64
- return bindings.has(token);
65
- },
66
- instance(token, value) {
67
- bindings.set(token, value);
68
- return this;
69
- },
70
- make(token) {
71
- if (!bindings.has(token)) {
72
- throw new Error(`Missing test binding for token: ${String(token)}`);
73
- }
74
- return bindings.get(token);
75
- }
76
- };
77
-
78
- const provider = new UsersCoreServiceProvider();
79
- await provider.boot(app);
48
+ registerAccountProfileRoutes(router, {
49
+ accountProfileService,
50
+ authService,
51
+ uploads: { readSingleMultipartFile }
52
+ });
53
+ registerAccountPreferencesRoutes(router);
54
+ registerAccountNotificationsRoutes(router);
55
+ registerAccountSecurityRoutes(router, { authService });
80
56
 
81
57
  return router.list();
82
58
  }
@@ -1,56 +0,0 @@
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";
8
- import { bootAccountProfileRoutes } from "./accountProfile/bootAccountProfileRoutes.js";
9
- import { bootAccountPreferencesRoutes } from "./accountPreferences/bootAccountPreferencesRoutes.js";
10
- import { bootAccountNotificationsRoutes } from "./accountNotifications/bootAccountNotificationsRoutes.js";
11
- import { bootAccountSecurityRoutes } from "./accountSecurity/bootAccountSecurityRoutes.js";
12
- import { registerSharedApi } from "./common/registerSharedApi.js";
13
- import { registerCommonRepositories } from "./common/registerCommonRepositories.js";
14
- import { registerUsersCore } from "./registerUsersCore.js";
15
- import { registerUsersBootstrap } from "./registerUsersBootstrap.js";
16
- import { registerAccountPreferences } from "./accountPreferences/registerAccountPreferences.js";
17
- import { registerAccountNotifications } from "./accountNotifications/registerAccountNotifications.js";
18
- import { registerAccountProfile } from "./accountProfile/registerAccountProfile.js";
19
- import { registerAccountSecurity } from "./accountSecurity/registerAccountSecurity.js";
20
- import { userProfileResource } from "../shared/resources/userProfileResource.js";
21
- import { userSettingsResource } from "../shared/resources/userSettingsResource.js";
22
-
23
- class UsersCoreServiceProvider {
24
- static id = "users.core";
25
-
26
- static startsAfter = ["json-rest-api.core", "runtime.actions"];
27
-
28
- async register(app) {
29
- registerSharedApi(app, USERS_SHARED_API);
30
- registerCommonRepositories(app);
31
- registerUsersCore(app);
32
- registerUsersBootstrap(app);
33
-
34
- registerAccountProfile(app);
35
- registerAccountPreferences(app);
36
- registerAccountNotifications(app);
37
- registerAccountSecurity(app);
38
- }
39
-
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));
49
- bootAccountProfileRoutes(app);
50
- bootAccountPreferencesRoutes(app);
51
- bootAccountNotificationsRoutes(app);
52
- bootAccountSecurityRoutes(app);
53
- }
54
- }
55
-
56
- export { UsersCoreServiceProvider };
@@ -1,37 +0,0 @@
1
- import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
- import { createService as createAccountNotificationsService } from "./accountNotificationsService.js";
3
- import { accountNotificationsActions } from "./accountNotificationsActions.js";
4
- import { deepFreeze } from "../common/support/deepFreeze.js";
5
- import { ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS } from "../common/support/realtimeServiceEvents.js";
6
-
7
- function registerAccountNotifications(app) {
8
- if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
9
- throw new Error("registerAccountNotifications requires application singleton()/service()/actions().");
10
- }
11
-
12
- app.service(
13
- "users.accountNotifications.service",
14
- (scope) =>
15
- createAccountNotificationsService({
16
- userSettingsRepository: scope.make("internal.repository.user-settings"),
17
- userProfilesRepository: scope.make("internal.repository.user-profiles"),
18
- authService: scope.make("authService")
19
- }),
20
- {
21
- events: deepFreeze({
22
- updateNotifications: ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS
23
- })
24
- }
25
- );
26
-
27
- app.actions(
28
- withActionDefaults(accountNotificationsActions, {
29
- domain: "settings",
30
- dependencies: {
31
- accountNotificationsService: "users.accountNotifications.service"
32
- }
33
- })
34
- );
35
- }
36
-
37
- export { registerAccountNotifications };
@@ -1,37 +0,0 @@
1
- import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
- import { createService as createAccountPreferencesService } from "./accountPreferencesService.js";
3
- import { accountPreferencesActions } from "./accountPreferencesActions.js";
4
- import { deepFreeze } from "../common/support/deepFreeze.js";
5
- import { ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS } from "../common/support/realtimeServiceEvents.js";
6
-
7
- function registerAccountPreferences(app) {
8
- if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
9
- throw new Error("registerAccountPreferences requires application singleton()/service()/actions().");
10
- }
11
-
12
- app.service(
13
- "users.accountPreferences.service",
14
- (scope) =>
15
- createAccountPreferencesService({
16
- userSettingsRepository: scope.make("internal.repository.user-settings"),
17
- userProfilesRepository: scope.make("internal.repository.user-profiles"),
18
- authService: scope.make("authService")
19
- }),
20
- {
21
- events: deepFreeze({
22
- updatePreferences: ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS
23
- })
24
- }
25
- );
26
-
27
- app.actions(
28
- withActionDefaults(accountPreferencesActions, {
29
- domain: "settings",
30
- dependencies: {
31
- accountPreferencesService: "users.accountPreferences.service"
32
- }
33
- })
34
- );
35
- }
36
-
37
- export { registerAccountPreferences };
@@ -1,56 +0,0 @@
1
- import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
- import { createService as createAccountProfileService } from "./accountProfileService.js";
3
- import { createService as createAvatarStorageService } from "./avatarStorageService.js";
4
- import { createService as createAvatarService } from "./avatarService.js";
5
- import { accountProfileActions } from "./accountProfileActions.js";
6
- import { deepFreeze } from "../common/support/deepFreeze.js";
7
- import { ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS } from "../common/support/realtimeServiceEvents.js";
8
-
9
-
10
- function registerAccountProfile(app) {
11
- if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
12
- throw new Error("registerAccountProfile requires application singleton()/service()/actions().");
13
- }
14
-
15
- app.singleton("users.avatar.storage.service", (scope) =>
16
- createAvatarStorageService({
17
- storage: scope.make("jskit.storage")
18
- })
19
- );
20
-
21
- app.singleton("users.avatar.service", (scope) =>
22
- createAvatarService({
23
- userProfilesRepository: scope.make("internal.repository.user-profiles"),
24
- avatarStorageService: scope.make("users.avatar.storage.service")
25
- })
26
- );
27
-
28
- app.service(
29
- "users.accountProfile.service",
30
- (scope) =>
31
- createAccountProfileService({
32
- userSettingsRepository: scope.make("internal.repository.user-settings"),
33
- userProfilesRepository: scope.make("internal.repository.user-profiles"),
34
- authService: scope.make("authService"),
35
- avatarService: scope.make("users.avatar.service")
36
- }),
37
- {
38
- events: deepFreeze({
39
- updateProfile: ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS,
40
- uploadAvatar: ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS,
41
- deleteAvatar: ACCOUNT_SETTINGS_AND_BOOTSTRAP_EVENTS
42
- })
43
- }
44
- );
45
-
46
- app.actions(
47
- withActionDefaults(accountProfileActions, {
48
- domain: "settings",
49
- dependencies: {
50
- accountProfileService: "users.accountProfile.service"
51
- }
52
- })
53
- );
54
- }
55
-
56
- export { registerAccountProfile };
@@ -1,29 +0,0 @@
1
- import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
- import { createService as createAccountSecurityService } from "./accountSecurityService.js";
3
- import { accountSecurityActions } from "./accountSecurityActions.js";
4
-
5
- function registerAccountSecurity(app) {
6
- if (!app || typeof app.singleton !== "function" || typeof app.actions !== "function") {
7
- throw new Error("registerAccountSecurity requires application singleton()/actions().");
8
- }
9
-
10
- app.singleton("users.accountSecurity.service", (scope) => {
11
- const authService = scope.has("authService") ? scope.make("authService") : null;
12
- return createAccountSecurityService({
13
- userSettingsRepository: scope.make("internal.repository.user-settings"),
14
- userProfilesRepository: scope.make("internal.repository.user-profiles"),
15
- authService
16
- });
17
- });
18
-
19
- app.actions(
20
- withActionDefaults(accountSecurityActions, {
21
- domain: "settings",
22
- dependencies: {
23
- accountSecurityService: "users.accountSecurity.service"
24
- }
25
- })
26
- );
27
- }
28
-
29
- export { registerAccountSecurity };
@@ -1,23 +0,0 @@
1
- import { INTERNAL_JSON_REST_API } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
2
- import { createRepository as createUserProfilesRepository } from "./repositories/userProfilesRepository.js";
3
- import { createRepository as createUserSettingsRepository } from "./repositories/userSettingsRepository.js";
4
-
5
- function registerCommonRepositories(app) {
6
- if (!app || typeof app.singleton !== "function") {
7
- throw new Error("registerCommonRepositories requires application singleton().");
8
- }
9
-
10
- app.singleton("internal.repository.user-settings", (scope) => {
11
- const api = scope.make(INTERNAL_JSON_REST_API);
12
- const knex = scope.make("jskit.database.knex");
13
- return createUserSettingsRepository({ api, knex });
14
- });
15
-
16
- app.singleton("internal.repository.user-profiles", (scope) => {
17
- const api = scope.make(INTERNAL_JSON_REST_API);
18
- const knex = scope.make("jskit.database.knex");
19
- return createUserProfilesRepository({ api, knex });
20
- });
21
- }
22
-
23
- export { registerCommonRepositories };
@@ -1,9 +0,0 @@
1
- function registerSharedApi(app, usersCoreApi) {
2
- if (!app || typeof app.singleton !== "function") {
3
- throw new Error("registerSharedApi requires application singleton().");
4
- }
5
-
6
- app.singleton("users.core", () => usersCoreApi);
7
- }
8
-
9
- export { registerSharedApi };