@jskit-ai/users-core 0.1.42 → 0.1.43
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 +6 -6
- package/package.json +6 -6
- package/src/server/accountProfile/avatarStorageService.js +3 -3
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +12 -13
- package/src/server/common/formatters/workspaceFormatter.js +3 -2
- package/src/server/common/repositories/repositoryUtils.js +12 -3
- package/src/server/common/repositories/userSettingsRepository.js +35 -11
- package/src/server/common/repositories/usersRepository.js +44 -27
- package/src/server/common/repositories/workspaceInvitesRepository.js +49 -13
- package/src/server/common/repositories/workspaceMembershipsRepository.js +55 -22
- package/src/server/common/repositories/workspacesRepository.js +41 -11
- package/src/server/common/services/accountContextService.js +3 -2
- package/src/server/common/services/authProfileSyncService.js +7 -5
- package/src/server/common/services/workspaceContextService.js +4 -1
- package/src/server/common/support/realtimeServiceEvents.js +4 -3
- package/src/server/common/validators/authenticatedUserValidator.js +5 -4
- package/src/server/consoleSettings/consoleService.js +3 -3
- package/src/server/consoleSettings/consoleSettingsRepository.js +10 -6
- package/src/server/usersBootstrapContributor.js +7 -3
- package/src/server/workspaceBootstrapContributor.js +5 -1
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +6 -4
- package/src/server/workspaceMembers/workspaceMembersService.js +23 -11
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +5 -4
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +3 -2
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +29 -10
- package/src/server/workspaceSettings/workspaceSettingsService.js +3 -2
- package/src/shared/resources/workspaceMembersResource.js +25 -21
- package/src/shared/resources/workspacePendingInvitationsResource.js +7 -12
- package/src/shared/resources/workspaceResource.js +13 -9
- package/src/shared/resources/workspaceSettingsResource.js +7 -5
- package/templates/migrations/users_core_console_owner.cjs +1 -1
- package/templates/migrations/users_core_generic_initial.cjs +4 -4
- package/templates/migrations/users_core_profile_username.cjs +1 -1
- package/test/authProfileSyncService.test.js +7 -4
- package/test/avatarStorageService.test.js +3 -3
- package/test/consoleService.test.js +9 -9
- package/test/registerServiceRealtimeEvents.test.js +9 -9
- package/test/repositoryContracts.test.js +40 -0
- package/test/usersBootstrapContributor.test.js +4 -4
- package/test/workspaceBootstrapContributor.test.js +1 -1
- package/test/workspaceInvitesRepository.test.js +3 -3
- package/test/workspaceMembersService.test.js +34 -34
- package/test/workspacePendingInvitationsResource.test.js +4 -4
- package/test/workspacePendingInvitationsService.test.js +11 -11
- package/test/workspaceRouteVisibilityResolver.test.js +6 -6
- package/test/workspaceService.test.js +33 -33
- package/test/workspaceSettingsRepository.test.js +7 -6
- package/test/workspaceSettingsResource.test.js +2 -2
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.43",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Users/account runtime plus HTTP routes for account and console features.",
|
|
7
7
|
dependsOn: [
|
|
@@ -138,11 +138,11 @@ export default Object.freeze({
|
|
|
138
138
|
mutations: {
|
|
139
139
|
dependencies: {
|
|
140
140
|
runtime: {
|
|
141
|
-
"@jskit-ai/auth-core": "0.1.
|
|
142
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
143
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
144
|
-
"@jskit-ai/kernel": "0.1.
|
|
145
|
-
"@jskit-ai/uploads-runtime": "0.1.
|
|
141
|
+
"@jskit-ai/auth-core": "0.1.32",
|
|
142
|
+
"@jskit-ai/database-runtime": "0.1.33",
|
|
143
|
+
"@jskit-ai/http-runtime": "0.1.32",
|
|
144
|
+
"@jskit-ai/kernel": "0.1.33",
|
|
145
|
+
"@jskit-ai/uploads-runtime": "0.1.11",
|
|
146
146
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
147
147
|
typebox: "^1.0.81"
|
|
148
148
|
},
|
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.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -24,11 +24,11 @@
|
|
|
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.
|
|
31
|
-
"@jskit-ai/uploads-runtime": "0.1.
|
|
27
|
+
"@jskit-ai/auth-core": "0.1.32",
|
|
28
|
+
"@jskit-ai/database-runtime": "0.1.33",
|
|
29
|
+
"@jskit-ai/http-runtime": "0.1.32",
|
|
30
|
+
"@jskit-ai/kernel": "0.1.33",
|
|
31
|
+
"@jskit-ai/uploads-runtime": "0.1.11",
|
|
32
32
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
33
33
|
"typebox": "^1.0.81"
|
|
34
34
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import {
|
|
3
3
|
createUploadStorageService,
|
|
4
4
|
detectCommonMimeTypeFromBuffer
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
const AVATAR_STORAGE_PREFIX = "users/avatars";
|
|
8
8
|
|
|
9
9
|
function buildAvatarStorageKey(userId) {
|
|
10
|
-
const normalizedUserId =
|
|
10
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
11
11
|
if (!normalizedUserId) {
|
|
12
|
-
throw new TypeError("Avatar storage requires a
|
|
12
|
+
throw new TypeError("Avatar storage requires a valid user id.");
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
return `${AVATAR_STORAGE_PREFIX}/${normalizedUserId}/avatar`;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { normalizeOpaqueId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
-
import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
|
|
1
|
+
import { normalizeOpaqueId, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
2
|
|
|
4
|
-
function buildVisibilityContribution({ visibility, scopeOwnerId =
|
|
3
|
+
function buildVisibilityContribution({ visibility, scopeOwnerId = null, userId = null } = {}) {
|
|
5
4
|
const requiresActorScope = visibility === "workspace_user";
|
|
6
5
|
const contribution = {
|
|
7
6
|
scopeKind: requiresActorScope ? "workspace_user" : "workspace",
|
|
8
7
|
requiresActorScope
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
if (scopeOwnerId
|
|
10
|
+
if (scopeOwnerId) {
|
|
12
11
|
contribution.scopeOwnerId = scopeOwnerId;
|
|
13
12
|
}
|
|
14
|
-
if (requiresActorScope &&
|
|
15
|
-
contribution.
|
|
13
|
+
if (requiresActorScope && userId != null) {
|
|
14
|
+
contribution.userId = userId;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
return contribution;
|
|
@@ -31,10 +30,10 @@ function createWorkspaceRouteVisibilityResolver({ workspaceService } = {}) {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
const actor = context?.actor || request?.user || null;
|
|
34
|
-
const
|
|
33
|
+
const userId = normalizeOpaqueId(actor?.id);
|
|
35
34
|
const workspace =
|
|
36
35
|
context?.workspace || context?.requestMeta?.resolvedWorkspaceContext?.workspace || request?.workspace || null;
|
|
37
|
-
const scopeOwnerId =
|
|
36
|
+
const scopeOwnerId = normalizeRecordId(workspace?.id, { fallback: null });
|
|
38
37
|
if (!scopeOwnerId) {
|
|
39
38
|
const workspaceSlug = normalizeText(input?.workspaceSlug).toLowerCase();
|
|
40
39
|
|
|
@@ -42,7 +41,7 @@ function createWorkspaceRouteVisibilityResolver({ workspaceService } = {}) {
|
|
|
42
41
|
return visibility === "workspace_user"
|
|
43
42
|
? buildVisibilityContribution({
|
|
44
43
|
visibility,
|
|
45
|
-
|
|
44
|
+
userId
|
|
46
45
|
})
|
|
47
46
|
: {};
|
|
48
47
|
}
|
|
@@ -50,12 +49,12 @@ function createWorkspaceRouteVisibilityResolver({ workspaceService } = {}) {
|
|
|
50
49
|
const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(actor, workspaceSlug, {
|
|
51
50
|
request
|
|
52
51
|
});
|
|
53
|
-
const resolvedWorkspaceOwnerId =
|
|
52
|
+
const resolvedWorkspaceOwnerId = normalizeRecordId(resolvedWorkspaceContext?.workspace?.id, { fallback: null });
|
|
54
53
|
if (!resolvedWorkspaceOwnerId) {
|
|
55
54
|
return visibility === "workspace_user"
|
|
56
55
|
? buildVisibilityContribution({
|
|
57
56
|
visibility,
|
|
58
|
-
|
|
57
|
+
userId
|
|
59
58
|
})
|
|
60
59
|
: {};
|
|
61
60
|
}
|
|
@@ -63,14 +62,14 @@ function createWorkspaceRouteVisibilityResolver({ workspaceService } = {}) {
|
|
|
63
62
|
return buildVisibilityContribution({
|
|
64
63
|
visibility,
|
|
65
64
|
scopeOwnerId: resolvedWorkspaceOwnerId,
|
|
66
|
-
|
|
65
|
+
userId
|
|
67
66
|
});
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
return buildVisibilityContribution({
|
|
71
70
|
visibility,
|
|
72
71
|
scopeOwnerId,
|
|
73
|
-
|
|
72
|
+
userId
|
|
74
73
|
});
|
|
75
74
|
}
|
|
76
75
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { resolveWorkspaceThemePalettes } from "../../../shared/settings.js";
|
|
2
2
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
4
|
|
|
4
5
|
function mapWorkspaceSummary(workspace, membership) {
|
|
5
6
|
return {
|
|
6
|
-
id:
|
|
7
|
+
id: normalizeRecordId(workspace.id, { fallback: "" }),
|
|
7
8
|
slug: normalizeText(workspace.slug),
|
|
8
9
|
name: normalizeText(workspace.name),
|
|
9
10
|
avatarUrl: normalizeText(workspace.avatarUrl),
|
|
@@ -39,7 +40,7 @@ function mapMembershipSummary(membership, workspace) {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
return {
|
|
42
|
-
workspaceId:
|
|
43
|
+
workspaceId: normalizeRecordId(workspace?.id || membership.workspaceId, { fallback: "" }),
|
|
43
44
|
roleSid: normalizeLowerText(membership.roleSid || "member") || "member",
|
|
44
45
|
status: normalizeLowerText(membership.status || "active") || "active"
|
|
45
46
|
};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
normalizeDbRecordId,
|
|
3
|
+
toInsertDateTime,
|
|
4
|
+
toNullableDateTime,
|
|
5
|
+
toIsoString,
|
|
6
|
+
createWithTransaction
|
|
7
|
+
} from "@jskit-ai/database-runtime/shared";
|
|
2
8
|
import { isDuplicateEntryError } from "@jskit-ai/database-runtime/shared/duplicateEntry";
|
|
3
|
-
import {
|
|
9
|
+
import { normalizeLowerText, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
10
|
|
|
5
11
|
function nowDb() {
|
|
6
12
|
return toInsertDateTime();
|
|
@@ -42,9 +48,12 @@ export {
|
|
|
42
48
|
isDuplicateEntryError,
|
|
43
49
|
normalizeText,
|
|
44
50
|
normalizeLowerText,
|
|
51
|
+
normalizeRecordId,
|
|
52
|
+
normalizeDbRecordId,
|
|
45
53
|
nowDb,
|
|
46
54
|
toNullableIso,
|
|
47
55
|
uniqueSorted,
|
|
48
56
|
parseJson,
|
|
49
|
-
toDbJson
|
|
57
|
+
toDbJson,
|
|
58
|
+
createWithTransaction
|
|
50
59
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
normalizeDbRecordId,
|
|
3
|
+
normalizeRecordId,
|
|
2
4
|
toIsoString,
|
|
3
5
|
nowDb,
|
|
4
|
-
isDuplicateEntryError
|
|
6
|
+
isDuplicateEntryError,
|
|
7
|
+
createWithTransaction
|
|
5
8
|
} from "./repositoryUtils.js";
|
|
6
9
|
import { DEFAULT_USER_SETTINGS } from "../../../shared/settings.js";
|
|
7
10
|
import {
|
|
@@ -14,7 +17,7 @@ function mapRow(row) {
|
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
const mapped = {
|
|
17
|
-
userId:
|
|
20
|
+
userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
|
|
18
21
|
passwordSignInEnabled: row.password_sign_in_enabled == null ? true : Boolean(row.password_sign_in_enabled),
|
|
19
22
|
passwordSetupRequired: row.password_setup_required == null ? false : Boolean(row.password_setup_required),
|
|
20
23
|
createdAt: toIsoString(row.created_at),
|
|
@@ -45,8 +48,13 @@ function normalizeBoolean(value, fallback = false) {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
function createInsertPayload(userId) {
|
|
51
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
52
|
+
if (!normalizedUserId) {
|
|
53
|
+
throw new TypeError("userSettingsRepository requires a valid user id.");
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
const payload = {
|
|
49
|
-
user_id:
|
|
57
|
+
user_id: normalizedUserId,
|
|
50
58
|
password_sign_in_enabled: DEFAULT_USER_SETTINGS.passwordSignInEnabled,
|
|
51
59
|
password_setup_required: DEFAULT_USER_SETTINGS.passwordSetupRequired,
|
|
52
60
|
created_at: nowDb(),
|
|
@@ -74,35 +82,50 @@ function createRepository(knex) {
|
|
|
74
82
|
if (typeof knex !== "function") {
|
|
75
83
|
throw new TypeError("userSettingsRepository requires knex.");
|
|
76
84
|
}
|
|
85
|
+
const withTransaction = createWithTransaction(knex);
|
|
77
86
|
|
|
78
87
|
async function findByUserId(userId, options = {}) {
|
|
79
88
|
const client = options?.trx || knex;
|
|
80
|
-
const
|
|
89
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
90
|
+
if (!normalizedUserId) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const row = await client("user_settings").where({ user_id: normalizedUserId }).first();
|
|
81
95
|
return mapRow(row);
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
async function ensureForUserId(userId, options = {}) {
|
|
85
99
|
const client = options?.trx || knex;
|
|
86
|
-
const
|
|
87
|
-
|
|
100
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
101
|
+
if (!normalizedUserId) {
|
|
102
|
+
throw new TypeError("userSettingsRepository.ensureForUserId requires a valid user id.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const existing = await findByUserId(normalizedUserId, { trx: client });
|
|
88
106
|
if (existing) {
|
|
89
107
|
return existing;
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
try {
|
|
93
|
-
await client("user_settings").insert(createInsertPayload(
|
|
111
|
+
await client("user_settings").insert(createInsertPayload(normalizedUserId));
|
|
94
112
|
} catch (error) {
|
|
95
113
|
if (!isDuplicateEntryError(error)) {
|
|
96
114
|
throw error;
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
|
|
100
|
-
return findByUserId(
|
|
118
|
+
return findByUserId(normalizedUserId, { trx: client });
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
async function patchUserSettings(userId, patch = {}, options = {}) {
|
|
104
122
|
const client = options?.trx || knex;
|
|
105
|
-
const
|
|
123
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
124
|
+
if (!normalizedUserId) {
|
|
125
|
+
throw new TypeError("userSettingsRepository.patchUserSettings requires a valid user id.");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const ensured = await ensureForUserId(normalizedUserId, { trx: client });
|
|
106
129
|
const source = patch && typeof patch === "object" ? patch : {};
|
|
107
130
|
|
|
108
131
|
const dbPatch = {
|
|
@@ -125,8 +148,8 @@ function createRepository(knex) {
|
|
|
125
148
|
if (Object.hasOwn(source, "passwordSetupRequired")) {
|
|
126
149
|
dbPatch.password_setup_required = normalizeBoolean(source.passwordSetupRequired, ensured.passwordSetupRequired);
|
|
127
150
|
}
|
|
128
|
-
await client("user_settings").where({ user_id:
|
|
129
|
-
return findByUserId(
|
|
151
|
+
await client("user_settings").where({ user_id: normalizedUserId }).update(dbPatch);
|
|
152
|
+
return findByUserId(normalizedUserId, { trx: client });
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
async function updatePreferences(userId, patch = {}, options = {}) {
|
|
@@ -155,6 +178,7 @@ function createRepository(knex) {
|
|
|
155
178
|
}
|
|
156
179
|
|
|
157
180
|
return Object.freeze({
|
|
181
|
+
withTransaction,
|
|
158
182
|
findByUserId,
|
|
159
183
|
ensureForUserId,
|
|
160
184
|
patchUserSettings,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isDuplicateEntryError,
|
|
3
3
|
normalizeLowerText,
|
|
4
|
+
normalizeDbRecordId,
|
|
5
|
+
normalizeRecordId,
|
|
4
6
|
normalizeText,
|
|
5
7
|
toIsoString,
|
|
6
8
|
toNullableDateTime,
|
|
7
9
|
toNullableIso,
|
|
8
|
-
nowDb
|
|
10
|
+
nowDb,
|
|
11
|
+
createWithTransaction
|
|
9
12
|
} from "./repositoryUtils.js";
|
|
10
13
|
|
|
11
14
|
const USERNAME_MAX_LENGTH = 120;
|
|
@@ -55,7 +58,7 @@ function mapProfileRow(row) {
|
|
|
55
58
|
return null;
|
|
56
59
|
}
|
|
57
60
|
return {
|
|
58
|
-
id:
|
|
61
|
+
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
59
62
|
authProvider: normalizeLowerText(row.auth_provider),
|
|
60
63
|
authProviderUserSid: normalizeText(row.auth_provider_user_sid),
|
|
61
64
|
email: normalizeLowerText(row.email),
|
|
@@ -90,11 +93,13 @@ function createDuplicateEmailConflictError() {
|
|
|
90
93
|
return error;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
async function resolveUniqueUsername(client, baseUsername, { excludeUserId =
|
|
96
|
+
async function resolveUniqueUsername(client, baseUsername, { excludeUserId = null } = {}) {
|
|
97
|
+
const normalizedExcludeUserId = normalizeDbRecordId(excludeUserId, { fallback: null });
|
|
94
98
|
for (let suffix = 0; suffix < 1000; suffix += 1) {
|
|
95
99
|
const candidate = buildUsernameCandidate(baseUsername, suffix);
|
|
96
100
|
const existing = await client("users").where({ username: candidate }).first();
|
|
97
|
-
|
|
101
|
+
const existingId = normalizeDbRecordId(existing?.id, { fallback: null });
|
|
102
|
+
if (!existing || existingId === normalizedExcludeUserId) {
|
|
98
103
|
return candidate;
|
|
99
104
|
}
|
|
100
105
|
}
|
|
@@ -106,10 +111,16 @@ function createRepository(knex) {
|
|
|
106
111
|
if (typeof knex !== "function") {
|
|
107
112
|
throw new TypeError("usersRepository requires knex.");
|
|
108
113
|
}
|
|
114
|
+
const withTransaction = createWithTransaction(knex);
|
|
109
115
|
|
|
110
116
|
async function findById(userId, options = {}) {
|
|
117
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
118
|
+
if (!normalizedUserId) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
111
122
|
const client = options?.trx || knex;
|
|
112
|
-
const row = await client("users").where({ id:
|
|
123
|
+
const row = await client("users").where({ id: normalizedUserId }).first();
|
|
113
124
|
return mapProfileRow(row);
|
|
114
125
|
}
|
|
115
126
|
|
|
@@ -130,42 +141,56 @@ function createRepository(knex) {
|
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
async function updateDisplayNameById(userId, displayName, options = {}) {
|
|
144
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
145
|
+
if (!normalizedUserId) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
133
149
|
const client = options?.trx || knex;
|
|
134
150
|
await client("users")
|
|
135
|
-
.where({ id:
|
|
151
|
+
.where({ id: normalizedUserId })
|
|
136
152
|
.update({
|
|
137
153
|
display_name: normalizeText(displayName)
|
|
138
154
|
});
|
|
139
|
-
return findById(
|
|
155
|
+
return findById(normalizedUserId, { trx: client });
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
async function updateAvatarById(userId, avatar = {}, options = {}) {
|
|
159
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
160
|
+
if (!normalizedUserId) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
143
164
|
const client = options?.trx || knex;
|
|
144
165
|
await client("users")
|
|
145
|
-
.where({ id:
|
|
166
|
+
.where({ id: normalizedUserId })
|
|
146
167
|
.update({
|
|
147
168
|
avatar_storage_key: avatar.avatarStorageKey || null,
|
|
148
169
|
avatar_version: avatar.avatarVersion == null ? null : String(avatar.avatarVersion),
|
|
149
170
|
avatar_updated_at: toNullableDateTime(avatar.avatarUpdatedAt) || nowDb()
|
|
150
171
|
});
|
|
151
172
|
|
|
152
|
-
return findById(
|
|
173
|
+
return findById(normalizedUserId, { trx: client });
|
|
153
174
|
}
|
|
154
175
|
|
|
155
176
|
async function clearAvatarById(userId, options = {}) {
|
|
177
|
+
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
178
|
+
if (!normalizedUserId) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
156
182
|
const client = options?.trx || knex;
|
|
157
183
|
await client("users")
|
|
158
|
-
.where({ id:
|
|
184
|
+
.where({ id: normalizedUserId })
|
|
159
185
|
.update({
|
|
160
186
|
avatar_storage_key: null,
|
|
161
187
|
avatar_version: null,
|
|
162
188
|
avatar_updated_at: null
|
|
163
189
|
});
|
|
164
|
-
return findById(
|
|
190
|
+
return findById(normalizedUserId, { trx: client });
|
|
165
191
|
}
|
|
166
192
|
|
|
167
193
|
async function upsert(profileLike = {}, options = {}) {
|
|
168
|
-
const client = options?.trx || knex;
|
|
169
194
|
const identity = normalizeIdentity(profileLike);
|
|
170
195
|
if (!identity) {
|
|
171
196
|
throw new TypeError("upsert requires provider/authProvider and providerUserId/authProviderUserSid.");
|
|
@@ -191,7 +216,7 @@ function createRepository(knex) {
|
|
|
191
216
|
const username = existingUsername || (await resolveUniqueUsername(trx, requestedUsername || usernameBaseFromEmail(email), {
|
|
192
217
|
excludeUserId: existing.id
|
|
193
218
|
}));
|
|
194
|
-
await trx("users").where({ id: existing.id }).update({
|
|
219
|
+
await trx("users").where({ id: normalizeDbRecordId(existing.id, { fallback: null }) }).update({
|
|
195
220
|
email,
|
|
196
221
|
display_name: displayName,
|
|
197
222
|
username
|
|
@@ -218,34 +243,26 @@ function createRepository(knex) {
|
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
245
|
|
|
221
|
-
const
|
|
222
|
-
return mapProfileRow(
|
|
246
|
+
const resolved = await trx("users").where(where).first();
|
|
247
|
+
return mapProfileRow(resolved);
|
|
223
248
|
};
|
|
224
249
|
|
|
225
250
|
if (options?.trx) {
|
|
226
|
-
return executeUpsert(
|
|
251
|
+
return executeUpsert(options.trx);
|
|
227
252
|
}
|
|
228
253
|
|
|
229
254
|
return knex.transaction(executeUpsert);
|
|
230
255
|
}
|
|
231
256
|
|
|
232
|
-
async function withTransaction(work) {
|
|
233
|
-
if (typeof work !== "function") {
|
|
234
|
-
throw new TypeError("withTransaction requires a callback.");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return knex.transaction((trx) => work(trx));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
257
|
return Object.freeze({
|
|
258
|
+
withTransaction,
|
|
241
259
|
findById,
|
|
242
260
|
findByIdentity,
|
|
243
261
|
updateDisplayNameById,
|
|
244
262
|
updateAvatarById,
|
|
245
263
|
clearAvatarById,
|
|
246
|
-
upsert
|
|
247
|
-
withTransaction
|
|
264
|
+
upsert
|
|
248
265
|
});
|
|
249
266
|
}
|
|
250
267
|
|
|
251
|
-
export { createRepository,
|
|
268
|
+
export { createRepository, mapProfileRow, normalizeIdentity };
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
|
|
1
2
|
import {
|
|
2
3
|
normalizeLowerText,
|
|
4
|
+
normalizeDbRecordId,
|
|
5
|
+
normalizeRecordId,
|
|
3
6
|
normalizeText,
|
|
4
7
|
toIsoString,
|
|
5
8
|
toNullableIso,
|
|
6
9
|
toNullableDateTime,
|
|
7
10
|
nowDb,
|
|
8
|
-
isDuplicateEntryError
|
|
11
|
+
isDuplicateEntryError,
|
|
12
|
+
createWithTransaction
|
|
9
13
|
} from "./repositoryUtils.js";
|
|
10
14
|
|
|
11
15
|
function mapRow(row) {
|
|
@@ -14,13 +18,13 @@ function mapRow(row) {
|
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
return {
|
|
17
|
-
id:
|
|
18
|
-
workspaceId:
|
|
21
|
+
id: normalizeDbRecordId(row.id, { fallback: "" }),
|
|
22
|
+
workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" }),
|
|
19
23
|
email: normalizeLowerText(row.email),
|
|
20
24
|
roleSid: normalizeLowerText(row.role_sid || "member") || "member",
|
|
21
25
|
status: normalizeLowerText(row.status || "pending") || "pending",
|
|
22
26
|
tokenHash: normalizeText(row.token_hash),
|
|
23
|
-
invitedByUserId: row.invited_by_user_id == null ? null :
|
|
27
|
+
invitedByUserId: row.invited_by_user_id == null ? null : normalizeDbRecordId(row.invited_by_user_id, { fallback: null }),
|
|
24
28
|
expiresAt: toNullableIso(row.expires_at),
|
|
25
29
|
acceptedAt: toNullableIso(row.accepted_at),
|
|
26
30
|
revokedAt: toNullableIso(row.revoked_at),
|
|
@@ -43,6 +47,7 @@ function createRepository(knex) {
|
|
|
43
47
|
if (typeof knex !== "function") {
|
|
44
48
|
throw new TypeError("workspaceInvitesRepository requires knex.");
|
|
45
49
|
}
|
|
50
|
+
const withTransaction = createWithTransaction(knex);
|
|
46
51
|
|
|
47
52
|
async function findPendingByTokenHash(tokenHash, options = {}) {
|
|
48
53
|
const client = options?.trx || knex;
|
|
@@ -69,10 +74,15 @@ function createRepository(knex) {
|
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
async function listPendingByWorkspaceIdWithWorkspace(workspaceId, options = {}) {
|
|
77
|
+
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
78
|
+
if (!normalizedWorkspaceId) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
const client = options?.trx || knex;
|
|
73
83
|
const rows = await client("workspace_invites as wi")
|
|
74
84
|
.join("workspaces as w", "w.id", "wi.workspace_id")
|
|
75
|
-
.where({ "wi.workspace_id":
|
|
85
|
+
.where({ "wi.workspace_id": normalizedWorkspaceId, "wi.status": "pending" })
|
|
76
86
|
.orderBy("wi.created_at", "desc")
|
|
77
87
|
.select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
|
|
78
88
|
|
|
@@ -82,14 +92,18 @@ function createRepository(knex) {
|
|
|
82
92
|
async function insert(payload = {}, options = {}) {
|
|
83
93
|
const client = options?.trx || knex;
|
|
84
94
|
const source = payload && typeof payload === "object" ? payload : {};
|
|
95
|
+
const workspaceId = normalizeRecordId(source.workspaceId, { fallback: null });
|
|
96
|
+
if (!workspaceId) {
|
|
97
|
+
throw new TypeError("workspaceInvitesRepository.insert requires workspaceId.");
|
|
98
|
+
}
|
|
85
99
|
|
|
86
100
|
const insertPayload = {
|
|
87
|
-
workspace_id:
|
|
101
|
+
workspace_id: workspaceId,
|
|
88
102
|
email: normalizeLowerText(source.email),
|
|
89
103
|
role_sid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
90
104
|
status: normalizeLowerText(source.status || "pending") || "pending",
|
|
91
105
|
token_hash: normalizeText(source.tokenHash),
|
|
92
|
-
invited_by_user_id: source.invitedByUserId == null ? null :
|
|
106
|
+
invited_by_user_id: source.invitedByUserId == null ? null : normalizeRecordId(source.invitedByUserId, { fallback: null }),
|
|
93
107
|
expires_at: toNullableDateTime(source.expiresAt),
|
|
94
108
|
accepted_at: null,
|
|
95
109
|
revoked_at: null,
|
|
@@ -99,8 +113,8 @@ function createRepository(knex) {
|
|
|
99
113
|
|
|
100
114
|
try {
|
|
101
115
|
const result = await client("workspace_invites").insert(insertPayload);
|
|
102
|
-
const insertedId =
|
|
103
|
-
if (
|
|
116
|
+
const insertedId = resolveInsertedRecordId(result, { fallback: null });
|
|
117
|
+
if (insertedId) {
|
|
104
118
|
const row = await client("workspace_invites").where({ id: insertedId }).first();
|
|
105
119
|
return mapRow(row);
|
|
106
120
|
}
|
|
@@ -118,9 +132,14 @@ function createRepository(knex) {
|
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
async function expirePendingByWorkspaceIdAndEmail(workspaceId, email, options = {}) {
|
|
135
|
+
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
136
|
+
if (!normalizedWorkspaceId) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
const client = options?.trx || knex;
|
|
122
141
|
await client("workspace_invites")
|
|
123
|
-
.where({ workspace_id:
|
|
142
|
+
.where({ workspace_id: normalizedWorkspaceId, email: normalizeLowerText(email), status: "pending" })
|
|
124
143
|
.update({
|
|
125
144
|
status: "expired",
|
|
126
145
|
updated_at: nowDb()
|
|
@@ -128,9 +147,14 @@ function createRepository(knex) {
|
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
async function markAcceptedById(inviteId, options = {}) {
|
|
150
|
+
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
151
|
+
if (!normalizedInviteId) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
131
155
|
const client = options?.trx || knex;
|
|
132
156
|
await client("workspace_invites")
|
|
133
|
-
.where({ id:
|
|
157
|
+
.where({ id: normalizedInviteId })
|
|
134
158
|
.update({
|
|
135
159
|
status: "accepted",
|
|
136
160
|
accepted_at: nowDb(),
|
|
@@ -139,9 +163,14 @@ function createRepository(knex) {
|
|
|
139
163
|
}
|
|
140
164
|
|
|
141
165
|
async function revokeById(inviteId, options = {}) {
|
|
166
|
+
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
167
|
+
if (!normalizedInviteId) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
142
171
|
const client = options?.trx || knex;
|
|
143
172
|
await client("workspace_invites")
|
|
144
|
-
.where({ id:
|
|
173
|
+
.where({ id: normalizedInviteId })
|
|
145
174
|
.update({
|
|
146
175
|
status: "revoked",
|
|
147
176
|
revoked_at: nowDb(),
|
|
@@ -150,14 +179,21 @@ function createRepository(knex) {
|
|
|
150
179
|
}
|
|
151
180
|
|
|
152
181
|
async function findPendingByIdForWorkspace(inviteId, workspaceId, options = {}) {
|
|
182
|
+
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
183
|
+
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
184
|
+
if (!normalizedInviteId || !normalizedWorkspaceId) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
153
188
|
const client = options?.trx || knex;
|
|
154
189
|
const row = await client("workspace_invites")
|
|
155
|
-
.where({ id:
|
|
190
|
+
.where({ id: normalizedInviteId, workspace_id: normalizedWorkspaceId, status: "pending" })
|
|
156
191
|
.first();
|
|
157
192
|
return mapRow(row);
|
|
158
193
|
}
|
|
159
194
|
|
|
160
195
|
return Object.freeze({
|
|
196
|
+
withTransaction,
|
|
161
197
|
findPendingByTokenHash,
|
|
162
198
|
listPendingByEmail,
|
|
163
199
|
listPendingByWorkspaceIdWithWorkspace,
|