@jskit-ai/users-core 0.1.47 → 0.1.49

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 (104) hide show
  1. package/package.descriptor.mjs +9 -46
  2. package/package.json +8 -19
  3. package/src/server/UsersCoreServiceProvider.js +0 -4
  4. package/src/server/common/registerCommonRepositories.js +0 -5
  5. package/src/server/common/services/authProfileSyncService.js +28 -7
  6. package/src/server/common/support/realtimeServiceEvents.js +1 -59
  7. package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
  8. package/src/server/registerUsersBootstrap.js +1 -3
  9. package/src/server/registerUsersCore.js +2 -14
  10. package/src/server/usersBootstrapContributor.js +10 -85
  11. package/src/shared/index.js +2 -99
  12. package/src/shared/settings.js +1 -119
  13. package/templates/migrations/users_core_generic_initial.cjs +0 -16
  14. package/test/authProfileSyncService.test.js +19 -10
  15. package/test/registerServiceRealtimeEvents.test.js +0 -94
  16. package/test/registerUsersCore.test.js +6 -19
  17. package/test/repositoryContracts.test.js +1 -11
  18. package/test/resourcesCanonical.test.js +1 -19
  19. package/test/settingsFieldRegistriesSingleton.test.js +0 -10
  20. package/test/usersBootstrapContributor.test.js +20 -38
  21. package/test/usersRouteRequestInputValidator.test.js +2 -43
  22. package/test/usersRouteResources.test.js +2 -20
  23. package/test-support/registerDefaultSettingsFields.js +0 -1
  24. package/src/server/UsersWorkspacesServiceProvider.js +0 -44
  25. package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
  26. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
  27. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
  28. package/src/server/common/formatters/workspaceFormatter.js +0 -53
  29. package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
  30. package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
  31. package/src/server/common/repositories/workspacesRepository.js +0 -202
  32. package/src/server/common/services/workspaceContextService.js +0 -281
  33. package/src/server/common/support/workspaceRoutePaths.js +0 -17
  34. package/src/server/common/validators/routeParamsValidator.js +0 -62
  35. package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +0 -63
  36. package/src/server/consoleSettings/consoleService.js +0 -36
  37. package/src/server/consoleSettings/consoleSettingsActions.js +0 -55
  38. package/src/server/consoleSettings/consoleSettingsRepository.js +0 -115
  39. package/src/server/consoleSettings/consoleSettingsService.js +0 -40
  40. package/src/server/consoleSettings/registerConsoleSettings.js +0 -56
  41. package/src/server/registerWorkspaceBootstrap.js +0 -27
  42. package/src/server/registerWorkspaceCore.js +0 -73
  43. package/src/server/registerWorkspaceRepositories.js +0 -26
  44. package/src/server/support/resolveWorkspace.js +0 -16
  45. package/src/server/support/workspaceActionSurfaces.js +0 -135
  46. package/src/server/support/workspaceInvitationsPolicy.js +0 -45
  47. package/src/server/support/workspaceRouteInput.js +0 -22
  48. package/src/server/workspaceBootstrapContributor.js +0 -211
  49. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
  50. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
  51. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
  52. package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
  53. package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
  54. package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
  55. package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
  56. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
  57. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
  58. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
  59. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
  60. package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
  61. package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
  62. package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
  63. package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
  64. package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
  65. package/src/shared/resources/consoleSettingsFields.js +0 -54
  66. package/src/shared/resources/consoleSettingsResource.js +0 -119
  67. package/src/shared/resources/workspaceMembersResource.js +0 -354
  68. package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
  69. package/src/shared/resources/workspaceResource.js +0 -176
  70. package/src/shared/resources/workspaceSettingsFields.js +0 -59
  71. package/src/shared/resources/workspaceSettingsResource.js +0 -169
  72. package/src/shared/roles.js +0 -161
  73. package/src/shared/support/usersApiPaths.js +0 -43
  74. package/src/shared/support/usersVisibility.js +0 -42
  75. package/src/shared/support/workspacePathModel.js +0 -145
  76. package/src/shared/tenancyMode.js +0 -35
  77. package/src/shared/tenancyProfile.js +0 -73
  78. package/templates/migrations/users_core_console_owner.cjs +0 -37
  79. package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +0 -11
  80. package/test/consoleService.test.js +0 -57
  81. package/test/consoleSettingsService.test.js +0 -86
  82. package/test/registerWorkspaceDirectory.test.js +0 -31
  83. package/test/registerWorkspaceSettings.test.js +0 -40
  84. package/test/roles.test.js +0 -159
  85. package/test/tenancyProfile.test.js +0 -67
  86. package/test/usersApiPaths.test.js +0 -49
  87. package/test/usersRouteValidators.test.js +0 -49
  88. package/test/usersVisibility.test.js +0 -27
  89. package/test/workspaceActionContextContributor.test.js +0 -344
  90. package/test/workspaceActionSurfaces.test.js +0 -105
  91. package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
  92. package/test/workspaceBootstrapContributor.test.js +0 -154
  93. package/test/workspaceInvitationsPolicy.test.js +0 -71
  94. package/test/workspaceInvitesRepository.test.js +0 -111
  95. package/test/workspaceMembersService.test.js +0 -398
  96. package/test/workspacePathModel.test.js +0 -93
  97. package/test/workspacePendingInvitationsResource.test.js +0 -38
  98. package/test/workspacePendingInvitationsService.test.js +0 -151
  99. package/test/workspaceRouteVisibilityResolver.test.js +0 -83
  100. package/test/workspaceService.test.js +0 -546
  101. package/test/workspaceSettingsActions.test.js +0 -52
  102. package/test/workspaceSettingsRepository.test.js +0 -202
  103. package/test/workspaceSettingsResource.test.js +0 -169
  104. package/test/workspaceSettingsService.test.js +0 -140
@@ -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 };