@jskit-ai/users-core 0.1.42 → 0.1.43

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 (48) hide show
  1. package/package.descriptor.mjs +6 -6
  2. package/package.json +6 -6
  3. package/src/server/accountProfile/avatarStorageService.js +3 -3
  4. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +12 -13
  5. package/src/server/common/formatters/workspaceFormatter.js +3 -2
  6. package/src/server/common/repositories/repositoryUtils.js +12 -3
  7. package/src/server/common/repositories/userSettingsRepository.js +35 -11
  8. package/src/server/common/repositories/usersRepository.js +44 -27
  9. package/src/server/common/repositories/workspaceInvitesRepository.js +49 -13
  10. package/src/server/common/repositories/workspaceMembershipsRepository.js +55 -22
  11. package/src/server/common/repositories/workspacesRepository.js +41 -11
  12. package/src/server/common/services/accountContextService.js +3 -2
  13. package/src/server/common/services/authProfileSyncService.js +7 -5
  14. package/src/server/common/services/workspaceContextService.js +4 -1
  15. package/src/server/common/support/realtimeServiceEvents.js +4 -3
  16. package/src/server/common/validators/authenticatedUserValidator.js +5 -4
  17. package/src/server/consoleSettings/consoleService.js +3 -3
  18. package/src/server/consoleSettings/consoleSettingsRepository.js +10 -6
  19. package/src/server/usersBootstrapContributor.js +7 -3
  20. package/src/server/workspaceBootstrapContributor.js +5 -1
  21. package/src/server/workspaceMembers/registerWorkspaceMembers.js +6 -4
  22. package/src/server/workspaceMembers/workspaceMembersService.js +23 -11
  23. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +5 -4
  24. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +3 -2
  25. package/src/server/workspaceSettings/workspaceSettingsRepository.js +29 -10
  26. package/src/server/workspaceSettings/workspaceSettingsService.js +3 -2
  27. package/src/shared/resources/workspaceMembersResource.js +25 -21
  28. package/src/shared/resources/workspacePendingInvitationsResource.js +7 -12
  29. package/src/shared/resources/workspaceResource.js +13 -9
  30. package/src/shared/resources/workspaceSettingsResource.js +7 -5
  31. package/templates/migrations/users_core_console_owner.cjs +1 -1
  32. package/templates/migrations/users_core_generic_initial.cjs +4 -4
  33. package/templates/migrations/users_core_profile_username.cjs +1 -1
  34. package/test/authProfileSyncService.test.js +7 -4
  35. package/test/avatarStorageService.test.js +3 -3
  36. package/test/consoleService.test.js +9 -9
  37. package/test/registerServiceRealtimeEvents.test.js +9 -9
  38. package/test/repositoryContracts.test.js +40 -0
  39. package/test/usersBootstrapContributor.test.js +4 -4
  40. package/test/workspaceBootstrapContributor.test.js +1 -1
  41. package/test/workspaceInvitesRepository.test.js +3 -3
  42. package/test/workspaceMembersService.test.js +34 -34
  43. package/test/workspacePendingInvitationsResource.test.js +4 -4
  44. package/test/workspacePendingInvitationsService.test.js +11 -11
  45. package/test/workspaceRouteVisibilityResolver.test.js +6 -6
  46. package/test/workspaceService.test.js +33 -33
  47. package/test/workspaceSettingsRepository.test.js +7 -6
  48. package/test/workspaceSettingsResource.test.js +2 -2
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  normalizeLowerText,
3
+ normalizeRecordId,
4
+ normalizeDbRecordId,
3
5
  normalizeText,
4
6
  toIsoString,
5
7
  nowDb,
6
- isDuplicateEntryError
8
+ isDuplicateEntryError,
9
+ createWithTransaction
7
10
  } from "./repositoryUtils.js";
8
11
  import { OWNER_ROLE_ID } from "../../../shared/roles.js";
9
12
 
@@ -13,9 +16,9 @@ function mapRow(row) {
13
16
  }
14
17
 
15
18
  return {
16
- id: Number(row.id),
17
- workspaceId: Number(row.workspace_id),
18
- userId: Number(row.user_id),
19
+ id: normalizeDbRecordId(row.id, { fallback: "" }),
20
+ workspaceId: normalizeDbRecordId(row.workspace_id, { fallback: "" }),
21
+ userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
19
22
  roleSid: normalizeLowerText(row.role_sid || "member") || "member",
20
23
  status: normalizeLowerText(row.status || "active") || "active",
21
24
  createdAt: toIsoString(row.created_at),
@@ -29,7 +32,7 @@ function mapMemberSummaryRow(row) {
29
32
  }
30
33
 
31
34
  return {
32
- userId: Number(row.user_id),
35
+ userId: normalizeDbRecordId(row.user_id, { fallback: "" }),
33
36
  roleSid: normalizeLowerText(row.role_sid || "member") || "member",
34
37
  status: normalizeLowerText(row.status || "active") || "active",
35
38
  displayName: normalizeText(row.display_name),
@@ -41,35 +44,48 @@ function createRepository(knex) {
41
44
  if (typeof knex !== "function") {
42
45
  throw new TypeError("workspaceMembershipsRepository requires knex.");
43
46
  }
47
+ const withTransaction = createWithTransaction(knex);
44
48
 
45
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
+
46
56
  const client = options?.trx || knex;
47
57
  const row = await client("workspace_memberships")
48
- .where({ workspace_id: Number(workspaceId), user_id: Number(userId) })
58
+ .where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
49
59
  .first();
50
60
  return mapRow(row);
51
61
  }
52
62
 
53
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
+
54
70
  const client = options?.trx || knex;
55
- const existing = await findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
71
+ const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
56
72
  if (existing) {
57
73
  if (existing.roleSid !== OWNER_ROLE_ID || existing.status !== "active") {
58
74
  await client("workspace_memberships")
59
- .where({ workspace_id: Number(workspaceId), user_id: Number(userId) })
75
+ .where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
60
76
  .update({
61
77
  role_sid: OWNER_ROLE_ID,
62
78
  status: "active",
63
79
  updated_at: nowDb()
64
80
  });
65
81
  }
66
- return findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
82
+ return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
67
83
  }
68
84
 
69
85
  try {
70
86
  await client("workspace_memberships").insert({
71
- workspace_id: Number(workspaceId),
72
- user_id: Number(userId),
87
+ workspace_id: normalizedWorkspaceId,
88
+ user_id: normalizedUserId,
73
89
  role_sid: OWNER_ROLE_ID,
74
90
  status: "active",
75
91
  created_at: nowDb(),
@@ -81,43 +97,54 @@ function createRepository(knex) {
81
97
  }
82
98
  }
83
99
 
84
- return findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
100
+ return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
85
101
  }
86
102
 
87
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
+
88
110
  const client = options?.trx || knex;
89
- const existing = await findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
111
+ const existing = await findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
90
112
  const roleSid = normalizeLowerText(patch.roleSid || existing?.roleSid || "member") || "member";
91
113
  const status = normalizeLowerText(patch.status || existing?.status || "active") || "active";
92
114
 
93
115
  if (!existing) {
94
116
  await client("workspace_memberships").insert({
95
- workspace_id: Number(workspaceId),
96
- user_id: Number(userId),
117
+ workspace_id: normalizedWorkspaceId,
118
+ user_id: normalizedUserId,
97
119
  role_sid: roleSid,
98
120
  status,
99
121
  created_at: nowDb(),
100
122
  updated_at: nowDb()
101
123
  });
102
- return findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
124
+ return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
103
125
  }
104
126
 
105
127
  await client("workspace_memberships")
106
- .where({ workspace_id: Number(workspaceId), user_id: Number(userId) })
128
+ .where({ workspace_id: normalizedWorkspaceId, user_id: normalizedUserId })
107
129
  .update({
108
130
  role_sid: roleSid,
109
131
  status,
110
132
  updated_at: nowDb()
111
133
  });
112
134
 
113
- return findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
135
+ return findByWorkspaceIdAndUserId(normalizedWorkspaceId, normalizedUserId, { trx: client });
114
136
  }
115
137
 
116
138
  async function listActiveByWorkspaceId(workspaceId, options = {}) {
139
+ const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
140
+ if (!normalizedWorkspaceId) {
141
+ return [];
142
+ }
143
+
117
144
  const client = options?.trx || knex;
118
145
  const rows = await client("workspace_memberships as wm")
119
146
  .join("users as up", "up.id", "wm.user_id")
120
- .where({ "wm.workspace_id": Number(workspaceId), "wm.status": "active" })
147
+ .where({ "wm.workspace_id": normalizedWorkspaceId, "wm.status": "active" })
121
148
  .orderBy("up.display_name", "asc")
122
149
  .select([
123
150
  "wm.user_id",
@@ -131,21 +158,27 @@ function createRepository(knex) {
131
158
  }
132
159
 
133
160
  async function listActiveWorkspaceIdsByUserId(userId, options = {}) {
161
+ const normalizedUserId = normalizeRecordId(userId, { fallback: null });
162
+ if (!normalizedUserId) {
163
+ return [];
164
+ }
165
+
134
166
  const client = options?.trx || knex;
135
167
  const rows = await client("workspace_memberships")
136
168
  .where({
137
- user_id: Number(userId),
169
+ user_id: normalizedUserId,
138
170
  status: "active"
139
171
  })
140
172
  .select("workspace_id")
141
173
  .orderBy("workspace_id", "asc");
142
174
 
143
175
  return rows
144
- .map((row) => Number(row.workspace_id))
145
- .filter((workspaceId) => Number.isInteger(workspaceId) && workspaceId > 0);
176
+ .map((row) => normalizeDbRecordId(row.workspace_id, { fallback: null }))
177
+ .filter(Boolean);
146
178
  }
147
179
 
148
180
  return Object.freeze({
181
+ withTransaction,
149
182
  findByWorkspaceIdAndUserId,
150
183
  ensureOwnerMembership,
151
184
  upsertMembership,
@@ -1,10 +1,14 @@
1
+ import { resolveInsertedRecordId } from "@jskit-ai/database-runtime/shared";
1
2
  import {
3
+ normalizeDbRecordId,
4
+ normalizeRecordId,
2
5
  normalizeText,
3
6
  normalizeLowerText,
4
7
  toIsoString,
5
8
  toNullableIso,
6
9
  nowDb,
7
- isDuplicateEntryError
10
+ isDuplicateEntryError,
11
+ createWithTransaction
8
12
  } from "./repositoryUtils.js";
9
13
 
10
14
  function mapRow(row) {
@@ -13,10 +17,10 @@ function mapRow(row) {
13
17
  }
14
18
 
15
19
  return {
16
- id: Number(row.id),
20
+ id: normalizeDbRecordId(row.id, { fallback: "" }),
17
21
  slug: normalizeText(row.slug),
18
22
  name: normalizeText(row.name),
19
- ownerUserId: Number(row.owner_user_id),
23
+ ownerUserId: normalizeDbRecordId(row.owner_user_id, { fallback: "" }),
20
24
  isPersonal: Boolean(row.is_personal),
21
25
  avatarUrl: row.avatar_url ? normalizeText(row.avatar_url) : "",
22
26
  createdAt: toIsoString(row.created_at),
@@ -41,6 +45,7 @@ function createRepository(knex) {
41
45
  if (typeof knex !== "function") {
42
46
  throw new TypeError("workspacesRepository requires knex.");
43
47
  }
48
+ const withTransaction = createWithTransaction(knex);
44
49
 
45
50
  function workspaceSelectColumns({ includeMembership = false } = {}) {
46
51
  const columns = [
@@ -61,9 +66,14 @@ function createRepository(knex) {
61
66
  }
62
67
 
63
68
  async function findById(workspaceId, options = {}) {
69
+ const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
70
+ if (!normalizedWorkspaceId) {
71
+ return null;
72
+ }
73
+
64
74
  const client = options?.trx || knex;
65
75
  const row = await client("workspaces as w")
66
- .where({ "w.id": Number(workspaceId) })
76
+ .where({ "w.id": normalizedWorkspaceId })
67
77
  .select(workspaceSelectColumns())
68
78
  .first();
69
79
  return mapRow(row);
@@ -84,9 +94,14 @@ function createRepository(knex) {
84
94
  }
85
95
 
86
96
  async function findPersonalByOwnerUserId(userId, options = {}) {
97
+ const normalizedUserId = normalizeRecordId(userId, { fallback: null });
98
+ if (!normalizedUserId) {
99
+ return null;
100
+ }
101
+
87
102
  const client = options?.trx || knex;
88
103
  const row = await client("workspaces as w")
89
- .where({ "w.owner_user_id": Number(userId), "w.is_personal": 1 })
104
+ .where({ "w.owner_user_id": normalizedUserId, "w.is_personal": 1 })
90
105
  .orderBy("w.id", "asc")
91
106
  .select(workspaceSelectColumns())
92
107
  .first();
@@ -96,11 +111,15 @@ function createRepository(knex) {
96
111
  async function insert(payload = {}, options = {}) {
97
112
  const client = options?.trx || knex;
98
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
+ }
99
118
 
100
119
  const insertPayload = {
101
120
  slug: normalizeLowerText(source.slug),
102
121
  name: normalizeText(source.name),
103
- owner_user_id: Number(source.ownerUserId),
122
+ owner_user_id: ownerUserId,
104
123
  is_personal: source.isPersonal ? 1 : 0,
105
124
  avatar_url: normalizeText(source.avatarUrl),
106
125
  created_at: nowDb(),
@@ -110,8 +129,8 @@ function createRepository(knex) {
110
129
 
111
130
  try {
112
131
  const result = await client("workspaces").insert(insertPayload);
113
- const insertedId = Array.isArray(result) ? Number(result[0]) : Number(result);
114
- if (Number.isInteger(insertedId) && insertedId > 0) {
132
+ const insertedId = resolveInsertedRecordId(result, { fallback: null });
133
+ if (insertedId) {
115
134
  return findById(insertedId, { trx: client });
116
135
  }
117
136
  const bySlug = await findBySlug(insertPayload.slug, { trx: client });
@@ -129,6 +148,11 @@ function createRepository(knex) {
129
148
  }
130
149
 
131
150
  async function updateById(workspaceId, patch = {}, options = {}) {
151
+ const normalizedWorkspaceId = normalizeRecordId(workspaceId, { fallback: null });
152
+ if (!normalizedWorkspaceId) {
153
+ return null;
154
+ }
155
+
132
156
  const client = options?.trx || knex;
133
157
  const source = patch && typeof patch === "object" ? patch : {};
134
158
  const dbPatch = {
@@ -142,15 +166,20 @@ function createRepository(knex) {
142
166
  dbPatch.avatar_url = normalizeText(source.avatarUrl);
143
167
  }
144
168
 
145
- await client("workspaces").where({ id: Number(workspaceId) }).update(dbPatch);
146
- return findById(workspaceId, { trx: client });
169
+ await client("workspaces").where({ id: normalizedWorkspaceId }).update(dbPatch);
170
+ return findById(normalizedWorkspaceId, { trx: client });
147
171
  }
148
172
 
149
173
  async function listForUserId(userId, options = {}) {
174
+ const normalizedUserId = normalizeRecordId(userId, { fallback: null });
175
+ if (!normalizedUserId) {
176
+ return [];
177
+ }
178
+
150
179
  const client = options?.trx || knex;
151
180
  const rows = await client("workspace_memberships as wm")
152
181
  .join("workspaces as w", "w.id", "wm.workspace_id")
153
- .where({ "wm.user_id": Number(userId) })
182
+ .where({ "wm.user_id": normalizedUserId })
154
183
  .whereNull("w.deleted_at")
155
184
  .orderBy("w.is_personal", "desc")
156
185
  .orderBy("w.id", "asc")
@@ -160,6 +189,7 @@ function createRepository(knex) {
160
189
  }
161
190
 
162
191
  return Object.freeze({
192
+ withTransaction,
163
193
  findById,
164
194
  findBySlug,
165
195
  findPersonalByOwnerUserId,
@@ -1,3 +1,4 @@
1
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
1
2
  import { normalizeIdentity } from "../repositories/usersRepository.js";
2
3
 
3
4
  async function resolveUserProfile(usersRepository, user) {
@@ -9,8 +10,8 @@ async function resolveUserProfile(usersRepository, user) {
9
10
  }
10
11
  }
11
12
 
12
- const userId = Number(user?.id);
13
- if (Number.isInteger(userId) && userId > 0) {
13
+ const userId = normalizeRecordId(user?.id, { fallback: null });
14
+ if (userId) {
14
15
  const profileById = await usersRepository.findById(userId);
15
16
  if (profileById) {
16
17
  return profileById;
@@ -1,3 +1,4 @@
1
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
1
2
  import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
2
3
  import { normalizeIdentity } from "../repositories/usersRepository.js";
3
4
 
@@ -46,7 +47,7 @@ function profileNeedsUpdate(existing, nextProfile) {
46
47
  }
47
48
 
48
49
  function requireSynchronizedProfile(profile) {
49
- if (profile && Number.isFinite(Number(profile.id)) && String(profile.displayName || "").trim()) {
50
+ if (profile && normalizeRecordId(profile.id, { fallback: null }) && String(profile.displayName || "").trim()) {
50
51
  return profile;
51
52
  }
52
53
 
@@ -60,6 +61,9 @@ function createService({ usersRepository, workspaceProvisioningService = null, u
60
61
  if (typeof usersRepository.upsert !== "function") {
61
62
  throw new Error("authProfileSyncService requires usersRepository.upsert().");
62
63
  }
64
+ if (typeof usersRepository.withTransaction !== "function") {
65
+ throw new Error("authProfileSyncService requires usersRepository.withTransaction().");
66
+ }
63
67
  if (!userSettingsRepository || typeof userSettingsRepository.ensureForUserId !== "function") {
64
68
  throw new Error("authProfileSyncService requires userSettingsRepository.ensureForUserId().");
65
69
  }
@@ -118,10 +122,8 @@ function createService({ usersRepository, workspaceProvisioningService = null, u
118
122
  if (options?.trx) {
119
123
  return runSync(options.trx);
120
124
  }
121
- if (typeof usersRepository.withTransaction === "function") {
122
- return usersRepository.withTransaction((trx) => runSync(trx));
123
- }
124
- return runSync();
125
+
126
+ return usersRepository.withTransaction((trx) => runSync(trx));
125
127
  }
126
128
 
127
129
  return Object.freeze({
@@ -11,6 +11,7 @@ import {
11
11
  mapWorkspaceSummary
12
12
  } from "../formatters/workspaceFormatter.js";
13
13
  import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
14
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
14
15
  import { authenticatedUserValidator } from "../validators/authenticatedUserValidator.js";
15
16
 
16
17
  function toSlugPart(value) {
@@ -237,7 +238,9 @@ function createService({
237
238
  normalizedUser.id,
238
239
  options
239
240
  );
240
- const actorOwnsWorkspace = Number(workspace.ownerUserId) === Number(normalizedUser.id);
241
+ const actorOwnsWorkspace =
242
+ normalizeRecordId(workspace.ownerUserId, { fallback: null }) ===
243
+ normalizeRecordId(normalizedUser.id, { fallback: null });
241
244
  const membershipIsActive = normalizeLowerText(membership?.status) === "active";
242
245
 
243
246
  if (!membershipIsActive && actorOwnsWorkspace) {
@@ -1,7 +1,8 @@
1
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
1
2
  import { deepFreeze } from "./deepFreeze.js";
2
3
 
3
4
  function resolveActorScopedEntityId({ options } = {}) {
4
- return Number(options?.context?.actor?.id || 0);
5
+ return normalizeRecordId(options?.context?.actor?.id, { fallback: "" });
5
6
  }
6
7
 
7
8
  function resolveWorkspaceSlugPayload({ args } = {}) {
@@ -66,7 +67,7 @@ function createWorkspaceEntityAndBootstrapEvents({
66
67
  source: "workspace",
67
68
  entity: normalizedWorkspaceEntity,
68
69
  operation: normalizedWorkspaceOperation,
69
- entityId: workspaceEntityId,
70
+ entityId: (payload = {}) => normalizeRecordId(workspaceEntityId(payload), { fallback: "" }),
70
71
  realtime: {
71
72
  event: normalizedWorkspaceRealtimeEvent,
72
73
  payload: resolveWorkspaceSlugPayload,
@@ -78,7 +79,7 @@ function createWorkspaceEntityAndBootstrapEvents({
78
79
  source: "users",
79
80
  entity: "bootstrap",
80
81
  operation: "updated",
81
- entityId: bootstrapEntityId,
82
+ entityId: (payload = {}) => normalizeRecordId(bootstrapEntityId(payload), { fallback: "" }),
82
83
  realtime: {
83
84
  event: "users.bootstrap.changed",
84
85
  audience: bootstrapAudience
@@ -1,11 +1,12 @@
1
1
  import { Type } from "@fastify/type-provider-typebox";
2
- import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
2
+ import { normalizeObjectInput, recordIdInputSchema } from "@jskit-ai/kernel/shared/validators";
3
3
  import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
4
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
4
5
 
5
6
  function normalizeAuthenticatedUser(input = {}) {
6
7
  const source = normalizeObjectInput(input);
7
- const id = Number(source.id);
8
- if (!Number.isInteger(id) || id < 1) {
8
+ const id = normalizeRecordId(source.id, { fallback: null });
9
+ if (!id) {
9
10
  return null;
10
11
  }
11
12
 
@@ -25,7 +26,7 @@ function normalizeAuthenticatedUser(input = {}) {
25
26
  const authenticatedUserValidator = Object.freeze({
26
27
  schema: Type.Object(
27
28
  {
28
- id: Type.Integer({ minimum: 1 }),
29
+ id: recordIdInputSchema,
29
30
  email: Type.String({ minLength: 1 }),
30
31
  username: Type.Optional(Type.String()),
31
32
  displayName: Type.Optional(Type.String()),
@@ -1,5 +1,5 @@
1
1
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
- import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
2
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
3
3
 
4
4
  function createService({ consoleSettingsRepository } = {}) {
5
5
  if (!consoleSettingsRepository || typeof consoleSettingsRepository.ensureOwnerUserId !== "function") {
@@ -7,7 +7,7 @@ function createService({ consoleSettingsRepository } = {}) {
7
7
  }
8
8
 
9
9
  async function ensureInitialConsoleMember(userId, options = {}) {
10
- const normalizedUserId = parsePositiveInteger(userId);
10
+ const normalizedUserId = normalizeRecordId(userId, { fallback: null });
11
11
  if (!normalizedUserId) {
12
12
  throw new AppError(400, "Invalid console user.");
13
13
  }
@@ -16,7 +16,7 @@ function createService({ consoleSettingsRepository } = {}) {
16
16
  }
17
17
 
18
18
  async function requireConsoleOwner(context = {}, options = {}) {
19
- const actorUserId = parsePositiveInteger(context?.actor?.id);
19
+ const actorUserId = normalizeRecordId(context?.actor?.id, { fallback: null });
20
20
  if (!actorUserId) {
21
21
  throw new AppError(401, "Authentication required.");
22
22
  }
@@ -1,8 +1,10 @@
1
1
  import {
2
+ normalizeDbRecordId,
3
+ normalizeRecordId,
2
4
  nowDb,
3
- toIsoString
5
+ toIsoString,
6
+ createWithTransaction
4
7
  } from "../common/repositories/repositoryUtils.js";
5
- import { parsePositiveInteger } from "@jskit-ai/kernel/server/runtime";
6
8
  import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
7
9
  import { consoleSettingsFields } from "../../shared/resources/consoleSettingsFields.js";
8
10
 
@@ -26,10 +28,10 @@ function mapSingletonRow(row) {
26
28
  throw new Error("console_settings singleton row is missing.");
27
29
  }
28
30
 
29
- const ownerUserId = parsePositiveInteger(row.owner_user_id);
31
+ const ownerUserId = normalizeDbRecordId(row.owner_user_id, { fallback: null });
30
32
  return {
31
- id: Number(row.id),
32
- ownerUserId: ownerUserId || null,
33
+ id: normalizeDbRecordId(row.id, { fallback: "1" }),
34
+ ownerUserId,
33
35
  settings: mapSettings(row),
34
36
  createdAt: toIsoString(row.created_at),
35
37
  updatedAt: toIsoString(row.updated_at)
@@ -40,6 +42,7 @@ function createRepository(knex) {
40
42
  if (typeof knex !== "function") {
41
43
  throw new TypeError("consoleSettingsRepository requires knex.");
42
44
  }
45
+ const withTransaction = createWithTransaction(knex);
43
46
 
44
47
  async function readSingleton(client) {
45
48
  return client("console_settings").where({ id: 1 }).first();
@@ -52,7 +55,7 @@ function createRepository(knex) {
52
55
 
53
56
  async function ensureOwnerUserId(userId, options = {}) {
54
57
  const client = options?.trx || knex;
55
- const candidateOwnerUserId = parsePositiveInteger(userId);
58
+ const candidateOwnerUserId = normalizeRecordId(userId, { fallback: null });
56
59
  if (!candidateOwnerUserId) {
57
60
  throw new TypeError("consoleSettingsRepository.ensureOwnerUserId requires a positive user id.");
58
61
  }
@@ -102,6 +105,7 @@ function createRepository(knex) {
102
105
  }
103
106
 
104
107
  return Object.freeze({
108
+ withTransaction,
105
109
  getSingleton,
106
110
  ensureOwnerUserId,
107
111
  updateSingleton
@@ -13,6 +13,7 @@ import {
13
13
  import { accountAvatarFormatter } from "./common/formatters/accountAvatarFormatter.js";
14
14
  import { authenticatedUserValidator } from "./common/validators/authenticatedUserValidator.js";
15
15
  import { userSettingsFields } from "../shared/resources/userSettingsFields.js";
16
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
16
17
 
17
18
  function getOAuthProviderCatalogPayload(authService) {
18
19
  if (!authService || typeof authService.getOAuthProviderCatalog !== "function") {
@@ -194,12 +195,15 @@ function createUsersBootstrapContributor({
194
195
  const latestProfile = (await usersRepository.findById(normalizedUser.id)) || normalizedUser;
195
196
  const userSettings = await userSettingsRepository.ensureForUserId(latestProfile.id);
196
197
 
197
- let seededConsoleOwnerUserId = 0;
198
+ let seededConsoleOwnerUserId = null;
198
199
  if (
199
200
  consoleService &&
200
201
  typeof consoleService.ensureInitialConsoleMember === "function"
201
202
  ) {
202
- seededConsoleOwnerUserId = Number(await consoleService.ensureInitialConsoleMember(latestProfile.id));
203
+ seededConsoleOwnerUserId = normalizeRecordId(
204
+ await consoleService.ensureInitialConsoleMember(latestProfile.id),
205
+ { fallback: null }
206
+ );
203
207
  }
204
208
 
205
209
  payload = {
@@ -221,7 +225,7 @@ function createUsersBootstrapContributor({
221
225
  requestedWorkspace: null,
222
226
  permissions: [],
223
227
  surfaceAccess: {
224
- consoleowner: seededConsoleOwnerUserId > 0 && seededConsoleOwnerUserId === Number(latestProfile.id)
228
+ consoleowner: Boolean(seededConsoleOwnerUserId) && seededConsoleOwnerUserId === String(latestProfile.id)
225
229
  },
226
230
  workspaceSettings: null,
227
231
  userSettings: mapUserSettingsBootstrap(userSettings),
@@ -1,4 +1,5 @@
1
1
  import { requireServiceMethod } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
2
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
3
  import { normalizeLowerText } from "@jskit-ai/kernel/shared/actions/textNormalization";
3
4
  import {
4
5
  TENANCY_MODE_NONE,
@@ -129,7 +130,10 @@ function createWorkspaceBootstrapContributor({
129
130
  return Object.freeze({
130
131
  contributorId,
131
132
  async contribute({ request = null, query = {}, payload = {} } = {}) {
132
- const normalizedUserId = Number(payload?.session?.authenticated === true ? payload?.session?.userId : 0);
133
+ const normalizedUserId = normalizeRecordId(
134
+ payload?.session?.authenticated === true ? payload?.session?.userId : null,
135
+ { fallback: null }
136
+ );
133
137
  const normalizedWorkspaceSlug = resolveBootstrapWorkspaceSlug({ query, request });
134
138
  if (!normalizedUserId) {
135
139
  if (!normalizedWorkspaceSlug || resolvedTenancyProfile.mode === TENANCY_MODE_NONE) {
@@ -1,4 +1,6 @@
1
1
  import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
+ import { normalizeDbRecordId } from "@jskit-ai/database-runtime/shared";
3
+ import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
4
  import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
3
5
  import { deepFreeze } from "../common/support/deepFreeze.js";
4
6
  import { createService as createWorkspaceMembersService } from "./workspaceMembersService.js";
@@ -23,8 +25,8 @@ const INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE = Object.freeze({
23
25
  return [];
24
26
  }
25
27
 
26
- const inviteId = Number(event?.entityId);
27
- if (!Number.isInteger(inviteId) || inviteId < 1) {
28
+ const inviteId = normalizeRecordId(event?.entityId, { fallback: null });
29
+ if (!inviteId) {
28
30
  return [];
29
31
  }
30
32
 
@@ -33,8 +35,8 @@ const INVITE_RECIPIENT_BOOTSTRAP_AUDIENCE = Object.freeze({
33
35
  .where("wi.id", inviteId)
34
36
  .first("up.id as user_id");
35
37
 
36
- const userId = Number(row?.user_id || 0);
37
- if (!Number.isInteger(userId) || userId < 1) {
38
+ const userId = normalizeDbRecordId(row?.user_id, { fallback: null });
39
+ if (!userId) {
38
40
  return [];
39
41
  }
40
42