@jskit-ai/users-core 0.1.48 → 0.1.50

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.
Files changed (87) hide show
  1. package/package.descriptor.mjs +7 -7
  2. package/package.json +7 -17
  3. package/src/server/common/services/authProfileSyncService.js +28 -7
  4. package/src/server/common/support/realtimeServiceEvents.js +1 -59
  5. package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
  6. package/src/server/registerUsersBootstrap.js +0 -1
  7. package/src/server/registerUsersCore.js +2 -14
  8. package/src/server/usersBootstrapContributor.js +2 -64
  9. package/src/shared/index.js +2 -99
  10. package/src/shared/settings.js +1 -119
  11. package/test/authProfileSyncService.test.js +19 -10
  12. package/test/registerServiceRealtimeEvents.test.js +0 -86
  13. package/test/registerUsersCore.test.js +6 -15
  14. package/test/repositoryContracts.test.js +1 -9
  15. package/test/resourcesCanonical.test.js +0 -16
  16. package/test/settingsFieldRegistriesSingleton.test.js +0 -5
  17. package/test/usersBootstrapContributor.test.js +2 -26
  18. package/test/usersRouteResources.test.js +0 -16
  19. package/src/server/UsersWorkspacesServiceProvider.js +0 -44
  20. package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
  21. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
  22. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
  23. package/src/server/common/formatters/workspaceFormatter.js +0 -53
  24. package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
  25. package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
  26. package/src/server/common/repositories/workspacesRepository.js +0 -202
  27. package/src/server/common/services/workspaceContextService.js +0 -281
  28. package/src/server/common/support/workspaceRoutePaths.js +0 -17
  29. package/src/server/common/validators/routeParamsValidator.js +0 -62
  30. package/src/server/registerWorkspaceBootstrap.js +0 -27
  31. package/src/server/registerWorkspaceCore.js +0 -73
  32. package/src/server/registerWorkspaceRepositories.js +0 -26
  33. package/src/server/support/resolveWorkspace.js +0 -16
  34. package/src/server/support/workspaceActionSurfaces.js +0 -118
  35. package/src/server/support/workspaceInvitationsPolicy.js +0 -45
  36. package/src/server/support/workspaceRouteInput.js +0 -22
  37. package/src/server/workspaceBootstrapContributor.js +0 -212
  38. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
  39. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
  40. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
  41. package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
  42. package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
  43. package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
  44. package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
  45. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
  46. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
  47. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
  48. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
  49. package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
  50. package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
  51. package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
  52. package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
  53. package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
  54. package/src/shared/resources/workspaceMembersResource.js +0 -354
  55. package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
  56. package/src/shared/resources/workspaceResource.js +0 -176
  57. package/src/shared/resources/workspaceSettingsFields.js +0 -59
  58. package/src/shared/resources/workspaceSettingsResource.js +0 -169
  59. package/src/shared/roles.js +0 -161
  60. package/src/shared/support/usersApiPaths.js +0 -43
  61. package/src/shared/support/usersVisibility.js +0 -42
  62. package/src/shared/support/workspacePathModel.js +0 -145
  63. package/src/shared/tenancyMode.js +0 -35
  64. package/src/shared/tenancyProfile.js +0 -73
  65. package/test/registerWorkspaceDirectory.test.js +0 -31
  66. package/test/registerWorkspaceSettings.test.js +0 -40
  67. package/test/roles.test.js +0 -159
  68. package/test/tenancyProfile.test.js +0 -67
  69. package/test/usersApiPaths.test.js +0 -49
  70. package/test/usersRouteValidators.test.js +0 -49
  71. package/test/usersVisibility.test.js +0 -27
  72. package/test/workspaceActionContextContributor.test.js +0 -344
  73. package/test/workspaceActionSurfaces.test.js +0 -85
  74. package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
  75. package/test/workspaceBootstrapContributor.test.js +0 -154
  76. package/test/workspaceInvitationsPolicy.test.js +0 -71
  77. package/test/workspaceInvitesRepository.test.js +0 -111
  78. package/test/workspaceMembersService.test.js +0 -398
  79. package/test/workspacePathModel.test.js +0 -93
  80. package/test/workspacePendingInvitationsResource.test.js +0 -38
  81. package/test/workspacePendingInvitationsService.test.js +0 -151
  82. package/test/workspaceRouteVisibilityResolver.test.js +0 -83
  83. package/test/workspaceService.test.js +0 -546
  84. package/test/workspaceSettingsActions.test.js +0 -52
  85. package/test/workspaceSettingsRepository.test.js +0 -202
  86. package/test/workspaceSettingsResource.test.js +0 -169
  87. package/test/workspaceSettingsService.test.js +0 -140
@@ -1,53 +0,0 @@
1
- import { resolveWorkspaceThemePalettes } from "../../../shared/settings.js";
2
- import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
3
- import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
4
-
5
- function mapWorkspaceSummary(workspace, membership) {
6
- return {
7
- id: normalizeRecordId(workspace.id, { fallback: "" }),
8
- slug: normalizeText(workspace.slug),
9
- name: normalizeText(workspace.name),
10
- avatarUrl: normalizeText(workspace.avatarUrl),
11
- roleSid: normalizeLowerText(membership?.roleSid || "member") || "member",
12
- isAccessible: normalizeLowerText(membership?.status || "active") === "active"
13
- };
14
- }
15
-
16
- function mapWorkspaceSettingsPublic(workspaceSettings, { workspaceInvitationsEnabled = true } = {}) {
17
- const source = workspaceSettings && typeof workspaceSettings === "object" ? workspaceSettings : {};
18
- const invitesAvailable = workspaceInvitationsEnabled === true;
19
- const invitesEnabled = invitesAvailable && source.invitesEnabled !== false;
20
- const themePalettes = resolveWorkspaceThemePalettes(source);
21
-
22
- return {
23
- lightPrimaryColor: themePalettes.light.color,
24
- lightSecondaryColor: themePalettes.light.secondaryColor,
25
- lightSurfaceColor: themePalettes.light.surfaceColor,
26
- lightSurfaceVariantColor: themePalettes.light.surfaceVariantColor,
27
- darkPrimaryColor: themePalettes.dark.color,
28
- darkSecondaryColor: themePalettes.dark.secondaryColor,
29
- darkSurfaceColor: themePalettes.dark.surfaceColor,
30
- darkSurfaceVariantColor: themePalettes.dark.surfaceVariantColor,
31
- invitesEnabled,
32
- invitesAvailable,
33
- invitesEffective: invitesAvailable && invitesEnabled
34
- };
35
- }
36
-
37
- function mapMembershipSummary(membership, workspace) {
38
- if (!membership) {
39
- return null;
40
- }
41
-
42
- return {
43
- workspaceId: normalizeRecordId(workspace?.id || membership.workspaceId, { fallback: "" }),
44
- roleSid: normalizeLowerText(membership.roleSid || "member") || "member",
45
- status: normalizeLowerText(membership.status || "active") || "active"
46
- };
47
- }
48
-
49
- export {
50
- mapMembershipSummary,
51
- mapWorkspaceSettingsPublic,
52
- mapWorkspaceSummary
53
- };
@@ -1,208 +0,0 @@
1
- import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
2
- import {
3
- normalizeLowerText,
4
- normalizeDbRecordId,
5
- normalizeRecordId,
6
- normalizeText,
7
- toIsoString,
8
- toNullableIso,
9
- toNullableDateTime,
10
- nowDb,
11
- isDuplicateEntryError,
12
- createWithTransaction
13
- } from "./repositoryUtils.js";
14
-
15
- function mapRow(row) {
16
- if (!row) {
17
- return null;
18
- }
19
-
20
- return {
21
- id: normalizeDbRecordId(row.id, { fallback: "" }),
22
- workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" }),
23
- email: normalizeLowerText(row.email),
24
- roleSid: normalizeLowerText(row.role_sid || "member") || "member",
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
36
- };
37
- }
38
-
39
- const WORKSPACE_INVITE_WITH_WORKSPACE_SELECT = Object.freeze([
40
- "wi.*",
41
- "w.slug as workspace_slug",
42
- "w.name as workspace_name",
43
- "w.avatar_url as workspace_avatar_url"
44
- ]);
45
-
46
- function createRepository(knex) {
47
- if (typeof knex !== "function") {
48
- throw new TypeError("workspaceInvitesRepository requires knex.");
49
- }
50
- const withTransaction = createWithTransaction(knex);
51
-
52
- async function findPendingByTokenHash(tokenHash, options = {}) {
53
- const client = options?.trx || knex;
54
- const row = await client("workspace_invites")
55
- .where({ token_hash: normalizeText(tokenHash), status: "pending" })
56
- .first();
57
- return mapRow(row);
58
- }
59
-
60
- async function listPendingByEmail(email, options = {}) {
61
- const client = options?.trx || knex;
62
- const normalizedEmail = normalizeLowerText(email);
63
- if (!normalizedEmail) {
64
- return [];
65
- }
66
-
67
- const rows = await client("workspace_invites as wi")
68
- .join("workspaces as w", "w.id", "wi.workspace_id")
69
- .where({ "wi.email": normalizedEmail, "wi.status": "pending" })
70
- .orderBy("wi.created_at", "desc")
71
- .select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
72
-
73
- return rows.map(mapRow).filter(Boolean);
74
- }
75
-
76
- async function listPendingByWorkspaceIdWithWorkspace(workspaceId, options = {}) {
77
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
78
- if (!normalizedWorkspaceId) {
79
- return [];
80
- }
81
-
82
- const client = options?.trx || knex;
83
- const rows = await client("workspace_invites as wi")
84
- .join("workspaces as w", "w.id", "wi.workspace_id")
85
- .where({ "wi.workspace_id": normalizedWorkspaceId, "wi.status": "pending" })
86
- .orderBy("wi.created_at", "desc")
87
- .select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
88
-
89
- return rows.map(mapRow).filter(Boolean);
90
- }
91
-
92
- async function insert(payload = {}, options = {}) {
93
- const client = options?.trx || knex;
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
- }
99
-
100
- const insertPayload = {
101
- workspace_id: workspaceId,
102
- email: normalizeLowerText(source.email),
103
- role_sid: normalizeLowerText(source.roleSid || "member") || "member",
104
- status: normalizeLowerText(source.status || "pending") || "pending",
105
- token_hash: normalizeText(source.tokenHash),
106
- invited_by_user_id: source.invitedByUserId == null ? null : normalizeRecordId(source.invitedByUserId, { fallback: null }),
107
- expires_at: toNullableDateTime(source.expiresAt),
108
- accepted_at: null,
109
- revoked_at: null,
110
- created_at: nowDb(),
111
- updated_at: nowDb()
112
- };
113
-
114
- try {
115
- const result = await client("workspace_invites").insert(insertPayload);
116
- const insertedId = resolveInsertedRecordId(result, { fallback: null });
117
- if (insertedId) {
118
- const row = await client("workspace_invites").where({ id: insertedId }).first();
119
- return mapRow(row);
120
- }
121
- } catch (error) {
122
- if (!isDuplicateEntryError(error)) {
123
- throw error;
124
- }
125
- }
126
-
127
- const row = await client("workspace_invites")
128
- .where({ workspace_id: insertPayload.workspace_id, email: insertPayload.email, status: "pending" })
129
- .orderBy("id", "desc")
130
- .first();
131
- return mapRow(row);
132
- }
133
-
134
- async function expirePendingByWorkspaceIdAndEmail(workspaceId, email, options = {}) {
135
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
136
- if (!normalizedWorkspaceId) {
137
- return;
138
- }
139
-
140
- const client = options?.trx || knex;
141
- await client("workspace_invites")
142
- .where({ workspace_id: normalizedWorkspaceId, email: normalizeLowerText(email), status: "pending" })
143
- .update({
144
- status: "expired",
145
- updated_at: nowDb()
146
- });
147
- }
148
-
149
- async function markAcceptedById(inviteId, options = {}) {
150
- const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
151
- if (!normalizedInviteId) {
152
- return;
153
- }
154
-
155
- const client = options?.trx || knex;
156
- await client("workspace_invites")
157
- .where({ id: normalizedInviteId })
158
- .update({
159
- status: "accepted",
160
- accepted_at: nowDb(),
161
- updated_at: nowDb()
162
- });
163
- }
164
-
165
- async function revokeById(inviteId, options = {}) {
166
- const normalizedInviteId = normalizeRecordId(inviteId, { fallback: null });
167
- if (!normalizedInviteId) {
168
- return;
169
- }
170
-
171
- const client = options?.trx || knex;
172
- await client("workspace_invites")
173
- .where({ id: normalizedInviteId })
174
- .update({
175
- status: "revoked",
176
- revoked_at: nowDb(),
177
- updated_at: nowDb()
178
- });
179
- }
180
-
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
-
188
- const client = options?.trx || knex;
189
- const row = await client("workspace_invites")
190
- .where({ id: normalizedInviteId, workspace_id: normalizedWorkspaceId, status: "pending" })
191
- .first();
192
- return mapRow(row);
193
- }
194
-
195
- return Object.freeze({
196
- withTransaction,
197
- findPendingByTokenHash,
198
- listPendingByEmail,
199
- listPendingByWorkspaceIdWithWorkspace,
200
- insert,
201
- expirePendingByWorkspaceIdAndEmail,
202
- markAcceptedById,
203
- revokeById,
204
- findPendingByIdForWorkspace
205
- });
206
- }
207
-
208
- export { createRepository, mapRow };
@@ -1,190 +0,0 @@
1
- import {
2
- normalizeLowerText,
3
- normalizeRecordId,
4
- normalizeDbRecordId,
5
- normalizeText,
6
- toIsoString,
7
- nowDb,
8
- isDuplicateEntryError,
9
- createWithTransaction
10
- } from "./repositoryUtils.js";
11
- import { OWNER_ROLE_ID } from "../../../shared/roles.js";
12
-
13
- function mapRow(row) {
14
- if (!row) {
15
- return null;
16
- }
17
-
18
- return {
19
- id: normalizeDbRecordId(row.id, { fallback: "" }),
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
- };
27
- }
28
-
29
- function mapMemberSummaryRow(row) {
30
- if (!row) {
31
- return null;
32
- }
33
-
34
- return {
35
- userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
36
- roleSid: normalizeLowerText(row.role_sid || "member") || "member",
37
- status: normalizeLowerText(row.status || "active") || "active",
38
- displayName: normalizeText(row.display_name),
39
- email: normalizeLowerText(row.email)
40
- };
41
- }
42
-
43
- function createRepository(knex) {
44
- if (typeof knex !== "function") {
45
- throw new TypeError("workspaceMembershipsRepository requires knex.");
46
- }
47
- const withTransaction = createWithTransaction(knex);
48
-
49
- async function findByWorkspaceIdAndUserId(workspaceId, userId, options = {}) {
50
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
51
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
52
- if (!normalizedWorkspaceId || !normalizedUserId) {
53
- return null;
54
- }
55
-
56
- const client = options?.trx || knex;
57
- const row = await client("workspace_memberships")
58
- .where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
59
- .first();
60
- return mapRow(row);
61
- }
62
-
63
- async function ensureOwnerMembership(workspaceId, userId, options = {}) {
64
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
65
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
66
- if (!normalizedWorkspaceId || !normalizedUserId) {
67
- throw new TypeError("workspaceMembershipsRepository.ensureOwnerMembership requires workspaceId and userId.");
68
- }
69
-
70
- const client = options?.trx || knex;
71
- const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
72
- if (existing) {
73
- if (existing.roleSid !== OWNER_ROLE_ID || existing.status !== "active") {
74
- await client("workspace_memberships")
75
- .where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
76
- .update({
77
- role_sid: OWNER_ROLE_ID,
78
- status: "active",
79
- updated_at: nowDb()
80
- });
81
- }
82
- return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
83
- }
84
-
85
- try {
86
- await client("workspace_memberships").insert({
87
- workspace_id: normalizedWorkspaceId,
88
- user_id: normalizedUserId,
89
- role_sid: OWNER_ROLE_ID,
90
- status: "active",
91
- created_at: nowDb(),
92
- updated_at: nowDb()
93
- });
94
- } catch (error) {
95
- if (!isDuplicateEntryError(error)) {
96
- throw error;
97
- }
98
- }
99
-
100
- return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
101
- }
102
-
103
- async function upsertMembership(workspaceId, userId, patch = {}, options = {}) {
104
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
105
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
106
- if (!normalizedWorkspaceId || !normalizedUserId) {
107
- throw new TypeError("workspaceMembershipsRepository.upsertMembership requires workspaceId and userId.");
108
- }
109
-
110
- const client = options?.trx || knex;
111
- const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
112
- const roleSid = normalizeLowerText(patch.roleSid || existing?.roleSid || "member") || "member";
113
- const status = normalizeLowerText(patch.status || existing?.status || "active") || "active";
114
-
115
- if (!existing) {
116
- await client("workspace_memberships").insert({
117
- workspace_id: normalizedWorkspaceId,
118
- user_id: normalizedUserId,
119
- role_sid: roleSid,
120
- status,
121
- created_at: nowDb(),
122
- updated_at: nowDb()
123
- });
124
- return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
125
- }
126
-
127
- await client("workspace_memberships")
128
- .where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
129
- .update({
130
- role_sid: roleSid,
131
- status,
132
- updated_at: nowDb()
133
- });
134
-
135
- return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
136
- }
137
-
138
- async function listActiveByWorkspaceId(workspaceId, options = {}) {
139
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
140
- if (!normalizedWorkspaceId) {
141
- return [];
142
- }
143
-
144
- const client = options?.trx || knex;
145
- const rows = await client("workspace_memberships as wm")
146
- .join("users as up", "up.id", "wm.user_id")
147
- .where({ "wm.workspace_id": normalizedWorkspaceId, "wm.status": "active" })
148
- .orderBy("up.display_name", "asc")
149
- .select([
150
- "wm.user_id",
151
- "wm.role_sid",
152
- "wm.status",
153
- "up.display_name",
154
- "up.email"
155
- ]);
156
-
157
- return rows.map(mapMemberSummaryRow).filter(Boolean);
158
- }
159
-
160
- async function listActiveWorkspaceIdsByUserId(userId, options = {}) {
161
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
162
- if (!normalizedUserId) {
163
- return [];
164
- }
165
-
166
- const client = options?.trx || knex;
167
- const rows = await client("workspace_memberships")
168
- .where({
169
- user_id: normalizedUserId,
170
- status: "active"
171
- })
172
- .select("workspace_id")
173
- .orderBy("workspace_id", "asc");
174
-
175
- return rows
176
- .map((row) => normalizeDbRecordId(row.workspace_id, { fallback: null }))
177
- .filter(Boolean);
178
- }
179
-
180
- return Object.freeze({
181
- withTransaction,
182
- findByWorkspaceIdAndUserId,
183
- ensureOwnerMembership,
184
- upsertMembership,
185
- listActiveByWorkspaceId,
186
- listActiveWorkspaceIdsByUserId
187
- });
188
- }
189
-
190
- export { createRepository, mapRow, mapMemberSummaryRow };
@@ -1,202 +0,0 @@
1
- import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
2
- import {
3
- normalizeDbRecordId,
4
- normalizeRecordId,
5
- normalizeText,
6
- normalizeLowerText,
7
- toIsoString,
8
- toNullableIso,
9
- nowDb,
10
- isDuplicateEntryError,
11
- createWithTransaction
12
- } from "./repositoryUtils.js";
13
-
14
- function mapRow(row) {
15
- if (!row) {
16
- return null;
17
- }
18
-
19
- return {
20
- id: normalizeDbRecordId(row.id, { fallback: "" }),
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
- };
30
- }
31
-
32
- function mapMembershipWorkspaceRow(row) {
33
- if (!row) {
34
- return null;
35
- }
36
-
37
- return {
38
- ...mapRow(row),
39
- roleSid: normalizeLowerText(row.role_sid || "member"),
40
- membershipStatus: normalizeLowerText(row.membership_status || "active") || "active"
41
- };
42
- }
43
-
44
- function createRepository(knex) {
45
- if (typeof knex !== "function") {
46
- throw new TypeError("workspacesRepository requires knex.");
47
- }
48
- const withTransaction = createWithTransaction(knex);
49
-
50
- function workspaceSelectColumns({ includeMembership = false } = {}) {
51
- const columns = [
52
- "w.id",
53
- "w.slug",
54
- "w.name",
55
- "w.owner_user_id",
56
- "w.is_personal",
57
- "w.avatar_url",
58
- "w.created_at",
59
- "w.updated_at",
60
- "w.deleted_at"
61
- ];
62
- if (includeMembership) {
63
- columns.push("wm.role_sid", "wm.status as membership_status");
64
- }
65
- return columns;
66
- }
67
-
68
- async function findById(workspaceId, options = {}) {
69
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
70
- if (!normalizedWorkspaceId) {
71
- return null;
72
- }
73
-
74
- const client = options?.trx || knex;
75
- const row = await client("workspaces as w")
76
- .where({ "w.id": normalizedWorkspaceId })
77
- .select(workspaceSelectColumns())
78
- .first();
79
- return mapRow(row);
80
- }
81
-
82
- async function findBySlug(slug, options = {}) {
83
- const client = options?.trx || knex;
84
- const normalizedSlug = normalizeLowerText(slug);
85
- if (!normalizedSlug) {
86
- return null;
87
- }
88
-
89
- const row = await client("workspaces as w")
90
- .where({ "w.slug": normalizedSlug })
91
- .select(workspaceSelectColumns())
92
- .first();
93
- return mapRow(row);
94
- }
95
-
96
- async function findPersonalByOwnerUserId(userId, options = {}) {
97
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
98
- if (!normalizedUserId) {
99
- return null;
100
- }
101
-
102
- const client = options?.trx || knex;
103
- const row = await client("workspaces as w")
104
- .where({ "w.owner_user_id": normalizedUserId, "w.is_personal": 1 })
105
- .orderBy("w.id", "asc")
106
- .select(workspaceSelectColumns())
107
- .first();
108
- return mapRow(row);
109
- }
110
-
111
- async function insert(payload = {}, options = {}) {
112
- const client = options?.trx || knex;
113
- const source = payload && typeof payload === "object" ? payload : {};
114
- const ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: null });
115
- if (!ownerUserId) {
116
- throw new TypeError("workspacesRepository.insert requires ownerUserId.");
117
- }
118
-
119
- const insertPayload = {
120
- slug: normalizeLowerText(source.slug),
121
- name: normalizeText(source.name),
122
- owner_user_id: ownerUserId,
123
- is_personal: source.isPersonal ? 1 : 0,
124
- avatar_url: normalizeText(source.avatarUrl),
125
- created_at: nowDb(),
126
- updated_at: nowDb(),
127
- deleted_at: null
128
- };
129
-
130
- try {
131
- const result = await client("workspaces").insert(insertPayload);
132
- const insertedId = resolveInsertedRecordId(result, { fallback: null });
133
- if (insertedId) {
134
- return findById(insertedId, { trx: client });
135
- }
136
- const bySlug = await findBySlug(insertPayload.slug, { trx: client });
137
- return bySlug;
138
- } catch (error) {
139
- if (!isDuplicateEntryError(error)) {
140
- throw error;
141
- }
142
- const bySlug = await findBySlug(insertPayload.slug, { trx: client });
143
- if (bySlug) {
144
- return bySlug;
145
- }
146
- throw error;
147
- }
148
- }
149
-
150
- async function updateById(workspaceId, patch = {}, options = {}) {
151
- const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
152
- if (!normalizedWorkspaceId) {
153
- return null;
154
- }
155
-
156
- const client = options?.trx || knex;
157
- const source = patch && typeof patch === "object" ? patch : {};
158
- const dbPatch = {
159
- updated_at: nowDb()
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 });
171
- }
172
-
173
- async function listForUserId(userId, options = {}) {
174
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
175
- if (!normalizedUserId) {
176
- return [];
177
- }
178
-
179
- const client = options?.trx || knex;
180
- const rows = await client("workspace_memberships as wm")
181
- .join("workspaces as w", "w.id", "wm.workspace_id")
182
- .where({ "wm.user_id": normalizedUserId })
183
- .whereNull("w.deleted_at")
184
- .orderBy("w.is_personal", "desc")
185
- .orderBy("w.id", "asc")
186
- .select(workspaceSelectColumns({ includeMembership: true }));
187
-
188
- return rows.map(mapMembershipWorkspaceRow).filter(Boolean);
189
- }
190
-
191
- return Object.freeze({
192
- withTransaction,
193
- findById,
194
- findBySlug,
195
- findPersonalByOwnerUserId,
196
- insert,
197
- updateById,
198
- listForUserId
199
- });
200
- }
201
-
202
- export { createRepository, mapRow, mapMembershipWorkspaceRow };