@jskit-ai/users-core 0.1.65 → 0.1.67
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 +14 -65
- package/package.json +10 -10
- package/src/server/UsersCoreServiceProvider.js +18 -2
- package/src/server/accountNotifications/accountNotificationsActions.js +3 -5
- package/src/server/accountNotifications/accountNotificationsService.js +3 -2
- package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +12 -11
- package/src/server/accountPreferences/accountPreferencesActions.js +3 -5
- package/src/server/accountPreferences/accountPreferencesService.js +3 -2
- package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +12 -11
- package/src/server/accountProfile/accountProfileActions.js +15 -32
- package/src/server/accountProfile/accountProfileService.js +9 -8
- package/src/server/accountProfile/bootAccountProfileRoutes.js +25 -19
- package/src/server/accountSecurity/accountSecurityActions.js +21 -16
- package/src/server/accountSecurity/accountSecurityService.js +16 -6
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +52 -40
- package/src/server/common/formatters/accountSettingsResponseFormatter.js +8 -18
- package/src/server/common/registerCommonRepositories.js +5 -2
- package/src/server/common/repositories/userProfilesRepository.js +227 -88
- package/src/server/common/repositories/userSettingsRepository.js +108 -100
- package/src/server/common/support/accountSettingsJsonApiTransport.js +10 -0
- package/src/server/usersBootstrapContributor.js +13 -32
- package/src/shared/resources/accountSettingsSchemas.js +83 -0
- package/src/shared/resources/userProfileResource.js +146 -126
- package/src/shared/resources/userSettingsResource.js +376 -353
- package/templates/packages/users/package.descriptor.mjs +4 -5
- package/templates/packages/users/package.json +0 -1
- package/templates/packages/users/src/server/UsersProvider.js +23 -24
- package/templates/packages/users/src/server/actions.js +26 -28
- package/templates/packages/users/src/server/registerRoutes.js +29 -15
- package/templates/packages/users/src/server/repository.js +35 -28
- package/templates/packages/users/src/server/service.js +20 -15
- package/templates/packages/users/src/shared/userResource.js +55 -68
- package/templates/packages/users-workspace/package.descriptor.mjs +4 -5
- package/templates/packages/users-workspace/src/server/UsersProvider.js +23 -24
- package/templates/packages/users-workspace/src/server/actions.js +28 -28
- package/templates/packages/users-workspace/src/server/registerRoutes.js +34 -16
- package/test/accountSecurityService.test.js +32 -0
- package/test/providerLifecycle.test.js +63 -0
- package/test/registerCommonRepositories.test.js +28 -8
- package/test/repositoryContracts.test.js +177 -28
- package/test/resourcesCanonical.test.js +18 -11
- package/test/userSettingsInternalResource.test.js +8 -0
- package/test/userSettingsResource.test.js +24 -7
- package/test/usersBootstrapContributor.test.js +40 -1
- package/test/usersPackageScaffoldContract.test.js +82 -4
- package/test/usersRouteRequestInputValidator.test.js +92 -23
- package/test/usersRouteResources.test.js +28 -18
- package/src/server/common/resources/userProfilesResource.js +0 -203
- package/src/server/common/validators/authenticatedUserValidator.js +0 -43
- package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
- package/src/shared/resources/userSettingsFields.js +0 -76
- package/templates/packages/main/src/shared/resources/userSettingsFields.js +0 -138
- package/templates/packages/users/src/server/actionIds.js +0 -6
- package/templates/packages/users/src/server/listConfig.js +0 -16
- package/test/settingsFieldRegistriesSingleton.test.js +0 -14
- package/test-support/registerDefaultSettingsFields.js +0 -2
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Type } from "@fastify/type-provider-typebox";
|
|
2
|
-
import { normalizeObjectInput, recordIdInputSchema } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
4
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
-
|
|
6
|
-
function normalizeAuthenticatedUser(input = {}) {
|
|
7
|
-
const source = normalizeObjectInput(input);
|
|
8
|
-
const id = normalizeRecordId(source.id, { fallback: null });
|
|
9
|
-
if (!id) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const email = normalizeLowerText(source.email);
|
|
14
|
-
return {
|
|
15
|
-
id,
|
|
16
|
-
email,
|
|
17
|
-
username: normalizeLowerText(source.username),
|
|
18
|
-
displayName: normalizeText(source.displayName) || email || `User ${id}`,
|
|
19
|
-
authProvider: normalizeLowerText(source.authProvider),
|
|
20
|
-
authProviderUserSid: normalizeText(source.authProviderUserSid),
|
|
21
|
-
avatarStorageKey: source.avatarStorageKey ? normalizeText(source.avatarStorageKey) : null,
|
|
22
|
-
avatarVersion: source.avatarVersion == null ? null : String(source.avatarVersion)
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const authenticatedUserValidator = Object.freeze({
|
|
27
|
-
schema: Type.Object(
|
|
28
|
-
{
|
|
29
|
-
id: recordIdInputSchema,
|
|
30
|
-
email: Type.String({ minLength: 1 }),
|
|
31
|
-
username: Type.Optional(Type.String()),
|
|
32
|
-
displayName: Type.Optional(Type.String()),
|
|
33
|
-
authProvider: Type.Optional(Type.String()),
|
|
34
|
-
authProviderUserSid: Type.Optional(Type.String()),
|
|
35
|
-
avatarStorageKey: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
36
|
-
avatarVersion: Type.Optional(Type.Union([Type.String(), Type.Number(), Type.Null()]))
|
|
37
|
-
},
|
|
38
|
-
{ additionalProperties: true }
|
|
39
|
-
),
|
|
40
|
-
normalize: normalizeAuthenticatedUser
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
export { authenticatedUserValidator };
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
2
|
-
import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
|
|
3
|
-
|
|
4
|
-
const USER_SETTINGS_SECTIONS = Object.freeze({
|
|
5
|
-
PREFERENCES: "preferences",
|
|
6
|
-
NOTIFICATIONS: "notifications"
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const USER_SETTINGS_SECTION_VALUES = Object.freeze(Object.values(USER_SETTINGS_SECTIONS));
|
|
10
|
-
|
|
11
|
-
const userSettingsFields = resolveGlobalArrayRegistry("jskit.users-core.userSettingsFields");
|
|
12
|
-
|
|
13
|
-
function defineField(field = {}) {
|
|
14
|
-
const key = normalizeText(field.key);
|
|
15
|
-
if (!key) {
|
|
16
|
-
throw new TypeError("userSettingsFields.defineField requires field.key.");
|
|
17
|
-
}
|
|
18
|
-
if (userSettingsFields.some((entry) => entry.key === key)) {
|
|
19
|
-
throw new Error(`userSettingsFields.defineField duplicate key: ${key}`);
|
|
20
|
-
}
|
|
21
|
-
if (!field.inputSchema || typeof field.inputSchema !== "object") {
|
|
22
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires inputSchema.`);
|
|
23
|
-
}
|
|
24
|
-
if (!field.outputSchema || typeof field.outputSchema !== "object") {
|
|
25
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires outputSchema.`);
|
|
26
|
-
}
|
|
27
|
-
const repository = field?.repository;
|
|
28
|
-
if (!repository || typeof repository !== "object" || Array.isArray(repository)) {
|
|
29
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires repository.column.`);
|
|
30
|
-
}
|
|
31
|
-
const repositoryColumn = normalizeText(repository.column);
|
|
32
|
-
if (!repositoryColumn) {
|
|
33
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires repository.column.`);
|
|
34
|
-
}
|
|
35
|
-
const section = normalizeLowerText(field.section);
|
|
36
|
-
if (!USER_SETTINGS_SECTION_VALUES.includes(section)) {
|
|
37
|
-
throw new TypeError(
|
|
38
|
-
`userSettingsFields.defineField("${key}") requires section to be one of: ${USER_SETTINGS_SECTION_VALUES.join(", ")}.`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
if (typeof field.normalizeInput !== "function") {
|
|
42
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires normalizeInput.`);
|
|
43
|
-
}
|
|
44
|
-
if (typeof field.normalizeOutput !== "function") {
|
|
45
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires normalizeOutput.`);
|
|
46
|
-
}
|
|
47
|
-
if (typeof field.resolveDefault !== "function") {
|
|
48
|
-
throw new TypeError(`userSettingsFields.defineField("${key}") requires resolveDefault.`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
userSettingsFields.push({
|
|
52
|
-
key,
|
|
53
|
-
section,
|
|
54
|
-
repository: Object.freeze({
|
|
55
|
-
column: repositoryColumn
|
|
56
|
-
}),
|
|
57
|
-
required: field.required !== false,
|
|
58
|
-
includeInBootstrap: field.includeInBootstrap !== false,
|
|
59
|
-
inputSchema: field.inputSchema,
|
|
60
|
-
outputSchema: field.outputSchema,
|
|
61
|
-
normalizeInput: field.normalizeInput,
|
|
62
|
-
normalizeOutput: field.normalizeOutput,
|
|
63
|
-
resolveDefault: field.resolveDefault
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function resetUserSettingsFields() {
|
|
68
|
-
userSettingsFields.splice(0, userSettingsFields.length);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export {
|
|
72
|
-
USER_SETTINGS_SECTIONS,
|
|
73
|
-
defineField,
|
|
74
|
-
resetUserSettingsFields,
|
|
75
|
-
userSettingsFields
|
|
76
|
-
};
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
-
import { DEFAULT_USER_SETTINGS } from "@jskit-ai/users-core/shared/settings";
|
|
4
|
-
import {
|
|
5
|
-
USER_SETTINGS_SECTIONS,
|
|
6
|
-
defineField,
|
|
7
|
-
resetUserSettingsFields
|
|
8
|
-
} from "@jskit-ai/users-core/shared/resources/userSettingsFields";
|
|
9
|
-
|
|
10
|
-
function normalizePositiveInteger(value, fallback) {
|
|
11
|
-
const numericValue = Number(value);
|
|
12
|
-
if (Number.isInteger(numericValue) && numericValue > 0) {
|
|
13
|
-
return numericValue;
|
|
14
|
-
}
|
|
15
|
-
return Number(fallback);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
resetUserSettingsFields();
|
|
19
|
-
|
|
20
|
-
defineField({
|
|
21
|
-
key: "theme",
|
|
22
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
23
|
-
repository: { column: "theme" },
|
|
24
|
-
required: true,
|
|
25
|
-
inputSchema: Type.String({ minLength: 1, maxLength: 32 }),
|
|
26
|
-
outputSchema: Type.String({ minLength: 1, maxLength: 32 }),
|
|
27
|
-
normalizeInput: (value) => normalizeText(value),
|
|
28
|
-
normalizeOutput: (value) => normalizeText(value) || DEFAULT_USER_SETTINGS.theme,
|
|
29
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.theme
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
defineField({
|
|
33
|
-
key: "locale",
|
|
34
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
35
|
-
repository: { column: "locale" },
|
|
36
|
-
required: true,
|
|
37
|
-
inputSchema: Type.String({ minLength: 1, maxLength: 24 }),
|
|
38
|
-
outputSchema: Type.String({ minLength: 1, maxLength: 24 }),
|
|
39
|
-
normalizeInput: (value) => normalizeLowerText(value),
|
|
40
|
-
normalizeOutput: (value) => normalizeLowerText(value) || DEFAULT_USER_SETTINGS.locale,
|
|
41
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.locale
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
defineField({
|
|
45
|
-
key: "timeZone",
|
|
46
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
47
|
-
repository: { column: "time_zone" },
|
|
48
|
-
required: true,
|
|
49
|
-
inputSchema: Type.String({ minLength: 1, maxLength: 64 }),
|
|
50
|
-
outputSchema: Type.String({ minLength: 1, maxLength: 64 }),
|
|
51
|
-
normalizeInput: (value) => normalizeText(value),
|
|
52
|
-
normalizeOutput: (value) => normalizeText(value) || DEFAULT_USER_SETTINGS.timeZone,
|
|
53
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.timeZone
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
defineField({
|
|
57
|
-
key: "dateFormat",
|
|
58
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
59
|
-
repository: { column: "date_format" },
|
|
60
|
-
required: true,
|
|
61
|
-
inputSchema: Type.String({ minLength: 1, maxLength: 32 }),
|
|
62
|
-
outputSchema: Type.String({ minLength: 1, maxLength: 32 }),
|
|
63
|
-
normalizeInput: (value) => normalizeText(value),
|
|
64
|
-
normalizeOutput: (value) => normalizeText(value) || DEFAULT_USER_SETTINGS.dateFormat,
|
|
65
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.dateFormat
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
defineField({
|
|
69
|
-
key: "numberFormat",
|
|
70
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
71
|
-
repository: { column: "number_format" },
|
|
72
|
-
required: true,
|
|
73
|
-
inputSchema: Type.String({ minLength: 1, maxLength: 32 }),
|
|
74
|
-
outputSchema: Type.String({ minLength: 1, maxLength: 32 }),
|
|
75
|
-
normalizeInput: (value) => normalizeText(value),
|
|
76
|
-
normalizeOutput: (value) => normalizeText(value) || DEFAULT_USER_SETTINGS.numberFormat,
|
|
77
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.numberFormat
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
defineField({
|
|
81
|
-
key: "currencyCode",
|
|
82
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
83
|
-
repository: { column: "currency_code" },
|
|
84
|
-
required: true,
|
|
85
|
-
inputSchema: Type.String({ minLength: 3, maxLength: 3, pattern: "^[A-Za-z]{3}$" }),
|
|
86
|
-
outputSchema: Type.String({ minLength: 3, maxLength: 3, pattern: "^[A-Z]{3}$" }),
|
|
87
|
-
normalizeInput: (value) => normalizeText(value).toUpperCase(),
|
|
88
|
-
normalizeOutput: (value) => normalizeText(value).toUpperCase() || DEFAULT_USER_SETTINGS.currencyCode,
|
|
89
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.currencyCode
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
defineField({
|
|
93
|
-
key: "avatarSize",
|
|
94
|
-
section: USER_SETTINGS_SECTIONS.PREFERENCES,
|
|
95
|
-
repository: { column: "avatar_size" },
|
|
96
|
-
required: true,
|
|
97
|
-
inputSchema: Type.Integer({ minimum: 1 }),
|
|
98
|
-
outputSchema: Type.Integer({ minimum: 1 }),
|
|
99
|
-
normalizeInput: (value) => Number(value),
|
|
100
|
-
normalizeOutput: (value) => normalizePositiveInteger(value, DEFAULT_USER_SETTINGS.avatarSize),
|
|
101
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.avatarSize
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
defineField({
|
|
105
|
-
key: "productUpdates",
|
|
106
|
-
section: USER_SETTINGS_SECTIONS.NOTIFICATIONS,
|
|
107
|
-
repository: { column: "notify_product_updates" },
|
|
108
|
-
required: true,
|
|
109
|
-
inputSchema: Type.Boolean(),
|
|
110
|
-
outputSchema: Type.Boolean(),
|
|
111
|
-
normalizeInput: (value) => value,
|
|
112
|
-
normalizeOutput: (value) => Boolean(value),
|
|
113
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.productUpdates
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
defineField({
|
|
117
|
-
key: "accountActivity",
|
|
118
|
-
section: USER_SETTINGS_SECTIONS.NOTIFICATIONS,
|
|
119
|
-
repository: { column: "notify_account_activity" },
|
|
120
|
-
required: true,
|
|
121
|
-
inputSchema: Type.Boolean(),
|
|
122
|
-
outputSchema: Type.Boolean(),
|
|
123
|
-
normalizeInput: (value) => value,
|
|
124
|
-
normalizeOutput: (value) => Boolean(value),
|
|
125
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.accountActivity
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
defineField({
|
|
129
|
-
key: "securityAlerts",
|
|
130
|
-
section: USER_SETTINGS_SECTIONS.NOTIFICATIONS,
|
|
131
|
-
repository: { column: "notify_security_alerts" },
|
|
132
|
-
required: true,
|
|
133
|
-
inputSchema: Type.Boolean(),
|
|
134
|
-
outputSchema: Type.Boolean(),
|
|
135
|
-
normalizeInput: (value) => value,
|
|
136
|
-
normalizeOutput: (value) => Boolean(value),
|
|
137
|
-
resolveDefault: () => DEFAULT_USER_SETTINGS.securityAlerts
|
|
138
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const LIST_CONFIG = Object.freeze({
|
|
2
|
-
searchColumns: ["display_name", "email", "username"],
|
|
3
|
-
orderBy: [
|
|
4
|
-
{
|
|
5
|
-
column: "display_name",
|
|
6
|
-
direction: "asc",
|
|
7
|
-
nulls: "last"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
column: "email",
|
|
11
|
-
direction: "asc"
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export { LIST_CONFIG };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
async function importWithIdentity(url, identity) {
|
|
5
|
-
return import(`${url.href}?identity=${identity}`);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
test("settings field registries stay shared across module identities", async () => {
|
|
9
|
-
const userModuleUrl = new URL("../src/shared/resources/userSettingsFields.js", import.meta.url);
|
|
10
|
-
|
|
11
|
-
const userA = await importWithIdentity(userModuleUrl, "user-a");
|
|
12
|
-
const userB = await importWithIdentity(userModuleUrl, "user-b");
|
|
13
|
-
assert.equal(userA.userSettingsFields, userB.userSettingsFields);
|
|
14
|
-
});
|