@jskit-ai/workspaces-core 0.1.21 → 0.1.23
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 +2 -2
- package/package.json +6 -6
- package/src/server/common/repositories/workspaceInvitesRepository.js +68 -65
- package/src/server/common/repositories/workspaceMembershipsRepository.js +83 -52
- package/src/server/common/repositories/workspacesRepository.js +42 -67
- package/src/server/common/resources/workspaceInvitesResource.js +207 -0
- package/src/server/common/resources/workspaceMembershipsResource.js +154 -0
- package/src/server/common/resources/workspacesResource.js +170 -0
- package/src/server/registerWorkspaceBootstrap.js +1 -1
- package/src/server/registerWorkspaceCore.js +3 -3
- package/src/server/registerWorkspaceRepositories.js +3 -3
- package/src/server/workspaceBootstrapContributor.js +4 -4
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +2 -2
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +2 -2
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +2 -2
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +5 -4
- package/src/shared/resources/workspaceMembersResource.js +1 -1
- package/src/shared/resources/workspacePendingInvitationsResource.js +1 -1
- package/src/shared/resources/workspaceResource.js +1 -1
- package/src/shared/resources/workspaceSettingsFields.js +10 -4
- package/src/shared/resources/workspaceSettingsResource.js +1 -1
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +9 -9
- package/test/exportsContract.test.js +3 -1
- package/test/registerWorkspaceBootstrap.test.js +1 -1
- package/test/registerWorkspaceSettings.test.js +1 -1
- package/test/usersRouteResources.test.js +1 -1
- package/test/workspaceBootstrapContributor.test.js +3 -3
- package/test/workspaceInvitesRepository.test.js +129 -18
- package/test/workspaceMembershipsRepository.test.js +212 -0
- package/test/workspacesRepository.test.js +253 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/workspaces-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.23",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Workspace tenancy runtime plus HTTP routes, role catalog, and workspace config scaffolding.",
|
|
7
7
|
dependsOn: [
|
|
@@ -112,7 +112,7 @@ export default Object.freeze({
|
|
|
112
112
|
mutations: {
|
|
113
113
|
dependencies: {
|
|
114
114
|
runtime: {
|
|
115
|
-
"@jskit-ai/users-core": "0.1.
|
|
115
|
+
"@jskit-ai/users-core": "0.1.57"
|
|
116
116
|
},
|
|
117
117
|
dev: {}
|
|
118
118
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/workspaces-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
21
|
-
"@jskit-ai/auth-core": "0.1.
|
|
22
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
23
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/users-core": "0.1.
|
|
21
|
+
"@jskit-ai/auth-core": "0.1.46",
|
|
22
|
+
"@jskit-ai/database-runtime": "0.1.47",
|
|
23
|
+
"@jskit-ai/http-runtime": "0.1.46",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.47",
|
|
25
|
+
"@jskit-ai/users-core": "0.1.57",
|
|
26
26
|
"typebox": "^1.0.81"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
|
|
2
2
|
import {
|
|
3
3
|
normalizeLowerText,
|
|
4
|
-
normalizeDbRecordId,
|
|
5
4
|
normalizeRecordId,
|
|
6
5
|
normalizeText,
|
|
7
|
-
toIsoString,
|
|
8
|
-
toNullableIso,
|
|
9
|
-
toNullableDateTime,
|
|
10
6
|
nowDb,
|
|
11
|
-
isDuplicateEntryError
|
|
12
|
-
createWithTransaction
|
|
7
|
+
isDuplicateEntryError
|
|
13
8
|
} from "./repositoryUtils.js";
|
|
9
|
+
import { workspaceInvitesResource } from "../resources/workspaceInvitesResource.js";
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const REPOSITORY_CONFIG = Object.freeze({
|
|
12
|
+
context: "internal.repository.workspace-invites"
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function normalizeInviteRecord(payload = {}) {
|
|
16
|
+
if (!payload) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return workspaceInvitesResource.operations.view.outputValidator.normalize(payload);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeInvitePatchPayload(payload = {}) {
|
|
23
|
+
return workspaceInvitesResource.operations.patch.bodyValidator.normalize(payload);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeInviteWithWorkspace(payload = {}) {
|
|
27
|
+
const invite = normalizeInviteRecord(payload);
|
|
28
|
+
if (!invite) {
|
|
17
29
|
return null;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
status: normalizeLowerText(row.status || "pending") || "pending",
|
|
26
|
-
tokenHash: normalizeText(row.token_hash),
|
|
27
|
-
invitedByUserId: row.invited_by_user_id == null ? null : normalizeDbRecordId(row.invited_by_user_id, { fallback: null }),
|
|
28
|
-
expiresAt: toNullableIso(row.expires_at),
|
|
29
|
-
acceptedAt: toNullableIso(row.accepted_at),
|
|
30
|
-
revokedAt: toNullableIso(row.revoked_at),
|
|
31
|
-
createdAt: toIsoString(row.created_at),
|
|
32
|
-
updatedAt: toIsoString(row.updated_at),
|
|
33
|
-
workspaceSlug: row.workspace_slug ? normalizeText(row.workspace_slug) : undefined,
|
|
34
|
-
workspaceName: row.workspace_name ? normalizeText(row.workspace_name) : undefined,
|
|
35
|
-
workspaceAvatarUrl: row.workspace_avatar_url ? normalizeText(row.workspace_avatar_url) : undefined
|
|
33
|
+
...invite,
|
|
34
|
+
workspaceSlug: payload?.workspace_slug ? normalizeText(payload.workspace_slug) : undefined,
|
|
35
|
+
workspaceName: payload?.workspace_name ? normalizeText(payload.workspace_name) : undefined,
|
|
36
|
+
workspaceAvatarUrl: payload?.workspace_avatar_url ? normalizeText(payload.workspace_avatar_url) : undefined
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -47,14 +48,15 @@ function createRepository(knex) {
|
|
|
47
48
|
if (typeof knex !== "function") {
|
|
48
49
|
throw new TypeError("workspaceInvitesRepository requires knex.");
|
|
49
50
|
}
|
|
50
|
-
const
|
|
51
|
+
const resourceRuntime = createCrudResourceRuntime(workspaceInvitesResource, knex, REPOSITORY_CONFIG);
|
|
52
|
+
const withTransaction = resourceRuntime.withTransaction;
|
|
51
53
|
|
|
52
54
|
async function findPendingByTokenHash(tokenHash, options = {}) {
|
|
53
55
|
const client = options?.trx || knex;
|
|
54
56
|
const row = await client("workspace_invites")
|
|
55
57
|
.where({ token_hash: normalizeText(tokenHash), status: "pending" })
|
|
56
58
|
.first();
|
|
57
|
-
return
|
|
59
|
+
return normalizeInviteRecord(row);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
async function listPendingByEmail(email, options = {}) {
|
|
@@ -70,7 +72,7 @@ function createRepository(knex) {
|
|
|
70
72
|
.orderBy("wi.created_at", "desc")
|
|
71
73
|
.select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
|
|
72
74
|
|
|
73
|
-
return rows.map(
|
|
75
|
+
return rows.map(normalizeInviteWithWorkspace).filter(Boolean);
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
async function listPendingByWorkspaceIdWithWorkspace(workspaceId, options = {}) {
|
|
@@ -86,38 +88,32 @@ function createRepository(knex) {
|
|
|
86
88
|
.orderBy("wi.created_at", "desc")
|
|
87
89
|
.select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
|
|
88
90
|
|
|
89
|
-
return rows.map(
|
|
91
|
+
return rows.map(normalizeInviteWithWorkspace).filter(Boolean);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
async function insert(payload = {}, options = {}) {
|
|
93
95
|
const client = options?.trx || knex;
|
|
94
|
-
const source = payload && typeof payload === "object" ? payload : {};
|
|
96
|
+
const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
95
97
|
const workspaceId = normalizeRecordId(source.workspaceId, { fallback: null });
|
|
96
98
|
if (!workspaceId) {
|
|
97
99
|
throw new TypeError("workspaceInvitesRepository.insert requires workspaceId.");
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const createPayload = {
|
|
103
|
+
...source,
|
|
104
|
+
workspaceId,
|
|
105
|
+
roleSid: normalizeLowerText(source.roleSid || "member") || "member",
|
|
104
106
|
status: normalizeLowerText(source.status || "pending") || "pending",
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
expires_at: toNullableDateTime(source.expiresAt),
|
|
108
|
-
accepted_at: null,
|
|
109
|
-
revoked_at: null,
|
|
110
|
-
created_at: nowDb(),
|
|
111
|
-
updated_at: nowDb()
|
|
107
|
+
acceptedAt: null,
|
|
108
|
+
revokedAt: null
|
|
112
109
|
};
|
|
113
110
|
|
|
114
111
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
112
|
+
return await resourceRuntime.create(createPayload, {
|
|
113
|
+
...options,
|
|
114
|
+
trx: client,
|
|
115
|
+
include: "none"
|
|
116
|
+
});
|
|
121
117
|
} catch (error) {
|
|
122
118
|
if (!isDuplicateEntryError(error)) {
|
|
123
119
|
throw error;
|
|
@@ -125,10 +121,10 @@ function createRepository(knex) {
|
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
const row = await client("workspace_invites")
|
|
128
|
-
.where({ workspace_id:
|
|
124
|
+
.where({ workspace_id: createPayload.workspaceId, email: createPayload.email, status: "pending" })
|
|
129
125
|
.orderBy("id", "desc")
|
|
130
126
|
.first();
|
|
131
|
-
return
|
|
127
|
+
return normalizeInviteRecord(row);
|
|
132
128
|
}
|
|
133
129
|
|
|
134
130
|
async function expirePendingByWorkspaceIdAndEmail(workspaceId, email, options = {}) {
|
|
@@ -138,10 +134,11 @@ function createRepository(knex) {
|
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
const client = options?.trx || knex;
|
|
137
|
+
const patch = normalizeInvitePatchPayload({ status: "expired" });
|
|
141
138
|
await client("workspace_invites")
|
|
142
139
|
.where({ workspace_id: normalizedWorkspaceId, email: normalizeLowerText(email), status: "pending" })
|
|
143
140
|
.update({
|
|
144
|
-
status:
|
|
141
|
+
status: patch.status,
|
|
145
142
|
updated_at: nowDb()
|
|
146
143
|
});
|
|
147
144
|
}
|
|
@@ -152,14 +149,17 @@ function createRepository(knex) {
|
|
|
152
149
|
return;
|
|
153
150
|
}
|
|
154
151
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.update({
|
|
152
|
+
await resourceRuntime.updateById(
|
|
153
|
+
normalizedInviteId,
|
|
154
|
+
{
|
|
159
155
|
status: "accepted",
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
acceptedAt: new Date()
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
...options,
|
|
160
|
+
include: "none"
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
async function revokeById(inviteId, options = {}) {
|
|
@@ -168,14 +168,17 @@ function createRepository(knex) {
|
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.update({
|
|
171
|
+
await resourceRuntime.updateById(
|
|
172
|
+
normalizedInviteId,
|
|
173
|
+
{
|
|
175
174
|
status: "revoked",
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
revokedAt: new Date()
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
...options,
|
|
179
|
+
include: "none"
|
|
180
|
+
}
|
|
181
|
+
);
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
async function findPendingByIdForWorkspace(inviteId, workspaceId, options = {}) {
|
|
@@ -189,7 +192,7 @@ function createRepository(knex) {
|
|
|
189
192
|
const row = await client("workspace_invites")
|
|
190
193
|
.where({ id: normalizedInviteId, workspace_id: normalizedWorkspaceId, status: "pending" })
|
|
191
194
|
.first();
|
|
192
|
-
return
|
|
195
|
+
return normalizeInviteRecord(row);
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
return Object.freeze({
|
|
@@ -205,4 +208,4 @@ function createRepository(knex) {
|
|
|
205
208
|
});
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
export { createRepository,
|
|
211
|
+
export { createRepository, normalizeInviteRecord, normalizeInviteWithWorkspace };
|
|
@@ -1,32 +1,30 @@
|
|
|
1
|
+
import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
|
|
1
2
|
import {
|
|
2
3
|
normalizeLowerText,
|
|
3
4
|
normalizeRecordId,
|
|
4
5
|
normalizeDbRecordId,
|
|
5
6
|
normalizeText,
|
|
6
|
-
|
|
7
|
-
nowDb,
|
|
8
|
-
isDuplicateEntryError,
|
|
9
|
-
createWithTransaction
|
|
7
|
+
isDuplicateEntryError
|
|
10
8
|
} from "./repositoryUtils.js";
|
|
11
9
|
import { OWNER_ROLE_ID } from "../../../shared/roles.js";
|
|
10
|
+
import { workspaceMembershipsResource } from "../resources/workspaceMembershipsResource.js";
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const REPOSITORY_CONFIG = Object.freeze({
|
|
13
|
+
context: "internal.repository.workspace-memberships"
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function normalizeMembershipRecord(payload = {}) {
|
|
17
|
+
if (!payload) {
|
|
15
18
|
return null;
|
|
16
19
|
}
|
|
20
|
+
return workspaceMembershipsResource.operations.view.outputValidator.normalize(payload);
|
|
21
|
+
}
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" }),
|
|
21
|
-
userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
|
|
22
|
-
roleSid: normalizeLowerText(row.role_sid || "member") || "member",
|
|
23
|
-
status: normalizeLowerText(row.status || "active") || "active",
|
|
24
|
-
createdAt: toIsoString(row.created_at),
|
|
25
|
-
updatedAt: toIsoString(row.updated_at)
|
|
26
|
-
};
|
|
23
|
+
function normalizeMembershipPatchPayload(payload = {}) {
|
|
24
|
+
return workspaceMembershipsResource.operations.patch.bodyValidator.normalize(payload);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
function
|
|
27
|
+
function normalizeMemberSummaryRow(row) {
|
|
30
28
|
if (!row) {
|
|
31
29
|
return null;
|
|
32
30
|
}
|
|
@@ -44,7 +42,8 @@ function createRepository(knex) {
|
|
|
44
42
|
if (typeof knex !== "function") {
|
|
45
43
|
throw new TypeError("workspaceMembershipsRepository requires knex.");
|
|
46
44
|
}
|
|
47
|
-
const
|
|
45
|
+
const resourceRuntime = createCrudResourceRuntime(workspaceMembershipsResource, knex, REPOSITORY_CONFIG);
|
|
46
|
+
const withTransaction = resourceRuntime.withTransaction;
|
|
48
47
|
|
|
49
48
|
async function findByWorkspaceIdAndUserId(workspaceId, userId, options = {}) {
|
|
50
49
|
const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
|
|
@@ -57,7 +56,7 @@ function createRepository(knex) {
|
|
|
57
56
|
const row = await client("workspace_memberships")
|
|
58
57
|
.where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
|
|
59
58
|
.first();
|
|
60
|
-
return
|
|
59
|
+
return normalizeMembershipRecord(row);
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
async function ensureOwnerMembership(workspaceId, userId, options = {}) {
|
|
@@ -71,26 +70,37 @@ function createRepository(knex) {
|
|
|
71
70
|
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
72
71
|
if (existing) {
|
|
73
72
|
if (existing.roleSid !== OWNER_ROLE_ID || existing.status !== "active") {
|
|
74
|
-
await
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
status: "active"
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
await resourceRuntime.updateById(
|
|
74
|
+
existing.id,
|
|
75
|
+
{
|
|
76
|
+
roleSid: OWNER_ROLE_ID,
|
|
77
|
+
status: "active"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
...options,
|
|
81
|
+
trx: client,
|
|
82
|
+
include: "none",
|
|
83
|
+
existingRecord: existing
|
|
84
|
+
}
|
|
85
|
+
);
|
|
81
86
|
}
|
|
82
87
|
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
try {
|
|
86
|
-
await
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
await resourceRuntime.create(
|
|
92
|
+
{
|
|
93
|
+
workspaceId: normalizedWorkspaceId,
|
|
94
|
+
userId: normalizedUserId,
|
|
95
|
+
roleSid: OWNER_ROLE_ID,
|
|
96
|
+
status: "active"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
...options,
|
|
100
|
+
trx: client,
|
|
101
|
+
include: "none"
|
|
102
|
+
}
|
|
103
|
+
);
|
|
94
104
|
} catch (error) {
|
|
95
105
|
if (!isDuplicateEntryError(error)) {
|
|
96
106
|
throw error;
|
|
@@ -109,28 +119,49 @@ function createRepository(knex) {
|
|
|
109
119
|
|
|
110
120
|
const client = options?.trx || knex;
|
|
111
121
|
const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
112
|
-
const
|
|
113
|
-
|
|
122
|
+
const normalizedPatch = normalizeMembershipPatchPayload({
|
|
123
|
+
roleSid: patch?.roleSid ?? existing?.roleSid ?? "member",
|
|
124
|
+
status: patch?.status ?? existing?.status ?? "active"
|
|
125
|
+
});
|
|
126
|
+
const roleSid = normalizeLowerText(normalizedPatch.roleSid || "member") || "member";
|
|
127
|
+
const status = normalizeLowerText(normalizedPatch.status || "active") || "active";
|
|
114
128
|
|
|
115
129
|
if (!existing) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
try {
|
|
131
|
+
await resourceRuntime.create(
|
|
132
|
+
{
|
|
133
|
+
workspaceId: normalizedWorkspaceId,
|
|
134
|
+
userId: normalizedUserId,
|
|
135
|
+
roleSid,
|
|
136
|
+
status
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
...options,
|
|
140
|
+
trx: client,
|
|
141
|
+
include: "none"
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (!isDuplicateEntryError(error)) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
124
149
|
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
125
150
|
}
|
|
126
151
|
|
|
127
|
-
await
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
status
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
await resourceRuntime.updateById(
|
|
153
|
+
existing.id,
|
|
154
|
+
{
|
|
155
|
+
roleSid,
|
|
156
|
+
status
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
...options,
|
|
160
|
+
trx: client,
|
|
161
|
+
include: "none",
|
|
162
|
+
existingRecord: existing
|
|
163
|
+
}
|
|
164
|
+
);
|
|
134
165
|
|
|
135
166
|
return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
|
|
136
167
|
}
|
|
@@ -154,7 +185,7 @@ function createRepository(knex) {
|
|
|
154
185
|
"up.email"
|
|
155
186
|
]);
|
|
156
187
|
|
|
157
|
-
return rows.map(
|
|
188
|
+
return rows.map(normalizeMemberSummaryRow).filter(Boolean);
|
|
158
189
|
}
|
|
159
190
|
|
|
160
191
|
async function listActiveWorkspaceIdsByUserId(userId, options = {}) {
|
|
@@ -187,4 +218,4 @@ function createRepository(knex) {
|
|
|
187
218
|
});
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
export { createRepository,
|
|
221
|
+
export { createRepository, normalizeMembershipRecord, normalizeMemberSummaryRow };
|
|
@@ -1,41 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
|
|
2
2
|
import {
|
|
3
|
-
normalizeDbRecordId,
|
|
4
3
|
normalizeRecordId,
|
|
5
4
|
normalizeText,
|
|
6
5
|
normalizeLowerText,
|
|
7
|
-
|
|
8
|
-
toNullableIso,
|
|
9
|
-
nowDb,
|
|
10
|
-
isDuplicateEntryError,
|
|
11
|
-
createWithTransaction
|
|
6
|
+
isDuplicateEntryError
|
|
12
7
|
} from "./repositoryUtils.js";
|
|
8
|
+
import { workspacesResource } from "../resources/workspacesResource.js";
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const REPOSITORY_CONFIG = Object.freeze({
|
|
11
|
+
context: "internal.repository.workspaces"
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function normalizeWorkspaceRecord(payload = {}) {
|
|
15
|
+
if (!payload) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
+
return workspacesResource.operations.view.outputValidator.normalize(payload);
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
slug: normalizeText(row.slug),
|
|
22
|
-
name: normalizeText(row.name),
|
|
23
|
-
ownerUserId: normalizeDbRecordId(row.owner_user_id, { fallback: "" }),
|
|
24
|
-
isPersonal: Boolean(row.is_personal),
|
|
25
|
-
avatarUrl: row.avatar_url ? normalizeText(row.avatar_url) : "",
|
|
26
|
-
createdAt: toIsoString(row.created_at),
|
|
27
|
-
updatedAt: toIsoString(row.updated_at),
|
|
28
|
-
deletedAt: toNullableIso(row.deleted_at)
|
|
29
|
-
};
|
|
21
|
+
function normalizeCreatePayload(payload = {}) {
|
|
22
|
+
return workspacesResource.operations.create.bodyValidator.normalize(payload);
|
|
30
23
|
}
|
|
31
24
|
|
|
32
|
-
function
|
|
25
|
+
function normalizeMembershipWorkspaceRow(row) {
|
|
33
26
|
if (!row) {
|
|
34
27
|
return null;
|
|
35
28
|
}
|
|
36
29
|
|
|
37
30
|
return {
|
|
38
|
-
...
|
|
31
|
+
...normalizeWorkspaceRecord(row),
|
|
39
32
|
roleSid: normalizeLowerText(row.role_sid || "member"),
|
|
40
33
|
membershipStatus: normalizeLowerText(row.membership_status || "active") || "active"
|
|
41
34
|
};
|
|
@@ -45,7 +38,8 @@ function createRepository(knex) {
|
|
|
45
38
|
if (typeof knex !== "function") {
|
|
46
39
|
throw new TypeError("workspacesRepository requires knex.");
|
|
47
40
|
}
|
|
48
|
-
const
|
|
41
|
+
const resourceRuntime = createCrudResourceRuntime(workspacesResource, knex, REPOSITORY_CONFIG);
|
|
42
|
+
const withTransaction = resourceRuntime.withTransaction;
|
|
49
43
|
|
|
50
44
|
function workspaceSelectColumns({ includeMembership = false } = {}) {
|
|
51
45
|
const columns = [
|
|
@@ -71,12 +65,10 @@ function createRepository(knex) {
|
|
|
71
65
|
return null;
|
|
72
66
|
}
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.first();
|
|
79
|
-
return mapRow(row);
|
|
68
|
+
return resourceRuntime.findById(normalizedWorkspaceId, {
|
|
69
|
+
...options,
|
|
70
|
+
include: "none"
|
|
71
|
+
});
|
|
80
72
|
}
|
|
81
73
|
|
|
82
74
|
async function findBySlug(slug, options = {}) {
|
|
@@ -90,7 +82,7 @@ function createRepository(knex) {
|
|
|
90
82
|
.where({ "w.slug": normalizedSlug })
|
|
91
83
|
.select(workspaceSelectColumns())
|
|
92
84
|
.first();
|
|
93
|
-
return
|
|
85
|
+
return normalizeWorkspaceRecord(row);
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
async function findPersonalByOwnerUserId(userId, options = {}) {
|
|
@@ -105,41 +97,35 @@ function createRepository(knex) {
|
|
|
105
97
|
.orderBy("w.id", "asc")
|
|
106
98
|
.select(workspaceSelectColumns())
|
|
107
99
|
.first();
|
|
108
|
-
return
|
|
100
|
+
return normalizeWorkspaceRecord(row);
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
async function insert(payload = {}, options = {}) {
|
|
112
104
|
const client = options?.trx || knex;
|
|
113
|
-
const
|
|
114
|
-
const ownerUserId = normalizeRecordId(
|
|
105
|
+
const normalizedPayload = normalizeCreatePayload(payload);
|
|
106
|
+
const ownerUserId = normalizeRecordId(normalizedPayload.ownerUserId, { fallback: null });
|
|
115
107
|
if (!ownerUserId) {
|
|
116
108
|
throw new TypeError("workspacesRepository.insert requires ownerUserId.");
|
|
117
109
|
}
|
|
118
110
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
avatar_url: normalizeText(source.avatarUrl),
|
|
125
|
-
created_at: nowDb(),
|
|
126
|
-
updated_at: nowDb(),
|
|
127
|
-
deleted_at: null
|
|
111
|
+
const createPayload = {
|
|
112
|
+
...normalizedPayload,
|
|
113
|
+
ownerUserId,
|
|
114
|
+
isPersonal: normalizedPayload.isPersonal === true,
|
|
115
|
+
avatarUrl: normalizeText(normalizedPayload.avatarUrl)
|
|
128
116
|
};
|
|
129
117
|
|
|
130
118
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
const bySlug = await findBySlug(insertPayload.slug, { trx: client });
|
|
137
|
-
return bySlug;
|
|
119
|
+
return await resourceRuntime.create(createPayload, {
|
|
120
|
+
...options,
|
|
121
|
+
trx: client,
|
|
122
|
+
include: "none"
|
|
123
|
+
});
|
|
138
124
|
} catch (error) {
|
|
139
125
|
if (!isDuplicateEntryError(error)) {
|
|
140
126
|
throw error;
|
|
141
127
|
}
|
|
142
|
-
const bySlug = await findBySlug(
|
|
128
|
+
const bySlug = await findBySlug(createPayload.slug, { trx: client });
|
|
143
129
|
if (bySlug) {
|
|
144
130
|
return bySlug;
|
|
145
131
|
}
|
|
@@ -153,21 +139,10 @@ function createRepository(knex) {
|
|
|
153
139
|
return null;
|
|
154
140
|
}
|
|
155
141
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
if (Object.hasOwn(source, "name")) {
|
|
163
|
-
dbPatch.name = normalizeText(source.name);
|
|
164
|
-
}
|
|
165
|
-
if (Object.hasOwn(source, "avatarUrl")) {
|
|
166
|
-
dbPatch.avatar_url = normalizeText(source.avatarUrl);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
await client("workspaces").where({ id: normalizedWorkspaceId }).update(dbPatch);
|
|
170
|
-
return findById(normalizedWorkspaceId, { trx: client });
|
|
142
|
+
return resourceRuntime.updateById(normalizedWorkspaceId, patch, {
|
|
143
|
+
...options,
|
|
144
|
+
include: "none"
|
|
145
|
+
});
|
|
171
146
|
}
|
|
172
147
|
|
|
173
148
|
async function listForUserId(userId, options = {}) {
|
|
@@ -185,7 +160,7 @@ function createRepository(knex) {
|
|
|
185
160
|
.orderBy("w.id", "asc")
|
|
186
161
|
.select(workspaceSelectColumns({ includeMembership: true }));
|
|
187
162
|
|
|
188
|
-
return rows.map(
|
|
163
|
+
return rows.map(normalizeMembershipWorkspaceRow).filter(Boolean);
|
|
189
164
|
}
|
|
190
165
|
|
|
191
166
|
return Object.freeze({
|
|
@@ -199,4 +174,4 @@ function createRepository(knex) {
|
|
|
199
174
|
});
|
|
200
175
|
}
|
|
201
176
|
|
|
202
|
-
export { createRepository,
|
|
177
|
+
export { createRepository, normalizeWorkspaceRecord, normalizeMembershipWorkspaceRow };
|