@jskit-ai/users-core 0.1.27 → 0.1.29
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.
- package/package.descriptor.mjs +5 -5
- package/package.json +5 -5
- package/src/server/common/contributors/workspaceActionContextContributor.js +6 -4
- package/src/server/common/services/authProfileSyncService.js +8 -2
- package/src/server/registerWorkspaceCore.js +1 -0
- package/src/shared/support/usersVisibility.js +9 -12
- package/test/authProfileSyncService.test.js +19 -1
- package/test/usersVisibility.test.js +13 -8
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/users-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.29",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Users/workspace domain runtime plus HTTP routes for workspace, account, and console features.",
|
|
7
7
|
dependsOn: [
|
|
@@ -196,10 +196,10 @@ export default Object.freeze({
|
|
|
196
196
|
mutations: {
|
|
197
197
|
dependencies: {
|
|
198
198
|
runtime: {
|
|
199
|
-
"@jskit-ai/auth-core": "0.1.
|
|
200
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
201
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
202
|
-
"@jskit-ai/kernel": "0.1.
|
|
199
|
+
"@jskit-ai/auth-core": "0.1.19",
|
|
200
|
+
"@jskit-ai/database-runtime": "0.1.20",
|
|
201
|
+
"@jskit-ai/http-runtime": "0.1.19",
|
|
202
|
+
"@jskit-ai/kernel": "0.1.20",
|
|
203
203
|
"@fastify/multipart": "^9.4.0",
|
|
204
204
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
205
205
|
"typebox": "^1.0.81"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"./shared/resources/consoleSettingsFields": "./src/shared/resources/consoleSettingsFields.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jskit-ai/auth-core": "0.1.
|
|
28
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
29
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
30
|
-
"@jskit-ai/kernel": "0.1.
|
|
27
|
+
"@jskit-ai/auth-core": "0.1.19",
|
|
28
|
+
"@jskit-ai/database-runtime": "0.1.20",
|
|
29
|
+
"@jskit-ai/http-runtime": "0.1.19",
|
|
30
|
+
"@jskit-ai/kernel": "0.1.20",
|
|
31
31
|
"@fastify/multipart": "^9.4.0",
|
|
32
32
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
33
33
|
"typebox": "^1.0.81"
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
requireServiceMethod
|
|
4
4
|
} from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
checkRouteVisibility,
|
|
7
7
|
USERS_ROUTE_VISIBILITY_PUBLIC,
|
|
8
8
|
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
9
9
|
USERS_ROUTE_VISIBILITY_WORKSPACE_USER
|
|
@@ -41,9 +41,11 @@ function createWorkspaceActionContextContributor({ workspaceService } = {}) {
|
|
|
41
41
|
|
|
42
42
|
const actionName = String(actionId || "").trim();
|
|
43
43
|
const hasLegacyWorkspaceActionId = WORKSPACE_CONTEXT_ACTION_IDS.includes(actionName);
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const routeVisibilityInput =
|
|
45
|
+
request && request.routeOptions && request.routeOptions.config
|
|
46
|
+
? request.routeOptions.config.visibility
|
|
47
|
+
: USERS_ROUTE_VISIBILITY_PUBLIC;
|
|
48
|
+
const routeVisibility = checkRouteVisibility(routeVisibilityInput);
|
|
47
49
|
const hasWorkspaceRouteVisibility = WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET.has(routeVisibility);
|
|
48
50
|
if (!hasLegacyWorkspaceActionId && !hasWorkspaceRouteVisibility) {
|
|
49
51
|
return {};
|
|
@@ -53,13 +53,16 @@ function requireSynchronizedProfile(profile) {
|
|
|
53
53
|
throw new Error("Profile synchronization failed.");
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function createService({ userProfilesRepository, workspaceProvisioningService = null } = {}) {
|
|
56
|
+
function createService({ userProfilesRepository, workspaceProvisioningService = null, userSettingsRepository = null } = {}) {
|
|
57
57
|
if (!userProfilesRepository || typeof userProfilesRepository.findByIdentity !== "function") {
|
|
58
58
|
throw new Error("authProfileSyncService requires userProfilesRepository.findByIdentity().");
|
|
59
59
|
}
|
|
60
60
|
if (typeof userProfilesRepository.upsert !== "function") {
|
|
61
61
|
throw new Error("authProfileSyncService requires userProfilesRepository.upsert().");
|
|
62
62
|
}
|
|
63
|
+
if (!userSettingsRepository || typeof userSettingsRepository.ensureForUserId !== "function") {
|
|
64
|
+
throw new Error("authProfileSyncService requires userSettingsRepository.ensureForUserId().");
|
|
65
|
+
}
|
|
63
66
|
|
|
64
67
|
async function findByIdentity(identityLike, options = {}) {
|
|
65
68
|
const normalized = buildNormalizedIdentityKey(identityLike);
|
|
@@ -93,11 +96,14 @@ function createService({ userProfilesRepository, workspaceProvisioningService =
|
|
|
93
96
|
const operationOptions = trx ? { ...options, trx } : options;
|
|
94
97
|
const existing = await findByIdentity(normalized, operationOptions);
|
|
95
98
|
if (!profileNeedsUpdate(existing, normalized)) {
|
|
96
|
-
|
|
99
|
+
const synchronizedProfile = requireSynchronizedProfile(existing);
|
|
100
|
+
await userSettingsRepository.ensureForUserId(synchronizedProfile.id, operationOptions);
|
|
101
|
+
return synchronizedProfile;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
const upserted = await upsertByIdentity(normalized, operationOptions);
|
|
100
105
|
const synchronizedProfile = requireSynchronizedProfile(upserted);
|
|
106
|
+
await userSettingsRepository.ensureForUserId(synchronizedProfile.id, operationOptions);
|
|
101
107
|
if (
|
|
102
108
|
!existing &&
|
|
103
109
|
workspaceProvisioningService &&
|
|
@@ -30,6 +30,7 @@ function registerWorkspaceCore(app) {
|
|
|
30
30
|
app.singleton("users.profile.sync.service", (scope) => {
|
|
31
31
|
return createAuthProfileSyncService({
|
|
32
32
|
userProfilesRepository: scope.make("userProfilesRepository"),
|
|
33
|
+
userSettingsRepository: scope.make("userSettingsRepository"),
|
|
33
34
|
workspaceProvisioningService: scope.make("users.workspace.service")
|
|
34
35
|
});
|
|
35
36
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
|
|
3
3
|
const USERS_ROUTE_VISIBILITY_PUBLIC = "public";
|
|
4
4
|
const USERS_ROUTE_VISIBILITY_USER = "user";
|
|
@@ -13,23 +13,20 @@ const USERS_ROUTE_VISIBILITY_LEVELS = Object.freeze([
|
|
|
13
13
|
]);
|
|
14
14
|
const USERS_ROUTE_VISIBILITY_LEVEL_SET = new Set(USERS_ROUTE_VISIBILITY_LEVELS);
|
|
15
15
|
|
|
16
|
-
function
|
|
17
|
-
const normalized =
|
|
16
|
+
function checkRouteVisibility(value, { context = "checkRouteVisibility" } = {}) {
|
|
17
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
18
18
|
if (USERS_ROUTE_VISIBILITY_LEVEL_SET.has(normalized)) {
|
|
19
19
|
return normalized;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return USERS_ROUTE_VISIBILITY_PUBLIC;
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
`${context} must be one of: ${USERS_ROUTE_VISIBILITY_LEVELS.join(", ")}.`
|
|
24
|
+
);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
function isWorkspaceVisibility(visibility = "") {
|
|
31
|
-
const normalized =
|
|
32
|
-
|
|
28
|
+
const normalized = checkRouteVisibility(visibility, {
|
|
29
|
+
context: "isWorkspaceVisibility visibility"
|
|
33
30
|
});
|
|
34
31
|
return normalized === USERS_ROUTE_VISIBILITY_WORKSPACE || normalized === USERS_ROUTE_VISIBILITY_WORKSPACE_USER;
|
|
35
32
|
}
|
|
@@ -40,6 +37,6 @@ export {
|
|
|
40
37
|
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
41
38
|
USERS_ROUTE_VISIBILITY_WORKSPACE_USER,
|
|
42
39
|
USERS_ROUTE_VISIBILITY_LEVELS,
|
|
43
|
-
|
|
40
|
+
checkRouteVisibility,
|
|
44
41
|
isWorkspaceVisibility
|
|
45
42
|
};
|
|
@@ -27,6 +27,12 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
27
27
|
return work(transaction);
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
+
userSettingsRepository: {
|
|
31
|
+
async ensureForUserId(userId, options = {}) {
|
|
32
|
+
calls.push({ step: "ensureUserSettings", userId: Number(userId), trx: options.trx || null });
|
|
33
|
+
return { userId: Number(userId) };
|
|
34
|
+
}
|
|
35
|
+
},
|
|
30
36
|
workspaceProvisioningService: {
|
|
31
37
|
async provisionWorkspaceForNewUser(_profile, options = {}) {
|
|
32
38
|
calls.push({ step: "provision", trx: options.trx || null });
|
|
@@ -45,10 +51,12 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
45
51
|
assert.equal(calls[0].step, "withTransaction");
|
|
46
52
|
assert.equal(calls[1].step, "find");
|
|
47
53
|
assert.equal(calls[2].step, "upsert");
|
|
48
|
-
assert.equal(calls[3].step, "
|
|
54
|
+
assert.equal(calls[3].step, "ensureUserSettings");
|
|
55
|
+
assert.equal(calls[4].step, "provision");
|
|
49
56
|
assert.equal(calls[1].trx, transaction);
|
|
50
57
|
assert.equal(calls[2].trx, transaction);
|
|
51
58
|
assert.equal(calls[3].trx, transaction);
|
|
59
|
+
assert.equal(calls[4].trx, transaction);
|
|
52
60
|
});
|
|
53
61
|
|
|
54
62
|
test("authProfileSyncService.syncIdentityProfile skips write path when profile is unchanged", async () => {
|
|
@@ -74,6 +82,11 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
74
82
|
return work({ trxId: "tx-2" });
|
|
75
83
|
}
|
|
76
84
|
},
|
|
85
|
+
userSettingsRepository: {
|
|
86
|
+
async ensureForUserId() {
|
|
87
|
+
return { userId: 7 };
|
|
88
|
+
}
|
|
89
|
+
},
|
|
77
90
|
workspaceProvisioningService: {
|
|
78
91
|
async provisionWorkspaceForNewUser() {
|
|
79
92
|
provisionCalls += 1;
|
|
@@ -104,6 +117,11 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
|
|
|
104
117
|
async upsert() {
|
|
105
118
|
return null;
|
|
106
119
|
}
|
|
120
|
+
},
|
|
121
|
+
userSettingsRepository: {
|
|
122
|
+
async ensureForUserId() {
|
|
123
|
+
return { userId: 1 };
|
|
124
|
+
}
|
|
107
125
|
}
|
|
108
126
|
});
|
|
109
127
|
|
|
@@ -2,16 +2,21 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import {
|
|
4
4
|
isWorkspaceVisibility,
|
|
5
|
-
|
|
5
|
+
checkRouteVisibility
|
|
6
6
|
} from "../src/shared/support/usersVisibility.js";
|
|
7
7
|
|
|
8
|
-
test("
|
|
9
|
-
assert.equal(
|
|
10
|
-
assert.equal(
|
|
11
|
-
assert.equal(
|
|
12
|
-
assert.
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
test("checkRouteVisibility normalizes valid users visibility levels and throws on invalid input", () => {
|
|
9
|
+
assert.equal(checkRouteVisibility("WORKSPACE"), "workspace");
|
|
10
|
+
assert.equal(checkRouteVisibility("workspace_user"), "workspace_user");
|
|
11
|
+
assert.equal(checkRouteVisibility("user"), "user");
|
|
12
|
+
assert.throws(
|
|
13
|
+
() => checkRouteVisibility(""),
|
|
14
|
+
/must be one of/
|
|
15
|
+
);
|
|
16
|
+
assert.throws(
|
|
17
|
+
() => checkRouteVisibility("unknown"),
|
|
18
|
+
/must be one of/
|
|
19
|
+
);
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
test("isWorkspaceVisibility recognizes workspace-only visibility levels", () => {
|