@jskit-ai/users-core 0.1.4
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 +464 -0
- package/package.json +35 -0
- package/src/server/UsersCoreServiceProvider.js +74 -0
- package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
- package/src/server/accountNotifications/accountNotificationsService.js +41 -0
- package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
- package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
- package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
- package/src/server/accountPreferences/accountPreferencesService.js +41 -0
- package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
- package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
- package/src/server/accountProfile/accountProfileActions.js +137 -0
- package/src/server/accountProfile/accountProfileService.js +124 -0
- package/src/server/accountProfile/avatarService.js +141 -0
- package/src/server/accountProfile/avatarStorageService.js +132 -0
- package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
- package/src/server/accountProfile/registerAccountProfile.js +62 -0
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
- package/src/server/accountSecurity/accountSecurityActions.js +144 -0
- package/src/server/accountSecurity/accountSecurityService.js +103 -0
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
- package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
- package/src/server/common/README.md +21 -0
- package/src/server/common/contributors/README.md +11 -0
- package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
- package/src/server/common/diTokens.js +21 -0
- package/src/server/common/formatters/README.md +11 -0
- package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
- package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
- package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
- package/src/server/common/formatters/workspaceFormatter.js +46 -0
- package/src/server/common/registerCommonRepositories.js +45 -0
- package/src/server/common/registerSharedApi.js +9 -0
- package/src/server/common/repositories/README.md +24 -0
- package/src/server/common/repositories/repositoryUtils.js +50 -0
- package/src/server/common/repositories/userProfilesRepository.js +251 -0
- package/src/server/common/repositories/userSettingsRepository.js +179 -0
- package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
- package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
- package/src/server/common/repositories/workspacesRepository.js +183 -0
- package/src/server/common/routes/README.md +11 -0
- package/src/server/common/services/README.md +12 -0
- package/src/server/common/services/accountContextService.js +31 -0
- package/src/server/common/services/authProfileSyncService.js +128 -0
- package/src/server/common/services/workspaceContextService.js +270 -0
- package/src/server/common/support/deepFreeze.js +17 -0
- package/src/server/common/support/realtimeServiceEvents.js +94 -0
- package/src/server/common/support/resolveActionUser.js +11 -0
- package/src/server/common/support/workspaceRoutePaths.js +17 -0
- package/src/server/common/validators/README.md +11 -0
- package/src/server/common/validators/authenticatedUserValidator.js +42 -0
- package/src/server/common/validators/routeParamsValidator.js +62 -0
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
- package/src/server/consoleSettings/consoleService.js +36 -0
- package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
- package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
- package/src/server/consoleSettings/consoleSettingsService.js +40 -0
- package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
- package/src/server/registerWorkspaceBootstrap.js +36 -0
- package/src/server/registerWorkspaceCore.js +95 -0
- package/src/server/support/resolveWorkspace.js +16 -0
- package/src/server/support/workspaceActionSurfaces.js +135 -0
- package/src/server/support/workspaceInvitationsPolicy.js +45 -0
- package/src/server/support/workspaceRouteInput.js +22 -0
- package/src/server/workspaceBootstrapContributor.js +401 -0
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
- package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
- package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
- package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
- package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
- package/src/shared/events/usersEvents.js +19 -0
- package/src/shared/index.js +91 -0
- package/src/shared/operationMessages.js +16 -0
- package/src/shared/resources/consoleSettingsFields.js +55 -0
- package/src/shared/resources/consoleSettingsResource.js +139 -0
- package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
- package/src/shared/resources/userProfileResource.js +148 -0
- package/src/shared/resources/userSettingsFields.js +71 -0
- package/src/shared/resources/userSettingsResource.js +416 -0
- package/src/shared/resources/workspaceMembersResource.js +352 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
- package/src/shared/resources/workspaceResource.js +149 -0
- package/src/shared/resources/workspaceSettingsFields.js +60 -0
- package/src/shared/resources/workspaceSettingsResource.js +178 -0
- package/src/shared/roles.js +136 -0
- package/src/shared/settings.js +31 -0
- package/src/shared/support/usersApiPaths.js +34 -0
- package/src/shared/support/usersVisibility.js +45 -0
- package/src/shared/support/workspacePathModel.js +145 -0
- package/src/shared/tenancyMode.js +35 -0
- package/src/shared/tenancyProfile.js +73 -0
- package/templates/config/workspaceRoles.js +30 -0
- package/templates/migrations/users_core_console_owner.cjs +39 -0
- package/templates/migrations/users_core_initial.cjs +118 -0
- package/templates/migrations/users_core_profile_username.cjs +98 -0
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
- package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
- package/test/authProfileSyncService.test.js +119 -0
- package/test/avatarService.test.js +114 -0
- package/test/avatarStorageService.test.js +61 -0
- package/test/consoleService.test.js +57 -0
- package/test/consoleSettingsService.test.js +86 -0
- package/test/exportsContract.test.js +38 -0
- package/test/registerAvatarMultipartSupport.test.js +64 -0
- package/test/registerServiceRealtimeEvents.test.js +160 -0
- package/test/registerWorkspaceDirectory.test.js +26 -0
- package/test/registerWorkspaceSettings.test.js +44 -0
- package/test/resourcesCanonical.test.js +90 -0
- package/test/roles.test.js +74 -0
- package/test/settingsFieldRegistriesSingleton.test.js +24 -0
- package/test/tenancyProfile.test.js +67 -0
- package/test/userSettingsResource.test.js +31 -0
- package/test/usersApiPaths.test.js +31 -0
- package/test/usersRouteRequestInputValidator.test.js +556 -0
- package/test/usersRouteResources.test.js +113 -0
- package/test/usersRouteValidators.test.js +49 -0
- package/test/usersVisibility.test.js +22 -0
- package/test/workspaceActionContextContributor.test.js +251 -0
- package/test/workspaceActionSurfaces.test.js +105 -0
- package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
- package/test/workspaceBootstrapContributor.test.js +466 -0
- package/test/workspaceInvitationsPolicy.test.js +71 -0
- package/test/workspaceInvitesRepository.test.js +111 -0
- package/test/workspaceMembersService.test.js +400 -0
- package/test/workspacePathModel.test.js +93 -0
- package/test/workspacePendingInvitationsResource.test.js +38 -0
- package/test/workspacePendingInvitationsService.test.js +151 -0
- package/test/workspaceRouteVisibilityResolver.test.js +83 -0
- package/test/workspaceService.test.js +480 -0
- package/test/workspaceSettingsActions.test.js +42 -0
- package/test/workspaceSettingsRepository.test.js +156 -0
- package/test/workspaceSettingsResource.test.js +156 -0
- package/test/workspaceSettingsService.test.js +120 -0
- package/test-support/registerDefaultSettingsFields.js +3 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { KERNEL_TOKENS } from "@jskit-ai/kernel/shared/support/tokens";
|
|
2
|
+
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
3
|
+
import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
|
|
4
|
+
import {
|
|
5
|
+
WORKSPACE_SETTINGS_CHANGED_EVENT
|
|
6
|
+
} from "../../shared/events/usersEvents.js";
|
|
7
|
+
import { deepFreeze } from "../common/support/deepFreeze.js";
|
|
8
|
+
import { createRepository as createWorkspaceSettingsRepository } from "./workspaceSettingsRepository.js";
|
|
9
|
+
import { createService as createWorkspaceSettingsService } from "./workspaceSettingsService.js";
|
|
10
|
+
import { workspaceSettingsActions } from "./workspaceSettingsActions.js";
|
|
11
|
+
import { createWorkspaceRoleCatalog } from "../../shared/roles.js";
|
|
12
|
+
import { USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN } from "../common/diTokens.js";
|
|
13
|
+
import { createWorkspaceEntityAndBootstrapEvents } from "../common/support/realtimeServiceEvents.js";
|
|
14
|
+
|
|
15
|
+
function resolveWorkspaceSettingsDefaultInvitesEnabled(appConfig = {}) {
|
|
16
|
+
const defaultInvitesEnabled = appConfig?.workspaceSettings?.defaults?.invitesEnabled;
|
|
17
|
+
|
|
18
|
+
if (typeof defaultInvitesEnabled !== "boolean") {
|
|
19
|
+
throw new TypeError("users.core requires appConfig.workspaceSettings.defaults.invitesEnabled.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return defaultInvitesEnabled;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function registerWorkspaceSettings(app) {
|
|
26
|
+
if (!app || typeof app.singleton !== "function" || typeof app.actions !== "function" || typeof app.service !== "function") {
|
|
27
|
+
throw new Error("registerWorkspaceSettings requires application singleton()/service()/actions().");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
app.singleton("workspaceSettingsRepository", (scope) => {
|
|
31
|
+
const knex = scope.make(KERNEL_TOKENS.Knex);
|
|
32
|
+
const appConfig = resolveAppConfig(scope);
|
|
33
|
+
return createWorkspaceSettingsRepository(knex, {
|
|
34
|
+
defaultInvitesEnabled: resolveWorkspaceSettingsDefaultInvitesEnabled(appConfig)
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.service(
|
|
39
|
+
"users.workspace.settings.service",
|
|
40
|
+
(scope) =>
|
|
41
|
+
createWorkspaceSettingsService({
|
|
42
|
+
workspaceSettingsRepository: scope.make("workspaceSettingsRepository"),
|
|
43
|
+
workspaceInvitationsEnabled: scope.make(USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN),
|
|
44
|
+
roleCatalog: createWorkspaceRoleCatalog(resolveAppConfig(scope))
|
|
45
|
+
}),
|
|
46
|
+
{
|
|
47
|
+
events: deepFreeze({
|
|
48
|
+
updateWorkspaceSettings: createWorkspaceEntityAndBootstrapEvents({
|
|
49
|
+
workspaceEntity: "settings",
|
|
50
|
+
workspaceOperation: "updated",
|
|
51
|
+
workspaceRealtimeEvent: WORKSPACE_SETTINGS_CHANGED_EVENT
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
app.actions(
|
|
58
|
+
withActionDefaults(workspaceSettingsActions, {
|
|
59
|
+
domain: "workspace",
|
|
60
|
+
dependencies: {
|
|
61
|
+
workspaceSettingsService: "users.workspace.settings.service"
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { registerWorkspaceSettings };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { workspaceSettingsResource } from "../../shared/resources/workspaceSettingsResource.js";
|
|
2
|
+
import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
|
|
3
|
+
import { resolveWorkspace } from "../support/resolveWorkspace.js";
|
|
4
|
+
|
|
5
|
+
const workspaceSettingsActions = Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
id: "workspace.settings.read",
|
|
8
|
+
version: 1,
|
|
9
|
+
kind: "query",
|
|
10
|
+
channels: ["api", "automation", "internal"],
|
|
11
|
+
surfacesFrom: "workspace",
|
|
12
|
+
permission: {
|
|
13
|
+
require: "any",
|
|
14
|
+
permissions: ["workspace.settings.view", "workspace.settings.update"]
|
|
15
|
+
},
|
|
16
|
+
inputValidator: workspaceSlugParamsValidator,
|
|
17
|
+
outputValidator: workspaceSettingsResource.operations.view.outputValidator,
|
|
18
|
+
idempotency: "none",
|
|
19
|
+
audit: {
|
|
20
|
+
actionName: "workspace.settings.read"
|
|
21
|
+
},
|
|
22
|
+
observability: {},
|
|
23
|
+
async execute(input, context, deps) {
|
|
24
|
+
const response = await deps.workspaceSettingsService.getWorkspaceSettings(resolveWorkspace(context, input), {
|
|
25
|
+
context
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "workspace.settings.update",
|
|
33
|
+
version: 1,
|
|
34
|
+
kind: "command",
|
|
35
|
+
channels: ["api", "assistant_tool", "automation", "internal"],
|
|
36
|
+
surfacesFrom: "workspace",
|
|
37
|
+
permission: {
|
|
38
|
+
require: "all",
|
|
39
|
+
permissions: ["workspace.settings.update"]
|
|
40
|
+
},
|
|
41
|
+
inputValidator: [
|
|
42
|
+
workspaceSlugParamsValidator,
|
|
43
|
+
{
|
|
44
|
+
patch: workspaceSettingsResource.operations.patch.bodyValidator
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
outputValidator: workspaceSettingsResource.operations.patch.outputValidator,
|
|
48
|
+
idempotency: "optional",
|
|
49
|
+
audit: {
|
|
50
|
+
actionName: "workspace.settings.update"
|
|
51
|
+
},
|
|
52
|
+
observability: {},
|
|
53
|
+
extensions: {
|
|
54
|
+
assistant: {
|
|
55
|
+
description: "Update workspace settings."
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async execute(input, context, deps) {
|
|
59
|
+
const response = await deps.workspaceSettingsService.updateWorkspaceSettings(
|
|
60
|
+
resolveWorkspace(context, input),
|
|
61
|
+
input.patch,
|
|
62
|
+
{
|
|
63
|
+
context
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
export { workspaceSettingsActions };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toIsoString,
|
|
3
|
+
nowDb,
|
|
4
|
+
isDuplicateEntryError
|
|
5
|
+
} from "../common/repositories/repositoryUtils.js";
|
|
6
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
7
|
+
import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
|
|
8
|
+
import {
|
|
9
|
+
workspaceSettingsFields,
|
|
10
|
+
resolveWorkspaceSettingsFieldKeys
|
|
11
|
+
} from "../../shared/resources/workspaceSettingsFields.js";
|
|
12
|
+
|
|
13
|
+
function resolveWorkspaceSettingsSeed(workspace = {}, { defaultInvitesEnabled = true } = {}) {
|
|
14
|
+
const source = normalizeObjectInput(workspace);
|
|
15
|
+
const seed = {};
|
|
16
|
+
for (const field of workspaceSettingsFields) {
|
|
17
|
+
const rawValue = Object.hasOwn(source, field.key)
|
|
18
|
+
? source[field.key]
|
|
19
|
+
: field.resolveDefault({
|
|
20
|
+
workspace: source,
|
|
21
|
+
defaultInvitesEnabled
|
|
22
|
+
});
|
|
23
|
+
seed[field.key] = field.normalizeOutput(rawValue, {
|
|
24
|
+
workspace: source,
|
|
25
|
+
defaultInvitesEnabled
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return seed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createRepository(knex, { defaultInvitesEnabled } = {}) {
|
|
32
|
+
if (typeof knex !== "function") {
|
|
33
|
+
throw new TypeError("workspaceSettingsRepository requires knex.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mapRow(row) {
|
|
37
|
+
if (!row) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const settings = {
|
|
42
|
+
workspaceId: Number(row.workspace_id)
|
|
43
|
+
};
|
|
44
|
+
for (const field of workspaceSettingsFields) {
|
|
45
|
+
const rawValue = Object.hasOwn(row, field.dbColumn)
|
|
46
|
+
? row[field.dbColumn]
|
|
47
|
+
: field.resolveDefault({
|
|
48
|
+
defaultInvitesEnabled
|
|
49
|
+
});
|
|
50
|
+
settings[field.key] = field.normalizeOutput(rawValue, {
|
|
51
|
+
defaultInvitesEnabled
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
settings.createdAt = toIsoString(row.created_at);
|
|
56
|
+
settings.updatedAt = toIsoString(row.updated_at);
|
|
57
|
+
return settings;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function findByWorkspaceId(workspaceId, options = {}) {
|
|
61
|
+
const client = options?.trx || knex;
|
|
62
|
+
const row = await client("workspace_settings").where({ workspace_id: Number(workspaceId) }).first();
|
|
63
|
+
return mapRow(row);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function ensureForWorkspaceId(workspaceId, options = {}) {
|
|
67
|
+
const client = options?.trx || knex;
|
|
68
|
+
const numericWorkspaceId = Number(workspaceId);
|
|
69
|
+
const seed = resolveWorkspaceSettingsSeed(options?.workspace, {
|
|
70
|
+
defaultInvitesEnabled
|
|
71
|
+
});
|
|
72
|
+
const existing = await findByWorkspaceId(numericWorkspaceId, { trx: client });
|
|
73
|
+
if (existing) {
|
|
74
|
+
return existing;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const insertPayload = {
|
|
79
|
+
workspace_id: numericWorkspaceId,
|
|
80
|
+
created_at: nowDb(),
|
|
81
|
+
updated_at: nowDb()
|
|
82
|
+
};
|
|
83
|
+
for (const field of workspaceSettingsFields) {
|
|
84
|
+
insertPayload[field.dbColumn] = seed[field.key];
|
|
85
|
+
}
|
|
86
|
+
await client("workspace_settings").insert(insertPayload);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (!isDuplicateEntryError(error)) {
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return findByWorkspaceId(numericWorkspaceId, { trx: client });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function updateSettingsByWorkspaceId(workspaceId, patch = {}, options = {}) {
|
|
97
|
+
const client = options?.trx || knex;
|
|
98
|
+
const ensured = await ensureForWorkspaceId(workspaceId, {
|
|
99
|
+
trx: client,
|
|
100
|
+
workspace: options?.workspace
|
|
101
|
+
});
|
|
102
|
+
const source = normalizeObjectInput(patch);
|
|
103
|
+
const settingsPatch = pickOwnProperties(source, resolveWorkspaceSettingsFieldKeys());
|
|
104
|
+
|
|
105
|
+
if (Object.keys(settingsPatch).length === 0) {
|
|
106
|
+
return ensured;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const dbPatch = {
|
|
110
|
+
updated_at: nowDb()
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
for (const field of workspaceSettingsFields) {
|
|
114
|
+
if (!Object.hasOwn(settingsPatch, field.key)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
dbPatch[field.dbColumn] = field.normalizeInput(settingsPatch[field.key], {
|
|
118
|
+
payload: source
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await client("workspace_settings").where({ workspace_id: Number(workspaceId) }).update({
|
|
123
|
+
...dbPatch
|
|
124
|
+
});
|
|
125
|
+
return findByWorkspaceId(workspaceId, { trx: client });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Object.freeze({
|
|
129
|
+
findByWorkspaceId,
|
|
130
|
+
ensureForWorkspaceId,
|
|
131
|
+
updateSettingsByWorkspaceId
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export { createRepository };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
2
|
+
import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
|
|
3
|
+
import {
|
|
4
|
+
workspaceSettingsFields,
|
|
5
|
+
resolveWorkspaceSettingsFieldKeys
|
|
6
|
+
} from "../../shared/resources/workspaceSettingsFields.js";
|
|
7
|
+
import { createWorkspaceRoleCatalog, cloneWorkspaceRoleCatalog } from "../../shared/roles.js";
|
|
8
|
+
|
|
9
|
+
function createService({
|
|
10
|
+
workspaceSettingsRepository,
|
|
11
|
+
workspaceInvitationsEnabled = true,
|
|
12
|
+
roleCatalog = null
|
|
13
|
+
} = {}) {
|
|
14
|
+
if (!workspaceSettingsRepository) {
|
|
15
|
+
throw new Error("workspaceSettingsService requires workspaceSettingsRepository.");
|
|
16
|
+
}
|
|
17
|
+
const resolvedRoleCatalog = roleCatalog && typeof roleCatalog === "object" ? roleCatalog : createWorkspaceRoleCatalog();
|
|
18
|
+
const invitesAvailable = workspaceInvitationsEnabled === true;
|
|
19
|
+
|
|
20
|
+
async function getWorkspaceSettings(workspace, options = {}) {
|
|
21
|
+
const settingsRecord = await workspaceSettingsRepository.ensureForWorkspaceId(workspace.id, {
|
|
22
|
+
...options,
|
|
23
|
+
workspace
|
|
24
|
+
});
|
|
25
|
+
const settings = {};
|
|
26
|
+
for (const field of workspaceSettingsFields) {
|
|
27
|
+
settings[field.key] = settingsRecord[field.key];
|
|
28
|
+
}
|
|
29
|
+
const invitesEnabled = invitesAvailable && settings.invitesEnabled !== false;
|
|
30
|
+
settings.invitesEnabled = invitesEnabled;
|
|
31
|
+
settings.invitesAvailable = invitesAvailable;
|
|
32
|
+
settings.invitesEffective = invitesAvailable && invitesEnabled;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
workspace: {
|
|
36
|
+
id: Number(workspace.id),
|
|
37
|
+
slug: String(workspace.slug || ""),
|
|
38
|
+
ownerUserId: Number(workspace.ownerUserId)
|
|
39
|
+
},
|
|
40
|
+
settings,
|
|
41
|
+
roleCatalog: cloneWorkspaceRoleCatalog(resolvedRoleCatalog)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function updateWorkspaceSettings(workspace, payload = {}, options = {}) {
|
|
46
|
+
const source = normalizeObjectInput(payload);
|
|
47
|
+
const settingsPatch = pickOwnProperties(source, resolveWorkspaceSettingsFieldKeys());
|
|
48
|
+
|
|
49
|
+
if (Object.keys(settingsPatch).length > 0) {
|
|
50
|
+
await workspaceSettingsRepository.updateSettingsByWorkspaceId(workspace.id, settingsPatch, {
|
|
51
|
+
...options,
|
|
52
|
+
workspace
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return getWorkspaceSettings(workspace, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
getWorkspaceSettings,
|
|
61
|
+
updateWorkspaceSettings
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { createService };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const ACCOUNT_SETTINGS_CHANGED_EVENT = "account.settings.changed";
|
|
2
|
+
const CONSOLE_SETTINGS_CHANGED_EVENT = "console.settings.changed";
|
|
3
|
+
const USERS_BOOTSTRAP_CHANGED_EVENT = "users.bootstrap.changed";
|
|
4
|
+
const WORKSPACE_SETTINGS_CHANGED_EVENT = "workspace.settings.changed";
|
|
5
|
+
const WORKSPACE_MEMBERS_CHANGED_EVENT = "workspace.members.changed";
|
|
6
|
+
const WORKSPACE_INVITES_CHANGED_EVENT = "workspace.invites.changed";
|
|
7
|
+
const WORKSPACES_CHANGED_EVENT = "workspaces.changed";
|
|
8
|
+
const WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT = "workspace.invitations.pending.changed";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
ACCOUNT_SETTINGS_CHANGED_EVENT,
|
|
12
|
+
CONSOLE_SETTINGS_CHANGED_EVENT,
|
|
13
|
+
USERS_BOOTSTRAP_CHANGED_EVENT,
|
|
14
|
+
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
15
|
+
WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
16
|
+
WORKSPACE_INVITES_CHANGED_EVENT,
|
|
17
|
+
WORKSPACES_CHANGED_EVENT,
|
|
18
|
+
WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
|
|
19
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OWNER_ROLE_ID,
|
|
3
|
+
ADMIN_ROLE_ID,
|
|
4
|
+
MEMBER_ROLE_ID,
|
|
5
|
+
resolveRolePermissions,
|
|
6
|
+
listRoleDescriptors,
|
|
7
|
+
hasPermission
|
|
8
|
+
} from "./roles.js";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_WORKSPACE_COLOR,
|
|
12
|
+
DEFAULT_USER_SETTINGS,
|
|
13
|
+
coerceWorkspaceColor
|
|
14
|
+
} from "./settings.js";
|
|
15
|
+
import {
|
|
16
|
+
TENANCY_MODE_NONE,
|
|
17
|
+
TENANCY_MODE_PERSONAL,
|
|
18
|
+
TENANCY_MODE_WORKSPACE,
|
|
19
|
+
normalizeTenancyMode,
|
|
20
|
+
WORKSPACE_SLUG_POLICY_NONE,
|
|
21
|
+
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
22
|
+
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
23
|
+
resolveTenancyProfile,
|
|
24
|
+
isWorkspaceTenancyMode
|
|
25
|
+
} from "./tenancyProfile.js";
|
|
26
|
+
import {
|
|
27
|
+
ACCOUNT_SETTINGS_CHANGED_EVENT,
|
|
28
|
+
CONSOLE_SETTINGS_CHANGED_EVENT,
|
|
29
|
+
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
30
|
+
WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
31
|
+
WORKSPACE_INVITES_CHANGED_EVENT,
|
|
32
|
+
WORKSPACES_CHANGED_EVENT,
|
|
33
|
+
WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
|
|
34
|
+
} from "./events/usersEvents.js";
|
|
35
|
+
|
|
36
|
+
const USERS_SHARED_API = Object.freeze({
|
|
37
|
+
OWNER_ROLE_ID,
|
|
38
|
+
ADMIN_ROLE_ID,
|
|
39
|
+
MEMBER_ROLE_ID,
|
|
40
|
+
resolveRolePermissions,
|
|
41
|
+
listRoleDescriptors,
|
|
42
|
+
hasPermission,
|
|
43
|
+
DEFAULT_WORKSPACE_COLOR,
|
|
44
|
+
DEFAULT_USER_SETTINGS,
|
|
45
|
+
coerceWorkspaceColor,
|
|
46
|
+
TENANCY_MODE_NONE,
|
|
47
|
+
TENANCY_MODE_PERSONAL,
|
|
48
|
+
TENANCY_MODE_WORKSPACE,
|
|
49
|
+
normalizeTenancyMode,
|
|
50
|
+
WORKSPACE_SLUG_POLICY_NONE,
|
|
51
|
+
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
52
|
+
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
53
|
+
resolveTenancyProfile,
|
|
54
|
+
isWorkspaceTenancyMode,
|
|
55
|
+
ACCOUNT_SETTINGS_CHANGED_EVENT,
|
|
56
|
+
CONSOLE_SETTINGS_CHANGED_EVENT,
|
|
57
|
+
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
58
|
+
WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
59
|
+
WORKSPACE_INVITES_CHANGED_EVENT,
|
|
60
|
+
WORKSPACES_CHANGED_EVENT,
|
|
61
|
+
WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
OWNER_ROLE_ID,
|
|
66
|
+
ADMIN_ROLE_ID,
|
|
67
|
+
MEMBER_ROLE_ID,
|
|
68
|
+
resolveRolePermissions,
|
|
69
|
+
listRoleDescriptors,
|
|
70
|
+
hasPermission,
|
|
71
|
+
DEFAULT_WORKSPACE_COLOR,
|
|
72
|
+
DEFAULT_USER_SETTINGS,
|
|
73
|
+
coerceWorkspaceColor,
|
|
74
|
+
TENANCY_MODE_NONE,
|
|
75
|
+
TENANCY_MODE_PERSONAL,
|
|
76
|
+
TENANCY_MODE_WORKSPACE,
|
|
77
|
+
normalizeTenancyMode,
|
|
78
|
+
WORKSPACE_SLUG_POLICY_NONE,
|
|
79
|
+
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
80
|
+
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
81
|
+
resolveTenancyProfile,
|
|
82
|
+
isWorkspaceTenancyMode,
|
|
83
|
+
ACCOUNT_SETTINGS_CHANGED_EVENT,
|
|
84
|
+
CONSOLE_SETTINGS_CHANGED_EVENT,
|
|
85
|
+
WORKSPACE_SETTINGS_CHANGED_EVENT,
|
|
86
|
+
WORKSPACE_MEMBERS_CHANGED_EVENT,
|
|
87
|
+
WORKSPACE_INVITES_CHANGED_EVENT,
|
|
88
|
+
WORKSPACES_CHANGED_EVENT,
|
|
89
|
+
WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT,
|
|
90
|
+
USERS_SHARED_API
|
|
91
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function createOperationMessages({
|
|
2
|
+
validationMessage = "Validation failed.",
|
|
3
|
+
apiValidationMessage = validationMessage
|
|
4
|
+
} = {}) {
|
|
5
|
+
const validation = String(validationMessage || "Validation failed.");
|
|
6
|
+
const apiValidation = String(apiValidationMessage || validation || "Validation failed.");
|
|
7
|
+
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
validation,
|
|
10
|
+
apiValidation
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
createOperationMessages
|
|
16
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
2
|
+
import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
|
|
3
|
+
|
|
4
|
+
const CONSOLE_SETTINGS_FIELDS_REGISTRY_KEY = Symbol.for("jskit.users-core.consoleSettingsFields");
|
|
5
|
+
const consoleSettingsFields = resolveGlobalArrayRegistry(CONSOLE_SETTINGS_FIELDS_REGISTRY_KEY);
|
|
6
|
+
|
|
7
|
+
function defineField(field = {}) {
|
|
8
|
+
const key = normalizeText(field.key);
|
|
9
|
+
if (!key) {
|
|
10
|
+
throw new TypeError("consoleSettingsFields.defineField requires field.key.");
|
|
11
|
+
}
|
|
12
|
+
if (consoleSettingsFields.some((entry) => entry.key === key)) {
|
|
13
|
+
throw new Error(`consoleSettingsFields.defineField duplicate key: ${key}`);
|
|
14
|
+
}
|
|
15
|
+
if (!field.inputSchema || typeof field.inputSchema !== "object") {
|
|
16
|
+
throw new TypeError(`consoleSettingsFields.defineField("${key}") requires inputSchema.`);
|
|
17
|
+
}
|
|
18
|
+
if (!field.outputSchema || typeof field.outputSchema !== "object") {
|
|
19
|
+
throw new TypeError(`consoleSettingsFields.defineField("${key}") requires outputSchema.`);
|
|
20
|
+
}
|
|
21
|
+
const dbColumn = normalizeText(field.dbColumn);
|
|
22
|
+
if (!dbColumn) {
|
|
23
|
+
throw new TypeError(`consoleSettingsFields.defineField("${key}") requires dbColumn.`);
|
|
24
|
+
}
|
|
25
|
+
if (typeof field.normalizeInput !== "function") {
|
|
26
|
+
throw new TypeError(`consoleSettingsFields.defineField("${key}") requires normalizeInput.`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof field.normalizeOutput !== "function") {
|
|
29
|
+
throw new TypeError(`consoleSettingsFields.defineField("${key}") requires normalizeOutput.`);
|
|
30
|
+
}
|
|
31
|
+
if (typeof field.resolveDefault !== "function") {
|
|
32
|
+
throw new TypeError(`consoleSettingsFields.defineField("${key}") requires resolveDefault.`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
consoleSettingsFields.push({
|
|
36
|
+
key,
|
|
37
|
+
dbColumn,
|
|
38
|
+
required: field.required !== false,
|
|
39
|
+
inputSchema: field.inputSchema,
|
|
40
|
+
outputSchema: field.outputSchema,
|
|
41
|
+
normalizeInput: field.normalizeInput,
|
|
42
|
+
normalizeOutput: field.normalizeOutput,
|
|
43
|
+
resolveDefault: field.resolveDefault
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resetConsoleSettingsFields() {
|
|
48
|
+
consoleSettingsFields.splice(0, consoleSettingsFields.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
defineField,
|
|
53
|
+
resetConsoleSettingsFields,
|
|
54
|
+
consoleSettingsFields
|
|
55
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { createOperationMessages } from "../operationMessages.js";
|
|
3
|
+
import {
|
|
4
|
+
createCursorListValidator,
|
|
5
|
+
normalizeObjectInput
|
|
6
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
7
|
+
import { consoleSettingsFields } from "./consoleSettingsFields.js";
|
|
8
|
+
|
|
9
|
+
function buildCreateSchema() {
|
|
10
|
+
const properties = {};
|
|
11
|
+
for (const field of consoleSettingsFields) {
|
|
12
|
+
properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
|
|
13
|
+
}
|
|
14
|
+
return Type.Object(properties, { additionalProperties: false });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildOutputSchema() {
|
|
18
|
+
const properties = {};
|
|
19
|
+
for (const field of consoleSettingsFields) {
|
|
20
|
+
properties[field.key] = field.outputSchema;
|
|
21
|
+
}
|
|
22
|
+
return Type.Object(properties, { additionalProperties: false });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildConsoleSettingsRecordSchema() {
|
|
26
|
+
return Type.Object(
|
|
27
|
+
{
|
|
28
|
+
settings: buildOutputSchema()
|
|
29
|
+
},
|
|
30
|
+
{ additionalProperties: false }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildConsoleSettingsCreateSchema() {
|
|
35
|
+
return buildCreateSchema();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildConsoleSettingsReplaceSchema() {
|
|
39
|
+
return buildConsoleSettingsCreateSchema();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildConsoleSettingsPatchSchema() {
|
|
43
|
+
return Type.Partial(buildConsoleSettingsCreateSchema(), {
|
|
44
|
+
additionalProperties: false
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeConsoleSettingsInput(payload = {}) {
|
|
49
|
+
const source = normalizeObjectInput(payload);
|
|
50
|
+
const normalized = {};
|
|
51
|
+
for (const field of consoleSettingsFields) {
|
|
52
|
+
if (!Object.hasOwn(source, field.key)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
normalized[field.key] = field.normalizeInput(source[field.key], {
|
|
56
|
+
payload: source
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const consoleSettingsOutputValidator = Object.freeze({
|
|
63
|
+
get schema() {
|
|
64
|
+
return buildConsoleSettingsRecordSchema();
|
|
65
|
+
},
|
|
66
|
+
normalize(payload = {}) {
|
|
67
|
+
const source = normalizeObjectInput(payload);
|
|
68
|
+
const settingsSource = normalizeObjectInput(source.settings);
|
|
69
|
+
const settings = {};
|
|
70
|
+
|
|
71
|
+
for (const field of consoleSettingsFields) {
|
|
72
|
+
const rawValue = Object.hasOwn(settingsSource, field.key)
|
|
73
|
+
? settingsSource[field.key]
|
|
74
|
+
: field.resolveDefault({
|
|
75
|
+
settings: settingsSource
|
|
76
|
+
});
|
|
77
|
+
settings[field.key] = field.normalizeOutput(rawValue, {
|
|
78
|
+
settings: settingsSource
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
settings
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const CONSOLE_SETTINGS_OPERATION_MESSAGES = createOperationMessages();
|
|
89
|
+
|
|
90
|
+
const consoleSettingsResource = Object.freeze({
|
|
91
|
+
resource: "consoleSettings",
|
|
92
|
+
operations: Object.freeze({
|
|
93
|
+
view: Object.freeze({
|
|
94
|
+
method: "GET",
|
|
95
|
+
messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
|
|
96
|
+
outputValidator: consoleSettingsOutputValidator
|
|
97
|
+
}),
|
|
98
|
+
list: Object.freeze({
|
|
99
|
+
method: "GET",
|
|
100
|
+
messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
|
|
101
|
+
outputValidator: createCursorListValidator(consoleSettingsOutputValidator)
|
|
102
|
+
}),
|
|
103
|
+
create: Object.freeze({
|
|
104
|
+
method: "POST",
|
|
105
|
+
messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
|
|
106
|
+
bodyValidator: Object.freeze({
|
|
107
|
+
get schema() {
|
|
108
|
+
return buildConsoleSettingsCreateSchema();
|
|
109
|
+
},
|
|
110
|
+
normalize: normalizeConsoleSettingsInput
|
|
111
|
+
}),
|
|
112
|
+
outputValidator: consoleSettingsOutputValidator
|
|
113
|
+
}),
|
|
114
|
+
replace: Object.freeze({
|
|
115
|
+
method: "PUT",
|
|
116
|
+
messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
|
|
117
|
+
bodyValidator: Object.freeze({
|
|
118
|
+
get schema() {
|
|
119
|
+
return buildConsoleSettingsReplaceSchema();
|
|
120
|
+
},
|
|
121
|
+
normalize: normalizeConsoleSettingsInput
|
|
122
|
+
}),
|
|
123
|
+
outputValidator: consoleSettingsOutputValidator
|
|
124
|
+
}),
|
|
125
|
+
patch: Object.freeze({
|
|
126
|
+
method: "PATCH",
|
|
127
|
+
messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
|
|
128
|
+
bodyValidator: Object.freeze({
|
|
129
|
+
get schema() {
|
|
130
|
+
return buildConsoleSettingsPatchSchema();
|
|
131
|
+
},
|
|
132
|
+
normalize: normalizeConsoleSettingsInput
|
|
133
|
+
}),
|
|
134
|
+
outputValidator: consoleSettingsOutputValidator
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export { consoleSettingsResource };
|