@jskit-ai/users-core 0.1.42 → 0.1.44
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/src/server/common/README.md +0 -20
- package/src/server/common/contributors/README.md +0 -11
- package/src/server/common/formatters/README.md +0 -11
- package/src/server/common/repositories/README.md +0 -24
- package/src/server/common/routes/README.md +0 -11
- package/src/server/common/services/README.md +0 -12
- package/src/server/common/validators/README.md +0 -11
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
2
|
import { buildInviteToken, hashInviteToken } from "@jskit-ai/auth-core/server/inviteTokens";
|
|
2
3
|
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
3
4
|
import { OWNER_ROLE_ID, createWorkspaceRoleCatalog, cloneWorkspaceRoleCatalog } from "../../shared/roles.js";
|
|
@@ -61,8 +62,11 @@ function createService({
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
async function updateMemberRole(workspace, payload = {}, options = {}) {
|
|
64
|
-
const memberUserId = payload.memberUserId;
|
|
65
|
+
const memberUserId = normalizeRecordId(payload.memberUserId, { fallback: null });
|
|
65
66
|
const roleSid = payload.roleSid;
|
|
67
|
+
if (!memberUserId) {
|
|
68
|
+
throw new AppError(400, "Validation failed.");
|
|
69
|
+
}
|
|
66
70
|
if (!assignableRoleIds.includes(roleSid)) {
|
|
67
71
|
throw new AppError(400, "Validation failed.", {
|
|
68
72
|
details: {
|
|
@@ -77,7 +81,7 @@ function createService({
|
|
|
77
81
|
if (!existingMembership || existingMembership.status !== "active") {
|
|
78
82
|
throw new AppError(404, "Member not found.");
|
|
79
83
|
}
|
|
80
|
-
if (
|
|
84
|
+
if (memberUserId === normalizeRecordId(workspace.ownerUserId, { fallback: null }) || existingMembership.roleSid === OWNER_ROLE_ID) {
|
|
81
85
|
throw new AppError(409, "Cannot change workspace owner role.");
|
|
82
86
|
}
|
|
83
87
|
|
|
@@ -95,13 +99,16 @@ function createService({
|
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
async function removeMember(workspace, payload = {}, options = {}) {
|
|
98
|
-
const memberUserId = payload.memberUserId;
|
|
102
|
+
const memberUserId = normalizeRecordId(payload.memberUserId, { fallback: null });
|
|
103
|
+
if (!memberUserId) {
|
|
104
|
+
throw new AppError(400, "Validation failed.");
|
|
105
|
+
}
|
|
99
106
|
|
|
100
107
|
const existingMembership = await workspaceMembershipsRepository.findByWorkspaceIdAndUserId(workspace.id, memberUserId, options);
|
|
101
108
|
if (!existingMembership || existingMembership.status !== "active") {
|
|
102
109
|
throw new AppError(404, "Member not found.");
|
|
103
110
|
}
|
|
104
|
-
if (
|
|
111
|
+
if (memberUserId === normalizeRecordId(workspace.ownerUserId, { fallback: null }) || existingMembership.roleSid === OWNER_ROLE_ID) {
|
|
105
112
|
throw new AppError(409, "Cannot remove workspace owner.");
|
|
106
113
|
}
|
|
107
114
|
|
|
@@ -155,13 +162,13 @@ function createService({
|
|
|
155
162
|
roleSid,
|
|
156
163
|
status: "pending",
|
|
157
164
|
tokenHash,
|
|
158
|
-
invitedByUserId:
|
|
165
|
+
invitedByUserId: normalizeRecordId(user?.id, { fallback: null }),
|
|
159
166
|
expiresAt: new Date(Date.now() + resolvedInviteExpiresInMs).toISOString()
|
|
160
167
|
},
|
|
161
168
|
options
|
|
162
169
|
);
|
|
163
|
-
const createdInviteId =
|
|
164
|
-
if (!
|
|
170
|
+
const createdInviteId = normalizeRecordId(createdInvite?.id, { fallback: null });
|
|
171
|
+
if (!createdInviteId) {
|
|
165
172
|
throw new Error("workspaceMembersService.createInvite expected repository to return created invite id.");
|
|
166
173
|
}
|
|
167
174
|
|
|
@@ -174,8 +181,13 @@ function createService({
|
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
async function revokeInvite(workspace, inviteId, options = {}) {
|
|
184
|
+
const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
|
|
185
|
+
if (!normalizedInviteId) {
|
|
186
|
+
throw new AppError(400, "Validation failed.");
|
|
187
|
+
}
|
|
188
|
+
|
|
177
189
|
const invite = await workspaceInvitesRepository.findPendingByIdForWorkspace(
|
|
178
|
-
|
|
190
|
+
normalizedInviteId,
|
|
179
191
|
workspace.id,
|
|
180
192
|
options
|
|
181
193
|
);
|
|
@@ -183,9 +195,9 @@ function createService({
|
|
|
183
195
|
throw new AppError(404, "Invite not found.");
|
|
184
196
|
}
|
|
185
197
|
|
|
186
|
-
await workspaceInvitesRepository.revokeById(
|
|
187
|
-
const revokedInviteId =
|
|
188
|
-
if (!
|
|
198
|
+
await workspaceInvitesRepository.revokeById(normalizedInviteId, options);
|
|
199
|
+
const revokedInviteId = normalizeRecordId(invite?.id, { fallback: null });
|
|
200
|
+
if (!revokedInviteId) {
|
|
189
201
|
throw new Error("workspaceMembersService.revokeInvite expected repository to return pending invite id.");
|
|
190
202
|
}
|
|
191
203
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
2
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
3
|
import { createService } from "./workspacePendingInvitationsService.js";
|
|
3
4
|
import { workspacePendingInvitationsActions } from "./workspacePendingInvitationsActions.js";
|
|
4
5
|
import { deepFreeze } from "../common/support/deepFreeze.js";
|
|
5
6
|
|
|
6
7
|
function workspaceAudienceFromEntityId({ event } = {}) {
|
|
7
|
-
const workspaceId =
|
|
8
|
-
if (!
|
|
8
|
+
const workspaceId = normalizeRecordId(event?.entityId, { fallback: null });
|
|
9
|
+
if (!workspaceId) {
|
|
9
10
|
return "none";
|
|
10
11
|
}
|
|
11
12
|
return {
|
|
@@ -14,7 +15,7 @@ function workspaceAudienceFromEntityId({ event } = {}) {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function actorUserEntityId({ options } = {}) {
|
|
17
|
-
return
|
|
18
|
+
return normalizeRecordId(options?.context?.actor?.id, { fallback: "" });
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function createActorUserEvent({ source, entity, realtimeEvent }) {
|
|
@@ -37,7 +38,7 @@ function createWorkspaceAudienceEvent({ entity, realtimeEvent }) {
|
|
|
37
38
|
source: "workspace",
|
|
38
39
|
entity,
|
|
39
40
|
operation: "updated",
|
|
40
|
-
entityId: ({ result }) => result?.workspaceId,
|
|
41
|
+
entityId: ({ result }) => normalizeRecordId(result?.workspaceId, { fallback: "" }),
|
|
41
42
|
realtime: {
|
|
42
43
|
event: realtimeEvent,
|
|
43
44
|
audience: workspaceAudienceFromEntityId
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveInviteTokenHash } from "@jskit-ai/auth-core/server/inviteTokens";
|
|
2
2
|
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
3
3
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
4
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
5
|
import { authenticatedUserValidator } from "../common/validators/authenticatedUserValidator.js";
|
|
5
6
|
|
|
6
7
|
function createService({
|
|
@@ -70,8 +71,8 @@ function createService({
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
function requireWorkspaceIdFromInvite(invite, methodName = "workspacePendingInvitationsService") {
|
|
73
|
-
const workspaceId =
|
|
74
|
-
if (!
|
|
74
|
+
const workspaceId = normalizeRecordId(invite?.workspaceId, { fallback: null });
|
|
75
|
+
if (!workspaceId) {
|
|
75
76
|
throw new Error(`${methodName} expected invite workspace id.`);
|
|
76
77
|
}
|
|
77
78
|
return workspaceId;
|
|
@@ -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 "../common/repositories/repositoryUtils.js";
|
|
6
9
|
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
7
10
|
import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
|
|
@@ -32,6 +35,7 @@ function createRepository(knex, { defaultInvitesEnabled } = {}) {
|
|
|
32
35
|
if (typeof knex !== "function") {
|
|
33
36
|
throw new TypeError("workspaceSettingsRepository requires knex.");
|
|
34
37
|
}
|
|
38
|
+
const withTransaction = createWithTransaction(knex);
|
|
35
39
|
|
|
36
40
|
function mapRow(row) {
|
|
37
41
|
if (!row) {
|
|
@@ -39,7 +43,7 @@ function createRepository(knex, { defaultInvitesEnabled } = {}) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const settings = {
|
|
42
|
-
workspaceId:
|
|
46
|
+
workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" })
|
|
43
47
|
};
|
|
44
48
|
for (const field of workspaceSettingsFields) {
|
|
45
49
|
const rawValue = Object.hasOwn(row, field.dbColumn)
|
|
@@ -59,24 +63,33 @@ function createRepository(knex, { defaultInvitesEnabled } = {}) {
|
|
|
59
63
|
|
|
60
64
|
async function findByWorkspaceId(workspaceId, options = {}) {
|
|
61
65
|
const client = options?.trx || knex;
|
|
62
|
-
const
|
|
66
|
+
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
67
|
+
if (!normalizedWorkspaceId) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const row = await client("workspace_settings").where({ workspace_id: normalizedWorkspaceId }).first();
|
|
63
72
|
return mapRow(row);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
async function ensureForWorkspaceId(workspaceId, options = {}) {
|
|
67
76
|
const client = options?.trx || knex;
|
|
68
|
-
const
|
|
77
|
+
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
78
|
+
if (!normalizedWorkspaceId) {
|
|
79
|
+
throw new TypeError("workspaceSettingsRepository.ensureForWorkspaceId requires a valid workspace id.");
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
const seed = resolveWorkspaceSettingsSeed(options?.workspace, {
|
|
70
83
|
defaultInvitesEnabled
|
|
71
84
|
});
|
|
72
|
-
const existing = await findByWorkspaceId(
|
|
85
|
+
const existing = await findByWorkspaceId(normalizedWorkspaceId, { trx: client });
|
|
73
86
|
if (existing) {
|
|
74
87
|
return existing;
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
try {
|
|
78
91
|
const insertPayload = {
|
|
79
|
-
workspace_id:
|
|
92
|
+
workspace_id: normalizedWorkspaceId,
|
|
80
93
|
created_at: nowDb(),
|
|
81
94
|
updated_at: nowDb()
|
|
82
95
|
};
|
|
@@ -90,12 +103,17 @@ function createRepository(knex, { defaultInvitesEnabled } = {}) {
|
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
return findByWorkspaceId(
|
|
106
|
+
return findByWorkspaceId(normalizedWorkspaceId, { trx: client });
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
async function updateSettingsByWorkspaceId(workspaceId, patch = {}, options = {}) {
|
|
97
110
|
const client = options?.trx || knex;
|
|
98
|
-
const
|
|
111
|
+
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
112
|
+
if (!normalizedWorkspaceId) {
|
|
113
|
+
throw new TypeError("workspaceSettingsRepository.updateSettingsByWorkspaceId requires a valid workspace id.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ensured = await ensureForWorkspaceId(normalizedWorkspaceId, {
|
|
99
117
|
trx: client,
|
|
100
118
|
workspace: options?.workspace
|
|
101
119
|
});
|
|
@@ -119,13 +137,14 @@ function createRepository(knex, { defaultInvitesEnabled } = {}) {
|
|
|
119
137
|
});
|
|
120
138
|
}
|
|
121
139
|
|
|
122
|
-
await client("workspace_settings").where({ workspace_id:
|
|
140
|
+
await client("workspace_settings").where({ workspace_id: normalizedWorkspaceId }).update({
|
|
123
141
|
...dbPatch
|
|
124
142
|
});
|
|
125
|
-
return findByWorkspaceId(
|
|
143
|
+
return findByWorkspaceId(normalizedWorkspaceId, { trx: client });
|
|
126
144
|
}
|
|
127
145
|
|
|
128
146
|
return Object.freeze({
|
|
147
|
+
withTransaction,
|
|
129
148
|
findByWorkspaceId,
|
|
130
149
|
ensureForWorkspaceId,
|
|
131
150
|
updateSettingsByWorkspaceId
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
1
2
|
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
2
3
|
import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
|
|
3
4
|
import {
|
|
@@ -33,9 +34,9 @@ function createService({
|
|
|
33
34
|
|
|
34
35
|
return {
|
|
35
36
|
workspace: {
|
|
36
|
-
id:
|
|
37
|
+
id: normalizeRecordId(workspace.id, { fallback: "" }),
|
|
37
38
|
slug: String(workspace.slug || ""),
|
|
38
|
-
ownerUserId:
|
|
39
|
+
ownerUserId: normalizeRecordId(workspace.ownerUserId, { fallback: "" })
|
|
39
40
|
},
|
|
40
41
|
settings,
|
|
41
42
|
roleCatalog: cloneWorkspaceRoleCatalog(resolvedRoleCatalog)
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { Type } from "@fastify/type-provider-typebox";
|
|
2
2
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
normalizeObjectInput,
|
|
5
|
+
recordIdSchema,
|
|
6
|
+
recordIdInputSchema,
|
|
7
|
+
nullableRecordIdSchema
|
|
8
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
9
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
10
|
import { createOperationMessages } from "../operationMessages.js";
|
|
6
11
|
import { createWorkspaceRoleCatalog, OWNER_ROLE_ID } from "../roles.js";
|
|
7
12
|
|
|
8
13
|
const workspaceSummaryOutputSchema = Type.Object(
|
|
9
14
|
{
|
|
10
|
-
id:
|
|
15
|
+
id: recordIdSchema,
|
|
11
16
|
slug: Type.String({ minLength: 1 }),
|
|
12
17
|
name: Type.String({ minLength: 1 }),
|
|
13
|
-
ownerUserId:
|
|
18
|
+
ownerUserId: recordIdSchema,
|
|
14
19
|
avatarUrl: Type.String()
|
|
15
20
|
},
|
|
16
21
|
{ additionalProperties: false }
|
|
@@ -18,7 +23,7 @@ const workspaceSummaryOutputSchema = Type.Object(
|
|
|
18
23
|
|
|
19
24
|
const memberSummaryOutputSchema = Type.Object(
|
|
20
25
|
{
|
|
21
|
-
userId:
|
|
26
|
+
userId: recordIdSchema,
|
|
22
27
|
roleSid: Type.String({ minLength: 1 }),
|
|
23
28
|
status: Type.String({ minLength: 1 }),
|
|
24
29
|
displayName: Type.String(),
|
|
@@ -30,12 +35,12 @@ const memberSummaryOutputSchema = Type.Object(
|
|
|
30
35
|
|
|
31
36
|
const inviteSummaryOutputSchema = Type.Object(
|
|
32
37
|
{
|
|
33
|
-
id:
|
|
38
|
+
id: recordIdSchema,
|
|
34
39
|
email: Type.String({ minLength: 3, format: "email" }),
|
|
35
40
|
roleSid: Type.String({ minLength: 1 }),
|
|
36
41
|
status: Type.String({ minLength: 1 }),
|
|
37
42
|
expiresAt: Type.String({ minLength: 1 }),
|
|
38
|
-
invitedByUserId:
|
|
43
|
+
invitedByUserId: nullableRecordIdSchema
|
|
39
44
|
},
|
|
40
45
|
{ additionalProperties: false }
|
|
41
46
|
);
|
|
@@ -44,26 +49,25 @@ function normalizeWorkspaceAdminSummary(workspace) {
|
|
|
44
49
|
const source = normalizeObjectInput(workspace);
|
|
45
50
|
|
|
46
51
|
return {
|
|
47
|
-
id:
|
|
52
|
+
id: normalizeRecordId(source.id, { fallback: "" }),
|
|
48
53
|
slug: normalizeText(source.slug),
|
|
49
54
|
name: normalizeText(source.name),
|
|
50
|
-
ownerUserId:
|
|
55
|
+
ownerUserId: normalizeRecordId(source.ownerUserId, { fallback: "" }),
|
|
51
56
|
avatarUrl: normalizeText(source.avatarUrl)
|
|
52
57
|
};
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
function normalizeMemberSummary(member, workspace) {
|
|
56
61
|
const source = normalizeObjectInput(member);
|
|
62
|
+
const userId = normalizeRecordId(source.userId, { fallback: "" });
|
|
57
63
|
|
|
58
64
|
return {
|
|
59
|
-
userId
|
|
65
|
+
userId,
|
|
60
66
|
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
61
67
|
status: normalizeLowerText(source.status || "active") || "active",
|
|
62
68
|
displayName: normalizeText(source.displayName),
|
|
63
69
|
email: normalizeLowerText(source.email),
|
|
64
|
-
isOwner:
|
|
65
|
-
Number(source.userId) === Number(workspace.ownerUserId) ||
|
|
66
|
-
normalizeLowerText(source.roleSid) === OWNER_ROLE_ID
|
|
70
|
+
isOwner: userId === workspace.ownerUserId || normalizeLowerText(source.roleSid) === OWNER_ROLE_ID
|
|
67
71
|
};
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -71,12 +75,12 @@ function normalizeInviteSummary(invite) {
|
|
|
71
75
|
const source = normalizeObjectInput(invite);
|
|
72
76
|
|
|
73
77
|
return {
|
|
74
|
-
id:
|
|
78
|
+
id: normalizeRecordId(source.id, { fallback: "" }),
|
|
75
79
|
email: normalizeLowerText(source.email),
|
|
76
80
|
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
77
81
|
status: normalizeLowerText(source.status || "pending") || "pending",
|
|
78
82
|
expiresAt: source.expiresAt,
|
|
79
|
-
invitedByUserId: source.invitedByUserId == null ? null :
|
|
83
|
+
invitedByUserId: source.invitedByUserId == null ? null : normalizeRecordId(source.invitedByUserId, { fallback: null })
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -176,7 +180,7 @@ const updateMemberRoleBodyValidator = Object.freeze({
|
|
|
176
180
|
const updateMemberRoleInputValidator = Object.freeze({
|
|
177
181
|
schema: Type.Object(
|
|
178
182
|
{
|
|
179
|
-
memberUserId:
|
|
183
|
+
memberUserId: recordIdInputSchema,
|
|
180
184
|
roleSid: Type.String({ minLength: 1 })
|
|
181
185
|
},
|
|
182
186
|
{ additionalProperties: false }
|
|
@@ -185,7 +189,7 @@ const updateMemberRoleInputValidator = Object.freeze({
|
|
|
185
189
|
const source = normalizeObjectInput(payload);
|
|
186
190
|
|
|
187
191
|
return {
|
|
188
|
-
memberUserId:
|
|
192
|
+
memberUserId: normalizeRecordId(source.memberUserId, { fallback: "" }),
|
|
189
193
|
roleSid: normalizeLowerText(source.roleSid)
|
|
190
194
|
};
|
|
191
195
|
}
|
|
@@ -194,7 +198,7 @@ const updateMemberRoleInputValidator = Object.freeze({
|
|
|
194
198
|
const removeMemberInputValidator = Object.freeze({
|
|
195
199
|
schema: Type.Object(
|
|
196
200
|
{
|
|
197
|
-
memberUserId:
|
|
201
|
+
memberUserId: recordIdInputSchema
|
|
198
202
|
},
|
|
199
203
|
{ additionalProperties: false }
|
|
200
204
|
),
|
|
@@ -202,7 +206,7 @@ const removeMemberInputValidator = Object.freeze({
|
|
|
202
206
|
const source = normalizeObjectInput(payload);
|
|
203
207
|
|
|
204
208
|
return {
|
|
205
|
-
memberUserId:
|
|
209
|
+
memberUserId: normalizeRecordId(source.memberUserId, { fallback: "" })
|
|
206
210
|
};
|
|
207
211
|
}
|
|
208
212
|
});
|
|
@@ -228,7 +232,7 @@ const createInviteBodyValidator = Object.freeze({
|
|
|
228
232
|
const revokeInviteInputValidator = Object.freeze({
|
|
229
233
|
schema: Type.Object(
|
|
230
234
|
{
|
|
231
|
-
inviteId:
|
|
235
|
+
inviteId: recordIdInputSchema
|
|
232
236
|
},
|
|
233
237
|
{ additionalProperties: false }
|
|
234
238
|
),
|
|
@@ -236,7 +240,7 @@ const revokeInviteInputValidator = Object.freeze({
|
|
|
236
240
|
const source = normalizeObjectInput(payload);
|
|
237
241
|
|
|
238
242
|
return {
|
|
239
|
-
inviteId:
|
|
243
|
+
inviteId: normalizeRecordId(source.inviteId, { fallback: "" })
|
|
240
244
|
};
|
|
241
245
|
}
|
|
242
246
|
});
|
|
@@ -2,20 +2,15 @@ import { Type } from "@fastify/type-provider-typebox";
|
|
|
2
2
|
import { encodeInviteTokenHash } from "@jskit-ai/auth-core/shared/inviteTokens";
|
|
3
3
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
4
4
|
import { createOperationMessages } from "../operationMessages.js";
|
|
5
|
-
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators
|
|
5
|
+
import { normalizeObjectInput, recordIdSchema } from "@jskit-ai/kernel/shared/validators";
|
|
6
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
7
|
|
|
7
8
|
function normalizePendingInvite(invite) {
|
|
8
|
-
const id =
|
|
9
|
-
const workspaceId =
|
|
9
|
+
const id = normalizeRecordId(invite?.id, { fallback: null });
|
|
10
|
+
const workspaceId = normalizeRecordId(invite?.workspaceId, { fallback: null });
|
|
10
11
|
const tokenHash = normalizeText(invite?.tokenHash);
|
|
11
12
|
|
|
12
|
-
if (!
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
if (!Number.isInteger(workspaceId) || workspaceId < 1) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
if (!tokenHash) {
|
|
13
|
+
if (!id || !workspaceId || !tokenHash) {
|
|
19
14
|
return null;
|
|
20
15
|
}
|
|
21
16
|
|
|
@@ -39,8 +34,8 @@ function normalizePendingInviteList(invites) {
|
|
|
39
34
|
const pendingInviteRecordValidator = Object.freeze({
|
|
40
35
|
schema: Type.Object(
|
|
41
36
|
{
|
|
42
|
-
id:
|
|
43
|
-
workspaceId:
|
|
37
|
+
id: recordIdSchema,
|
|
38
|
+
workspaceId: recordIdSchema,
|
|
44
39
|
workspaceSlug: Type.String({ minLength: 1 }),
|
|
45
40
|
workspaceName: Type.String({ minLength: 1 }),
|
|
46
41
|
workspaceAvatarUrl: Type.String(),
|
|
@@ -2,8 +2,11 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
3
|
import {
|
|
4
4
|
normalizeObjectInput,
|
|
5
|
-
createCursorListValidator
|
|
5
|
+
createCursorListValidator,
|
|
6
|
+
recordIdSchema,
|
|
7
|
+
recordIdInputSchema
|
|
6
8
|
} from "@jskit-ai/kernel/shared/validators";
|
|
9
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
10
|
|
|
8
11
|
function normalizeWorkspaceAvatarUrl(value) {
|
|
9
12
|
const avatarUrl = normalizeText(value);
|
|
@@ -31,7 +34,7 @@ function normalizeWorkspaceInput(payload = {}) {
|
|
|
31
34
|
normalized.name = normalizeText(source.name);
|
|
32
35
|
}
|
|
33
36
|
if (Object.hasOwn(source, "ownerUserId")) {
|
|
34
|
-
normalized.ownerUserId =
|
|
37
|
+
normalized.ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: "" });
|
|
35
38
|
}
|
|
36
39
|
if (Object.hasOwn(source, "avatarUrl")) {
|
|
37
40
|
normalized.avatarUrl = normalizeWorkspaceAvatarUrl(source.avatarUrl);
|
|
@@ -47,10 +50,10 @@ function normalizeWorkspaceOutput(payload = {}) {
|
|
|
47
50
|
const source = normalizeObjectInput(payload);
|
|
48
51
|
|
|
49
52
|
return {
|
|
50
|
-
id:
|
|
53
|
+
id: normalizeRecordId(source.id, { fallback: "" }),
|
|
51
54
|
slug: normalizeLowerText(source.slug),
|
|
52
55
|
name: normalizeText(source.name),
|
|
53
|
-
ownerUserId:
|
|
56
|
+
ownerUserId: normalizeRecordId(source.ownerUserId, { fallback: "" }),
|
|
54
57
|
avatarUrl: normalizeText(source.avatarUrl)
|
|
55
58
|
};
|
|
56
59
|
}
|
|
@@ -59,7 +62,7 @@ function normalizeWorkspaceListItemOutput(payload = {}) {
|
|
|
59
62
|
const source = normalizeObjectInput(payload);
|
|
60
63
|
|
|
61
64
|
return {
|
|
62
|
-
id:
|
|
65
|
+
id: normalizeRecordId(source.id, { fallback: "" }),
|
|
63
66
|
slug: normalizeLowerText(source.slug),
|
|
64
67
|
name: normalizeText(source.name),
|
|
65
68
|
avatarUrl: normalizeText(source.avatarUrl),
|
|
@@ -70,10 +73,10 @@ function normalizeWorkspaceListItemOutput(payload = {}) {
|
|
|
70
73
|
|
|
71
74
|
const responseRecordSchema = Type.Object(
|
|
72
75
|
{
|
|
73
|
-
id:
|
|
76
|
+
id: recordIdSchema,
|
|
74
77
|
slug: Type.String({ minLength: 1 }),
|
|
75
78
|
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
76
|
-
ownerUserId:
|
|
79
|
+
ownerUserId: recordIdSchema,
|
|
77
80
|
avatarUrl: Type.String()
|
|
78
81
|
},
|
|
79
82
|
{ additionalProperties: false }
|
|
@@ -81,7 +84,7 @@ const responseRecordSchema = Type.Object(
|
|
|
81
84
|
|
|
82
85
|
const listItemSchema = Type.Object(
|
|
83
86
|
{
|
|
84
|
-
id:
|
|
87
|
+
id: recordIdSchema,
|
|
85
88
|
slug: Type.String({ minLength: 1 }),
|
|
86
89
|
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
87
90
|
avatarUrl: Type.String(),
|
|
@@ -94,7 +97,8 @@ const listItemSchema = Type.Object(
|
|
|
94
97
|
const createRequestBodySchema = Type.Object(
|
|
95
98
|
{
|
|
96
99
|
name: Type.String({ minLength: 1, maxLength: 160 }),
|
|
97
|
-
slug: Type.Optional(Type.String({ minLength: 1, maxLength: 120 }))
|
|
100
|
+
slug: Type.Optional(Type.String({ minLength: 1, maxLength: 120 })),
|
|
101
|
+
ownerUserId: Type.Optional(recordIdInputSchema)
|
|
98
102
|
},
|
|
99
103
|
{ additionalProperties: false }
|
|
100
104
|
);
|
|
@@ -3,8 +3,10 @@ import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeObjectInput,
|
|
5
5
|
createCursorListValidator,
|
|
6
|
-
normalizeSettingsFieldInput
|
|
6
|
+
normalizeSettingsFieldInput,
|
|
7
|
+
recordIdSchema
|
|
7
8
|
} from "@jskit-ai/kernel/shared/validators";
|
|
9
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
10
|
import { workspaceSettingsFields } from "./workspaceSettingsFields.js";
|
|
9
11
|
import { createWorkspaceRoleCatalog } from "../roles.js";
|
|
10
12
|
|
|
@@ -39,9 +41,9 @@ function buildResponseRecordSchema() {
|
|
|
39
41
|
{
|
|
40
42
|
workspace: Type.Object(
|
|
41
43
|
{
|
|
42
|
-
id:
|
|
44
|
+
id: recordIdSchema,
|
|
43
45
|
slug: Type.String({ minLength: 1 }),
|
|
44
|
-
ownerUserId:
|
|
46
|
+
ownerUserId: recordIdSchema
|
|
45
47
|
},
|
|
46
48
|
{ additionalProperties: false }
|
|
47
49
|
),
|
|
@@ -98,9 +100,9 @@ function normalizeOutput(payload = {}) {
|
|
|
98
100
|
|
|
99
101
|
return {
|
|
100
102
|
workspace: {
|
|
101
|
-
id:
|
|
103
|
+
id: normalizeRecordId(workspace.id, { fallback: "" }),
|
|
102
104
|
slug: normalizeText(workspace.slug),
|
|
103
|
-
ownerUserId:
|
|
105
|
+
ownerUserId: normalizeRecordId(workspace.ownerUserId, { fallback: "" })
|
|
104
106
|
},
|
|
105
107
|
settings: normalizedSettings,
|
|
106
108
|
roleCatalog: hasRoleCatalog ? roleCatalog : createWorkspaceRoleCatalog()
|
|
@@ -5,7 +5,7 @@ exports.up = async function up(knex) {
|
|
|
5
5
|
const hasUsersTable = await knex.schema.hasTable("users");
|
|
6
6
|
if (!hasUsersTable) {
|
|
7
7
|
await knex.schema.createTable("users", (table) => {
|
|
8
|
-
table.
|
|
8
|
+
table.bigIncrements("id").primary();
|
|
9
9
|
table.string("auth_provider", 64).notNullable();
|
|
10
10
|
table.string("auth_provider_user_sid", 191).notNullable();
|
|
11
11
|
table.string("email", 255).notNullable();
|
|
@@ -24,7 +24,7 @@ exports.up = async function up(knex) {
|
|
|
24
24
|
const hasUserSettingsTable = await knex.schema.hasTable("user_settings");
|
|
25
25
|
if (!hasUserSettingsTable) {
|
|
26
26
|
await knex.schema.createTable("user_settings", (table) => {
|
|
27
|
-
table.
|
|
27
|
+
table.bigInteger("user_id").unsigned().primary().references("id").inTable("users").onDelete("CASCADE");
|
|
28
28
|
table.string("theme", 32).notNullable().defaultTo("system");
|
|
29
29
|
table.string("locale", 24).notNullable().defaultTo("en");
|
|
30
30
|
table.string("time_zone", 64).notNullable().defaultTo("UTC");
|
|
@@ -45,8 +45,8 @@ exports.up = async function up(knex) {
|
|
|
45
45
|
const hasConsoleSettingsTable = await knex.schema.hasTable("console_settings");
|
|
46
46
|
if (!hasConsoleSettingsTable) {
|
|
47
47
|
await knex.schema.createTable("console_settings", (table) => {
|
|
48
|
-
table.
|
|
49
|
-
table.
|
|
48
|
+
table.bigInteger("id").primary();
|
|
49
|
+
table.bigInteger("owner_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL");
|
|
50
50
|
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
51
51
|
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
52
52
|
});
|
|
@@ -62,7 +62,7 @@ exports.up = async function up(knex) {
|
|
|
62
62
|
for (const profile of profiles) {
|
|
63
63
|
const nextUsername = resolveUniqueUsername(usernameBaseFromEmail(profile.email), usedUsernames);
|
|
64
64
|
usedUsernames.add(nextUsername);
|
|
65
|
-
await knex("users").where({ id:
|
|
65
|
+
await knex("users").where({ id: profile.id }).update({
|
|
66
66
|
username: nextUsername
|
|
67
67
|
});
|
|
68
68
|
}
|
|
@@ -15,7 +15,7 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
|
|
|
15
15
|
async upsert(payload, options = {}) {
|
|
16
16
|
calls.push({ step: "upsert", trx: options.trx || null });
|
|
17
17
|
return {
|
|
18
|
-
id: 13,
|
|
18
|
+
id: "13",
|
|
19
19
|
authProvider: payload.authProvider,
|
|
20
20
|
authProviderUserSid: payload.authProviderUserSid,
|
|
21
21
|
email: payload.email,
|
|
@@ -67,7 +67,7 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
67
67
|
usersRepository: {
|
|
68
68
|
async findByIdentity() {
|
|
69
69
|
return {
|
|
70
|
-
id: 7,
|
|
70
|
+
id: "7",
|
|
71
71
|
authProvider: "supabase",
|
|
72
72
|
authProviderUserSid: "abc-7",
|
|
73
73
|
email: "tony@example.com",
|
|
@@ -84,7 +84,7 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
|
|
|
84
84
|
},
|
|
85
85
|
userSettingsRepository: {
|
|
86
86
|
async ensureForUserId() {
|
|
87
|
-
return { userId: 7 };
|
|
87
|
+
return { userId: "7" };
|
|
88
88
|
}
|
|
89
89
|
},
|
|
90
90
|
workspaceProvisioningService: {
|
|
@@ -116,11 +116,14 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
|
|
|
116
116
|
},
|
|
117
117
|
async upsert() {
|
|
118
118
|
return null;
|
|
119
|
+
},
|
|
120
|
+
async withTransaction(work) {
|
|
121
|
+
return work({ trxId: "tx-3" });
|
|
119
122
|
}
|
|
120
123
|
},
|
|
121
124
|
userSettingsRepository: {
|
|
122
125
|
async ensureForUserId() {
|
|
123
|
-
return { userId: 1 };
|
|
126
|
+
return { userId: "1" };
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
});
|