@jskit-ai/workspaces-core 0.1.30 → 0.1.32

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 (61) hide show
  1. package/package.descriptor.mjs +11 -22
  2. package/package.json +11 -9
  3. package/src/server/WorkspacesCoreServiceProvider.js +22 -2
  4. package/src/server/common/repositories/workspaceInvitesRepository.js +233 -78
  5. package/src/server/common/repositories/workspaceMembershipsRepository.js +177 -86
  6. package/src/server/common/repositories/workspacesRepository.js +179 -86
  7. package/src/server/common/services/workspaceContextService.js +26 -24
  8. package/src/server/common/validators/routeParamsValidator.js +36 -53
  9. package/src/server/registerWorkspaceCore.js +6 -7
  10. package/src/server/registerWorkspaceRepositories.js +7 -3
  11. package/src/server/support/workspaceServerScopeSupport.js +1 -1
  12. package/src/server/workspaceBootstrapContributor.js +5 -14
  13. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +54 -27
  14. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +30 -24
  15. package/src/server/workspaceMembers/bootWorkspaceMembers.js +70 -32
  16. package/src/server/workspaceMembers/workspaceMembersActions.js +61 -27
  17. package/src/server/workspaceMembers/workspaceMembersService.js +43 -7
  18. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +28 -13
  19. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +13 -15
  20. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +33 -10
  21. package/src/server/workspaceSettings/bootWorkspaceSettings.js +32 -13
  22. package/src/server/workspaceSettings/registerWorkspaceSettings.js +5 -1
  23. package/src/server/workspaceSettings/workspaceSettingsActions.js +18 -12
  24. package/src/server/workspaceSettings/workspaceSettingsRepository.js +104 -91
  25. package/src/server/workspaceSettings/workspaceSettingsService.js +5 -6
  26. package/src/shared/jsonApiTransports.js +79 -0
  27. package/src/shared/resources/workspaceInvitesResource.js +158 -0
  28. package/src/shared/resources/workspaceMembersResource.js +176 -311
  29. package/src/shared/resources/workspaceMembershipsResource.js +96 -0
  30. package/src/shared/resources/workspacePendingInvitationsResource.js +25 -72
  31. package/src/shared/resources/workspaceResource.js +113 -144
  32. package/src/shared/resources/workspaceRoleCatalogSchema.js +31 -0
  33. package/src/shared/resources/workspaceSettingsResource.js +276 -148
  34. package/test/repositoryContracts.test.js +16 -4
  35. package/test/resourcesCanonical.test.js +39 -16
  36. package/test/routeParamsValidator.test.js +37 -19
  37. package/test/usersRouteResources.test.js +27 -17
  38. package/test/workspaceActionContextContributor.test.js +1 -1
  39. package/test/workspaceInternalCrudResources.test.js +98 -0
  40. package/test/workspaceInvitesRepository.test.js +196 -148
  41. package/test/workspaceMembersResource.test.js +35 -0
  42. package/test/workspaceMembershipsRepository.test.js +155 -115
  43. package/test/workspacePendingInvitationsResource.test.js +18 -23
  44. package/test/workspacePendingInvitationsService.test.js +2 -1
  45. package/test/workspaceServerScopeSupport.test.js +21 -3
  46. package/test/workspaceSettingsActions.test.js +5 -7
  47. package/test/workspaceSettingsInternalResource.test.js +8 -0
  48. package/test/workspaceSettingsRepository.test.js +158 -123
  49. package/test/workspaceSettingsResource.test.js +51 -62
  50. package/test/workspaceSettingsService.test.js +0 -1
  51. package/test/workspacesRepository.test.js +318 -174
  52. package/test/workspacesRouteRequestInputValidator.test.js +25 -11
  53. package/src/server/common/resources/workspaceInvitesResource.js +0 -207
  54. package/src/server/common/resources/workspaceMembershipsResource.js +0 -154
  55. package/src/server/common/resources/workspacesResource.js +0 -170
  56. package/src/server/common/validators/authenticatedUserValidator.js +0 -43
  57. package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
  58. package/src/shared/resources/workspaceSettingsFields.js +0 -65
  59. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
  60. package/test/settingsFieldRegistriesSingleton.test.js +0 -14
  61. package/test-support/registerDefaultSettingsFields.js +0 -1
@@ -1,10 +1,13 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/workspaces-core",
4
- version: "0.1.30",
4
+ version: "0.1.32",
5
5
  kind: "runtime",
6
6
  description: "Workspace tenancy runtime plus HTTP routes, role catalog, and workspace config scaffolding.",
7
7
  dependsOn: [
8
+ "@jskit-ai/json-rest-api-core",
9
+ "@jskit-ai/resource-core",
10
+ "@jskit-ai/resource-crud-core",
8
11
  "@jskit-ai/users-core"
9
12
  ],
10
13
  capabilities: {
@@ -13,7 +16,8 @@ export default Object.freeze({
13
16
  "workspaces.server-routes"
14
17
  ],
15
18
  requires: [
16
- "users.core"
19
+ "users.core",
20
+ "json-rest-api.core"
17
21
  ]
18
22
  },
19
23
  runtime: {
@@ -112,7 +116,10 @@ export default Object.freeze({
112
116
  mutations: {
113
117
  dependencies: {
114
118
  runtime: {
115
- "@jskit-ai/users-core": "0.1.64"
119
+ "@jskit-ai/json-rest-api-core": "0.1.1",
120
+ "@jskit-ai/resource-core": "0.1.1",
121
+ "@jskit-ai/resource-crud-core": "0.1.1",
122
+ "@jskit-ai/users-core": "0.1.66"
116
123
  },
117
124
  dev: {}
118
125
  },
@@ -147,18 +154,10 @@ export default Object.freeze({
147
154
  from: "templates/migrations/workspaces_core_workspaces_drop_color.cjs",
148
155
  toDir: "migrations",
149
156
  extension: ".cjs",
150
- reason: "Drop legacy workspaces.color now that workspace theme colors live in workspace_settings.",
157
+ reason: "Drop workspaces.color now that workspace theme colors live in workspace_settings.",
151
158
  category: "migration",
152
159
  id: "users-core-workspaces-drop-color"
153
160
  },
154
- {
155
- from: "templates/packages/main/src/shared/resources/workspaceSettingsFields.js",
156
- to: "packages/main/src/shared/resources/workspaceSettingsFields.js",
157
- preserveOnRemove: true,
158
- reason: "Install app-owned workspace settings field definitions.",
159
- category: "workspaces-core",
160
- id: "users-core-app-owned-workspace-settings-fields"
161
- },
162
161
  {
163
162
  from: "templates/config/roles.js",
164
163
  to: "config/roles.js",
@@ -169,16 +168,6 @@ export default Object.freeze({
169
168
  }
170
169
  ],
171
170
  text: [
172
- {
173
- op: "append-text",
174
- file: "packages/main/src/shared/index.js",
175
- position: "top",
176
- skipIfContains: "import \"./resources/workspaceSettingsFields.js\";",
177
- value: "import \"./resources/workspaceSettingsFields.js\";\n",
178
- reason: "Load app-owned workspace settings field definitions inside the main shared module.",
179
- category: "workspaces-core",
180
- id: "users-core-main-shared-workspace-settings-field-import"
181
- },
182
171
  {
183
172
  op: "append-text",
184
173
  file: "config/public.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/workspaces-core",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -9,20 +9,22 @@
9
9
  "./server/WorkspacesCoreServiceProvider": "./src/server/WorkspacesCoreServiceProvider.js",
10
10
  "./server/validators/routeParamsValidator": "./src/server/common/validators/routeParamsValidator.js",
11
11
  "./server/support/workspaceRouteInput": "./src/server/support/workspaceRouteInput.js",
12
+ "./shared/jsonApiTransports": "./src/shared/jsonApiTransports.js",
12
13
  "./shared/settings": "./src/shared/settings.js",
13
14
  "./shared/tenancyProfile": "./src/shared/tenancyProfile.js",
14
15
  "./shared/support/workspacePathModel": "./src/shared/support/workspacePathModel.js",
15
16
  "./shared/resources/workspaceResource": "./src/shared/resources/workspaceResource.js",
16
- "./shared/resources/workspaceSettingsFields": "./src/shared/resources/workspaceSettingsFields.js",
17
17
  "./shared/resources/workspaceSettingsResource": "./src/shared/resources/workspaceSettingsResource.js"
18
18
  },
19
19
  "dependencies": {
20
- "@fastify/type-provider-typebox": "^6.1.0",
21
- "@jskit-ai/auth-core": "0.1.53",
22
- "@jskit-ai/database-runtime": "0.1.54",
23
- "@jskit-ai/http-runtime": "0.1.53",
24
- "@jskit-ai/kernel": "0.1.54",
25
- "@jskit-ai/users-core": "0.1.64",
26
- "typebox": "^1.0.81"
20
+ "@jskit-ai/auth-core": "0.1.55",
21
+ "@jskit-ai/database-runtime": "0.1.56",
22
+ "@jskit-ai/http-runtime": "0.1.55",
23
+ "@jskit-ai/json-rest-api-core": "0.1.1",
24
+ "@jskit-ai/kernel": "0.1.56",
25
+ "@jskit-ai/resource-crud-core": "0.1.1",
26
+ "@jskit-ai/resource-core": "0.1.1",
27
+ "@jskit-ai/users-core": "0.1.66",
28
+ "json-rest-schema": "1.x.x"
27
29
  }
28
30
  }
@@ -1,3 +1,9 @@
1
+ import {
2
+ INTERNAL_JSON_REST_API,
3
+ addResourceIfMissing,
4
+ createJsonRestResourceScopeOptions
5
+ } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
6
+ import { toDatabaseDateTimeUtc } from "@jskit-ai/database-runtime/shared";
1
7
  import { bootWorkspaceDirectoryRoutes } from "./workspaceDirectory/bootWorkspaceDirectoryRoutes.js";
2
8
  import { registerWorkspaceDirectory } from "./workspaceDirectory/registerWorkspaceDirectory.js";
3
9
  import {
@@ -11,13 +17,17 @@ import { bootWorkspaceSettings } from "./workspaceSettings/bootWorkspaceSettings
11
17
  import { registerWorkspaceRepositories } from "./registerWorkspaceRepositories.js";
12
18
  import { registerWorkspaceCore } from "./registerWorkspaceCore.js";
13
19
  import { registerWorkspaceBootstrap } from "./registerWorkspaceBootstrap.js";
20
+ import { workspaceResource } from "../shared/resources/workspaceResource.js";
21
+ import { workspaceMembershipsResource } from "../shared/resources/workspaceMembershipsResource.js";
22
+ import { workspaceInvitesResource } from "../shared/resources/workspaceInvitesResource.js";
23
+ import { workspaceSettingsResource } from "../shared/resources/workspaceSettingsResource.js";
14
24
 
15
25
  class WorkspacesCoreServiceProvider {
16
26
  static id = "workspaces.core";
17
27
 
18
- static dependsOn = ["users.core"];
28
+ static dependsOn = ["users.core", "json-rest-api.core"];
19
29
 
20
- register(app) {
30
+ async register(app) {
21
31
  registerWorkspaceRepositories(app);
22
32
  registerWorkspaceCore(app);
23
33
  registerWorkspaceBootstrap(app);
@@ -28,6 +38,16 @@ class WorkspacesCoreServiceProvider {
28
38
  }
29
39
 
30
40
  async boot(app) {
41
+ const api = app.make(INTERNAL_JSON_REST_API);
42
+ const scopeOptions = {
43
+ writeSerializers: {
44
+ "datetime-utc": toDatabaseDateTimeUtc
45
+ }
46
+ };
47
+ await addResourceIfMissing(api, "workspaces", createJsonRestResourceScopeOptions(workspaceResource, scopeOptions));
48
+ await addResourceIfMissing(api, "workspaceMemberships", createJsonRestResourceScopeOptions(workspaceMembershipsResource, scopeOptions));
49
+ await addResourceIfMissing(api, "workspaceInvites", createJsonRestResourceScopeOptions(workspaceInvitesResource, scopeOptions));
50
+ await addResourceIfMissing(api, "workspaceSettings", createJsonRestResourceScopeOptions(workspaceSettingsResource, scopeOptions));
31
51
  if (app.make("workspaces.enabled") !== true) {
32
52
  return;
33
53
  }
@@ -1,26 +1,70 @@
1
- import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
2
1
  import {
2
+ createWithTransaction,
3
3
  normalizeLowerText,
4
4
  normalizeRecordId,
5
+ normalizeDbRecordId,
5
6
  normalizeText,
6
7
  nowDb,
7
- isDuplicateEntryError
8
+ isDuplicateEntryError,
9
+ toIsoString
8
10
  } from "./repositoryUtils.js";
9
- import { workspaceInvitesResource } from "../resources/workspaceInvitesResource.js";
11
+ import {
12
+ createJsonApiInputRecord,
13
+ createJsonApiRelationship,
14
+ createJsonRestContext,
15
+ simplifyJsonApiDocument
16
+ } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
10
17
 
11
- const REPOSITORY_CONFIG = Object.freeze({
12
- context: "internal.repository.workspace-invites"
13
- });
18
+ const RESOURCE_TYPE = "workspaceInvites";
14
19
 
15
20
  function normalizeInviteRecord(payload) {
16
21
  if (!payload) {
17
22
  return null;
18
23
  }
19
- return workspaceInvitesResource.operations.view.outputValidator.normalize(payload);
24
+
25
+ return {
26
+ id: normalizeDbRecordId(payload.id, { fallback: null }),
27
+ workspaceId: normalizeDbRecordId(payload?.workspace?.id || payload?.workspaceId, { fallback: null }),
28
+ email: normalizeLowerText(payload.email),
29
+ roleSid: normalizeLowerText(payload.roleSid || "member") || "member",
30
+ status: normalizeLowerText(payload.status || "pending") || "pending",
31
+ tokenHash: normalizeText(payload.tokenHash),
32
+ invitedByUserId: normalizeDbRecordId(payload?.invitedByUser?.id || payload?.invitedByUserId, { fallback: null }),
33
+ expiresAt: payload.expiresAt ? toIsoString(payload.expiresAt) : null,
34
+ acceptedAt: payload.acceptedAt ? toIsoString(payload.acceptedAt) : null,
35
+ revokedAt: payload.revokedAt ? toIsoString(payload.revokedAt) : null,
36
+ createdAt: payload.createdAt ? toIsoString(payload.createdAt) : null,
37
+ updatedAt: payload.updatedAt ? toIsoString(payload.updatedAt) : null
38
+ };
20
39
  }
21
40
 
22
41
  function normalizeInvitePatchPayload(payload = {}) {
23
- return workspaceInvitesResource.operations.patch.bodyValidator.normalize(payload);
42
+ const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
43
+ const normalized = {};
44
+
45
+ if (Object.hasOwn(source, "roleSid")) {
46
+ normalized.roleSid = normalizeLowerText(source.roleSid);
47
+ }
48
+ if (Object.hasOwn(source, "status")) {
49
+ normalized.status = normalizeLowerText(source.status);
50
+ }
51
+ if (Object.hasOwn(source, "invitedByUserId")) {
52
+ normalized.invitedByUserId =
53
+ source.invitedByUserId == null
54
+ ? null
55
+ : normalizeRecordId(source.invitedByUserId, { fallback: null });
56
+ }
57
+ if (Object.hasOwn(source, "expiresAt")) {
58
+ normalized.expiresAt = source.expiresAt == null ? null : new Date(source.expiresAt);
59
+ }
60
+ if (Object.hasOwn(source, "acceptedAt")) {
61
+ normalized.acceptedAt = source.acceptedAt == null ? null : new Date(source.acceptedAt);
62
+ }
63
+ if (Object.hasOwn(source, "revokedAt")) {
64
+ normalized.revokedAt = source.revokedAt == null ? null : new Date(source.revokedAt);
65
+ }
66
+
67
+ return normalized;
24
68
  }
25
69
 
26
70
  function normalizeInviteWithWorkspace(payload = {}) {
@@ -31,48 +75,82 @@ function normalizeInviteWithWorkspace(payload = {}) {
31
75
 
32
76
  return {
33
77
  ...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
78
+ workspaceSlug: payload?.workspace?.slug ? normalizeText(payload.workspace.slug) : undefined,
79
+ workspaceName: payload?.workspace?.name ? normalizeText(payload.workspace.name) : undefined,
80
+ workspaceAvatarUrl: payload?.workspace?.avatarUrl ? normalizeText(payload.workspace.avatarUrl) : undefined
37
81
  };
38
82
  }
39
83
 
40
- const WORKSPACE_INVITE_WITH_WORKSPACE_SELECT = Object.freeze([
41
- "wi.*",
42
- "w.slug as workspace_slug",
43
- "w.name as workspace_name",
44
- "w.avatar_url as workspace_avatar_url"
45
- ]);
84
+ function createInviteRelationships({ workspaceId = null, invitedByUserId = undefined } = {}) {
85
+ const relationships = {};
46
86
 
47
- function createRepository(knex) {
87
+ if (workspaceId) {
88
+ relationships.workspace = createJsonApiRelationship("workspaces", workspaceId);
89
+ }
90
+ if (invitedByUserId !== undefined) {
91
+ relationships.invitedByUser = createJsonApiRelationship("userProfiles", invitedByUserId);
92
+ }
93
+
94
+ return relationships;
95
+ }
96
+
97
+ function createRepository({ api, knex } = {}) {
98
+ if (!api?.resources?.workspaceInvites) {
99
+ throw new TypeError("workspaceInvitesRepository requires json-rest-api workspaceInvites resource.");
100
+ }
48
101
  if (typeof knex !== "function") {
49
102
  throw new TypeError("workspaceInvitesRepository requires knex.");
50
103
  }
51
- const resourceRuntime = createCrudResourceRuntime(workspaceInvitesResource, knex, REPOSITORY_CONFIG);
52
- const withTransaction = resourceRuntime.withTransaction;
104
+
105
+ const withTransaction = createWithTransaction(knex);
106
+
107
+ async function queryInvites(filters = {}, options = {}, { includeWorkspace = false } = {}) {
108
+ const result = await api.resources.workspaceInvites.query(
109
+ {
110
+ queryParams: {
111
+ filters,
112
+ ...(includeWorkspace ? { include: ["workspace"] } : {})
113
+ },
114
+ transaction: options?.trx || null,
115
+ simplified: false
116
+ },
117
+ createJsonRestContext(options?.context || null)
118
+ );
119
+
120
+ return Array.isArray(simplifyJsonApiDocument(result)) ? simplifyJsonApiDocument(result) : [];
121
+ }
53
122
 
54
123
  async function findPendingByTokenHash(tokenHash, options = {}) {
55
- const client = options?.trx || knex;
56
- const row = await client("workspace_invites")
57
- .where({ token_hash: normalizeText(tokenHash), status: "pending" })
58
- .first();
59
- return normalizeInviteRecord(row);
124
+ const rows = await queryInvites(
125
+ {
126
+ tokenHash: normalizeText(tokenHash),
127
+ status: "pending"
128
+ },
129
+ options
130
+ );
131
+
132
+ return normalizeInviteRecord(rows[0] || null);
60
133
  }
61
134
 
62
135
  async function listPendingByEmail(email, options = {}) {
63
- const client = options?.trx || knex;
64
136
  const normalizedEmail = normalizeLowerText(email);
65
137
  if (!normalizedEmail) {
66
138
  return [];
67
139
  }
68
140
 
69
- const rows = await client("workspace_invites as wi")
70
- .join("workspaces as w", "w.id", "wi.workspace_id")
71
- .where({ "wi.email": normalizedEmail, "wi.status": "pending" })
72
- .orderBy("wi.created_at", "desc")
73
- .select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
141
+ const rows = await queryInvites(
142
+ {
143
+ email: normalizedEmail,
144
+ status: "pending"
145
+ },
146
+ options,
147
+ { includeWorkspace: true }
148
+ );
74
149
 
75
- return rows.map(normalizeInviteWithWorkspace).filter(Boolean);
150
+ return rows
151
+ .map(normalizeInviteWithWorkspace)
152
+ .filter(Boolean)
153
+ .sort((left, right) => String(right.createdAt || "").localeCompare(String(left.createdAt || "")));
76
154
  }
77
155
 
78
156
  async function listPendingByWorkspaceIdWithWorkspace(workspaceId, options = {}) {
@@ -81,18 +159,22 @@ function createRepository(knex) {
81
159
  return [];
82
160
  }
83
161
 
84
- const client = options?.trx || knex;
85
- const rows = await client("workspace_invites as wi")
86
- .join("workspaces as w", "w.id", "wi.workspace_id")
87
- .where({ "wi.workspace_id": normalizedWorkspaceId, "wi.status": "pending" })
88
- .orderBy("wi.created_at", "desc")
89
- .select(WORKSPACE_INVITE_WITH_WORKSPACE_SELECT);
162
+ const rows = await queryInvites(
163
+ {
164
+ workspace: normalizedWorkspaceId,
165
+ status: "pending"
166
+ },
167
+ options,
168
+ { includeWorkspace: true }
169
+ );
90
170
 
91
- return rows.map(normalizeInviteWithWorkspace).filter(Boolean);
171
+ return rows
172
+ .map(normalizeInviteWithWorkspace)
173
+ .filter(Boolean)
174
+ .sort((left, right) => String(right.createdAt || "").localeCompare(String(left.createdAt || "")));
92
175
  }
93
176
 
94
177
  async function insert(payload = {}, options = {}) {
95
- const client = options?.trx || knex;
96
178
  const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
97
179
  const workspaceId = normalizeRecordId(source.workspaceId, { fallback: null });
98
180
  if (!workspaceId) {
@@ -109,22 +191,52 @@ function createRepository(knex) {
109
191
  };
110
192
 
111
193
  try {
112
- return await resourceRuntime.create(createPayload, {
113
- ...options,
114
- trx: client,
115
- include: "none"
116
- });
194
+ const created = await api.resources.workspaceInvites.post(
195
+ {
196
+ inputRecord: createJsonApiInputRecord(
197
+ RESOURCE_TYPE,
198
+ {
199
+ email: createPayload.email,
200
+ roleSid: createPayload.roleSid,
201
+ status: createPayload.status,
202
+ tokenHash: createPayload.tokenHash,
203
+ expiresAt: createPayload.expiresAt ?? null,
204
+ acceptedAt: null,
205
+ revokedAt: null,
206
+ createdAt: new Date(),
207
+ updatedAt: new Date()
208
+ },
209
+ {
210
+ relationships: createInviteRelationships({
211
+ workspaceId: createPayload.workspaceId,
212
+ invitedByUserId: createPayload.invitedByUserId ?? null
213
+ })
214
+ }
215
+ ),
216
+ transaction: options?.trx || null,
217
+ simplified: false
218
+ },
219
+ createJsonRestContext(options?.context || null)
220
+ );
221
+
222
+ return normalizeInviteRecord(simplifyJsonApiDocument(created));
117
223
  } catch (error) {
118
224
  if (!isDuplicateEntryError(error)) {
119
225
  throw error;
120
226
  }
121
227
  }
122
228
 
123
- const row = await client("workspace_invites")
124
- .where({ workspace_id: createPayload.workspaceId, email: createPayload.email, status: "pending" })
125
- .orderBy("id", "desc")
126
- .first();
127
- return normalizeInviteRecord(row);
229
+ const rows = await queryInvites(
230
+ {
231
+ workspace: createPayload.workspaceId,
232
+ email: createPayload.email,
233
+ status: "pending"
234
+ },
235
+ options
236
+ );
237
+
238
+ rows.sort((left, right) => String(right.id || "").localeCompare(String(left.id || "")));
239
+ return normalizeInviteRecord(rows[0] || null);
128
240
  }
129
241
 
130
242
  async function expirePendingByWorkspaceIdAndEmail(workspaceId, email, options = {}) {
@@ -133,14 +245,38 @@ function createRepository(knex) {
133
245
  return;
134
246
  }
135
247
 
136
- const client = options?.trx || knex;
137
248
  const patch = normalizeInvitePatchPayload({ status: "expired" });
138
- await client("workspace_invites")
139
- .where({ workspace_id: normalizedWorkspaceId, email: normalizeLowerText(email), status: "pending" })
140
- .update({
141
- status: patch.status,
142
- updated_at: nowDb()
143
- });
249
+ const rows = await queryInvites(
250
+ {
251
+ workspace: normalizedWorkspaceId,
252
+ email: normalizeLowerText(email),
253
+ status: "pending"
254
+ },
255
+ options
256
+ );
257
+
258
+ for (const row of rows) {
259
+ if (!row?.id) {
260
+ continue;
261
+ }
262
+ await api.resources.workspaceInvites.patch(
263
+ {
264
+ inputRecord: createJsonApiInputRecord(
265
+ RESOURCE_TYPE,
266
+ {
267
+ status: patch.status,
268
+ updatedAt: nowDb()
269
+ },
270
+ {
271
+ id: row.id
272
+ }
273
+ ),
274
+ transaction: options?.trx || null,
275
+ simplified: false
276
+ },
277
+ createJsonRestContext(options?.context || null)
278
+ );
279
+ }
144
280
  }
145
281
 
146
282
  async function markAcceptedById(inviteId, options = {}) {
@@ -149,16 +285,23 @@ function createRepository(knex) {
149
285
  return;
150
286
  }
151
287
 
152
- await resourceRuntime.updateById(
153
- normalizedInviteId,
288
+ await api.resources.workspaceInvites.patch(
154
289
  {
155
- status: "accepted",
156
- acceptedAt: new Date()
290
+ inputRecord: createJsonApiInputRecord(
291
+ RESOURCE_TYPE,
292
+ {
293
+ status: "accepted",
294
+ acceptedAt: new Date(),
295
+ updatedAt: new Date()
296
+ },
297
+ {
298
+ id: normalizedInviteId
299
+ }
300
+ ),
301
+ transaction: options?.trx || null,
302
+ simplified: false
157
303
  },
158
- {
159
- ...options,
160
- include: "none"
161
- }
304
+ createJsonRestContext(options?.context || null)
162
305
  );
163
306
  }
164
307
 
@@ -168,16 +311,23 @@ function createRepository(knex) {
168
311
  return;
169
312
  }
170
313
 
171
- await resourceRuntime.updateById(
172
- normalizedInviteId,
314
+ await api.resources.workspaceInvites.patch(
173
315
  {
174
- status: "revoked",
175
- revokedAt: new Date()
316
+ inputRecord: createJsonApiInputRecord(
317
+ RESOURCE_TYPE,
318
+ {
319
+ status: "revoked",
320
+ revokedAt: new Date(),
321
+ updatedAt: new Date()
322
+ },
323
+ {
324
+ id: normalizedInviteId
325
+ }
326
+ ),
327
+ transaction: options?.trx || null,
328
+ simplified: false
176
329
  },
177
- {
178
- ...options,
179
- include: "none"
180
- }
330
+ createJsonRestContext(options?.context || null)
181
331
  );
182
332
  }
183
333
 
@@ -188,11 +338,16 @@ function createRepository(knex) {
188
338
  return null;
189
339
  }
190
340
 
191
- const client = options?.trx || knex;
192
- const row = await client("workspace_invites")
193
- .where({ id: normalizedInviteId, workspace_id: normalizedWorkspaceId, status: "pending" })
194
- .first();
195
- return normalizeInviteRecord(row);
341
+ const rows = await queryInvites(
342
+ {
343
+ id: normalizedInviteId,
344
+ workspace: normalizedWorkspaceId,
345
+ status: "pending"
346
+ },
347
+ options
348
+ );
349
+
350
+ return normalizeInviteRecord(rows[0] || null);
196
351
  }
197
352
 
198
353
  return Object.freeze({