@jskit-ai/users-core 0.1.56 → 0.1.58

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 (47) hide show
  1. package/package.descriptor.mjs +175 -6
  2. package/package.json +6 -6
  3. package/src/server/accountNotifications/accountNotificationsService.js +3 -3
  4. package/src/server/accountNotifications/registerAccountNotifications.js +2 -2
  5. package/src/server/accountPreferences/accountPreferencesService.js +3 -3
  6. package/src/server/accountPreferences/registerAccountPreferences.js +2 -2
  7. package/src/server/accountProfile/accountProfileService.js +5 -5
  8. package/src/server/accountProfile/avatarService.js +6 -6
  9. package/src/server/accountProfile/registerAccountProfile.js +3 -3
  10. package/src/server/accountSecurity/accountSecurityService.js +3 -3
  11. package/src/server/accountSecurity/registerAccountSecurity.js +2 -2
  12. package/src/server/common/registerCommonRepositories.js +5 -5
  13. package/src/server/common/repositories/{usersRepository.js → userProfilesRepository.js} +117 -95
  14. package/src/server/common/repositories/userSettingsRepository.js +12 -11
  15. package/src/server/common/resources/userProfilesResource.js +203 -0
  16. package/src/server/common/services/accountContextService.js +4 -4
  17. package/src/server/common/services/authProfileSyncService.js +11 -11
  18. package/src/server/common/support/identity.js +17 -0
  19. package/src/server/registerUsersBootstrap.js +2 -2
  20. package/src/server/registerUsersCore.js +2 -2
  21. package/src/server/usersBootstrapContributor.js +5 -5
  22. package/src/shared/resources/userProfileResource.js +1 -1
  23. package/src/shared/resources/userSettingsFields.js +10 -4
  24. package/src/shared/resources/userSettingsResource.js +1 -1
  25. package/templates/packages/main/src/shared/resources/userSettingsFields.js +10 -10
  26. package/templates/packages/users/package.descriptor.mjs +65 -0
  27. package/templates/packages/users/package.json +10 -0
  28. package/templates/packages/users/src/server/UsersProvider.js +91 -0
  29. package/templates/packages/users/src/server/actionIds.js +6 -0
  30. package/templates/packages/users/src/server/actions.js +73 -0
  31. package/templates/packages/users/src/server/listConfig.js +16 -0
  32. package/templates/packages/users/src/server/registerRoutes.js +87 -0
  33. package/templates/packages/users/src/server/repository.js +41 -0
  34. package/templates/packages/users/src/server/service.js +28 -0
  35. package/templates/packages/users/src/shared/index.js +1 -0
  36. package/templates/packages/users/src/shared/userResource.js +74 -0
  37. package/templates/packages/users-workspace/package.descriptor.mjs +66 -0
  38. package/templates/packages/users-workspace/src/server/UsersProvider.js +92 -0
  39. package/templates/packages/users-workspace/src/server/actions.js +74 -0
  40. package/templates/packages/users-workspace/src/server/registerRoutes.js +93 -0
  41. package/test/authProfileSyncService.test.js +3 -3
  42. package/test/avatarService.test.js +2 -2
  43. package/test/registerCommonRepositories.test.js +37 -0
  44. package/test/repositoryContracts.test.js +48 -5
  45. package/test/usersBootstrapContributor.test.js +2 -2
  46. package/test/usersPackageScaffoldContract.test.js +98 -0
  47. package/test/usersRouteResources.test.js +1 -1
@@ -1,37 +1,29 @@
1
+ import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
1
2
  import {
2
3
  isDuplicateEntryError,
3
- normalizeLowerText,
4
4
  normalizeDbRecordId,
5
+ normalizeLowerText,
5
6
  normalizeRecordId,
6
- normalizeText,
7
- toIsoString,
8
- toNullableDateTime,
9
- toNullableIso,
10
- nowDb,
11
- createWithTransaction
7
+ nowDb
12
8
  } from "./repositoryUtils.js";
9
+ import { normalizeIdentity } from "../support/identity.js";
10
+ import { resource } from "../resources/userProfilesResource.js";
13
11
 
14
12
  const USERNAME_MAX_LENGTH = 120;
13
+ const REPOSITORY_CONFIG = Object.freeze({
14
+ context: "internal.repository.user-profiles"
15
+ });
15
16
 
16
- function normalizeIdentity(identityLike) {
17
- const source = identityLike && typeof identityLike === "object" ? identityLike : {};
18
- const provider = normalizeLowerText(source.provider || source.authProvider);
19
- const providerUserId = normalizeText(source.providerUserId || source.authProviderUserSid);
20
- if (!provider || !providerUserId) {
21
- return null;
22
- }
23
- return {
24
- provider,
25
- providerUserId
26
- };
17
+ function normalizeProfileRecord(payload = {}) {
18
+ return resource.operations.view.outputValidator.normalize(payload);
19
+ }
20
+
21
+ function normalizeCreatePayload(payload = {}) {
22
+ return resource.operations.create.bodyValidator.normalize(payload);
27
23
  }
28
24
 
29
25
  function normalizeUsername(value) {
30
- const normalized = normalizeLowerText(value)
31
- .replace(/[^a-z0-9]+/g, "-")
32
- .replace(/^-+|-+$/g, "")
33
- .slice(0, USERNAME_MAX_LENGTH);
34
- return normalized || "";
26
+ return normalizeCreatePayload({ username: value }).username || "";
35
27
  }
36
28
 
37
29
  function usernameBaseFromEmail(email) {
@@ -53,28 +45,11 @@ function buildUsernameCandidate(baseUsername, suffix) {
53
45
  return `${trimmedBase}${suffixText}`;
54
46
  }
55
47
 
56
- function mapProfileRow(row) {
57
- if (!row) {
58
- return null;
59
- }
60
- return {
61
- id: normalizeDbRecordId(row.id, { fallback: "" }),
62
- authProvider: normalizeLowerText(row.auth_provider),
63
- authProviderUserSid: normalizeText(row.auth_provider_user_sid),
64
- email: normalizeLowerText(row.email),
65
- username: normalizeLowerText(row.username),
66
- displayName: normalizeText(row.display_name),
67
- avatarStorageKey: row.avatar_storage_key ? normalizeText(row.avatar_storage_key) : null,
68
- avatarVersion: row.avatar_version == null ? null : String(row.avatar_version),
69
- avatarUpdatedAt: toNullableIso(row.avatar_updated_at),
70
- createdAt: toIsoString(row.created_at)
71
- };
72
- }
73
-
74
48
  function duplicateTargetsEmail(error) {
75
49
  if (!isDuplicateEntryError(error)) {
76
50
  return false;
77
51
  }
52
+
78
53
  const message = normalizeLowerText(error?.sqlMessage || error?.message);
79
54
  return message.includes("email");
80
55
  }
@@ -83,6 +58,7 @@ function duplicateTargetsUsername(error) {
83
58
  if (!isDuplicateEntryError(error)) {
84
59
  return false;
85
60
  }
61
+
86
62
  const message = normalizeLowerText(error?.sqlMessage || error?.message);
87
63
  return message.includes("username");
88
64
  }
@@ -109,9 +85,11 @@ async function resolveUniqueUsername(client, baseUsername, { excludeUserId = nul
109
85
 
110
86
  function createRepository(knex) {
111
87
  if (typeof knex !== "function") {
112
- throw new TypeError("usersRepository requires knex.");
88
+ throw new TypeError("internal.repository.user-profiles requires knex.");
113
89
  }
114
- const withTransaction = createWithTransaction(knex);
90
+
91
+ const resourceRuntime = createCrudResourceRuntime(resource, knex, REPOSITORY_CONFIG);
92
+ const withTransaction = resourceRuntime.withTransaction;
115
93
 
116
94
  async function findById(userId, options = {}) {
117
95
  const normalizedUserId = normalizeRecordId(userId, { fallback: null });
@@ -119,25 +97,38 @@ function createRepository(knex) {
119
97
  return null;
120
98
  }
121
99
 
100
+ return resourceRuntime.findById(normalizedUserId, {
101
+ ...options,
102
+ include: "none"
103
+ });
104
+ }
105
+
106
+ async function findByEmail(email, options = {}) {
107
+ const normalizedEmail = normalizeCreatePayload({ email }).email || "";
108
+ if (!normalizedEmail) {
109
+ return null;
110
+ }
111
+
122
112
  const client = options?.trx || knex;
123
- const row = await client("users").where({ id: normalizedUserId }).first();
124
- return mapProfileRow(row);
113
+ const row = await client("users").where({ email: normalizedEmail }).first();
114
+ return row ? normalizeProfileRecord(row) : null;
125
115
  }
126
116
 
127
117
  async function findByIdentity(identityLike, options = {}) {
128
- const client = options?.trx || knex;
129
118
  const identity = normalizeIdentity(identityLike);
130
119
  if (!identity) {
131
120
  return null;
132
121
  }
133
122
 
123
+ const client = options?.trx || knex;
134
124
  const row = await client("users")
135
125
  .where({
136
126
  auth_provider: identity.provider,
137
127
  auth_provider_user_sid: identity.providerUserId
138
128
  })
139
129
  .first();
140
- return mapProfileRow(row);
130
+
131
+ return row ? normalizeProfileRecord(row) : null;
141
132
  }
142
133
 
143
134
  async function updateDisplayNameById(userId, displayName, options = {}) {
@@ -146,13 +137,14 @@ function createRepository(knex) {
146
137
  return null;
147
138
  }
148
139
 
149
- const client = options?.trx || knex;
150
- await client("users")
151
- .where({ id: normalizedUserId })
152
- .update({
153
- display_name: normalizeText(displayName)
154
- });
155
- return findById(normalizedUserId, { trx: client });
140
+ return resourceRuntime.updateById(
141
+ normalizedUserId,
142
+ { displayName },
143
+ {
144
+ ...options,
145
+ include: "none"
146
+ }
147
+ );
156
148
  }
157
149
 
158
150
  async function updateAvatarById(userId, avatar = {}, options = {}) {
@@ -161,16 +153,18 @@ function createRepository(knex) {
161
153
  return null;
162
154
  }
163
155
 
164
- const client = options?.trx || knex;
165
- await client("users")
166
- .where({ id: normalizedUserId })
167
- .update({
168
- avatar_storage_key: avatar.avatarStorageKey || null,
169
- avatar_version: avatar.avatarVersion == null ? null : String(avatar.avatarVersion),
170
- avatar_updated_at: toNullableDateTime(avatar.avatarUpdatedAt) || nowDb()
171
- });
172
-
173
- return findById(normalizedUserId, { trx: client });
156
+ return resourceRuntime.updateById(
157
+ normalizedUserId,
158
+ {
159
+ avatarStorageKey: avatar.avatarStorageKey ?? null,
160
+ avatarVersion: avatar.avatarVersion ?? null,
161
+ avatarUpdatedAt: avatar.avatarUpdatedAt ?? nowDb()
162
+ },
163
+ {
164
+ ...options,
165
+ include: "none"
166
+ }
167
+ );
174
168
  }
175
169
 
176
170
  async function clearAvatarById(userId, options = {}) {
@@ -179,26 +173,33 @@ function createRepository(knex) {
179
173
  return null;
180
174
  }
181
175
 
182
- const client = options?.trx || knex;
183
- await client("users")
184
- .where({ id: normalizedUserId })
185
- .update({
186
- avatar_storage_key: null,
187
- avatar_version: null,
188
- avatar_updated_at: null
189
- });
190
- return findById(normalizedUserId, { trx: client });
176
+ return resourceRuntime.updateById(
177
+ normalizedUserId,
178
+ {
179
+ avatarStorageKey: null,
180
+ avatarVersion: null,
181
+ avatarUpdatedAt: null
182
+ },
183
+ {
184
+ ...options,
185
+ include: "none"
186
+ }
187
+ );
191
188
  }
192
189
 
193
190
  async function upsert(profileLike = {}, options = {}) {
194
- const identity = normalizeIdentity(profileLike);
191
+ const normalizedPayload = normalizeCreatePayload(profileLike);
192
+ const identity = normalizeIdentity({
193
+ provider: normalizedPayload.authProvider,
194
+ providerUserId: normalizedPayload.authProviderUserSid
195
+ });
195
196
  if (!identity) {
196
197
  throw new TypeError("upsert requires provider/authProvider and providerUserId/authProviderUserSid.");
197
198
  }
198
199
 
199
- const email = normalizeLowerText(profileLike.email);
200
- const displayName = normalizeText(profileLike.displayName);
201
- const requestedUsername = normalizeUsername(profileLike.username);
200
+ const email = normalizedPayload.email || "";
201
+ const displayName = normalizedPayload.displayName || "";
202
+ const requestedUsername = normalizeUsername(normalizedPayload.username);
202
203
  if (!email || !displayName) {
203
204
  throw new TypeError("upsert requires email and displayName.");
204
205
  }
@@ -213,24 +214,44 @@ function createRepository(knex) {
213
214
  try {
214
215
  if (existing) {
215
216
  const existingUsername = normalizeUsername(existing.username);
216
- const username = existingUsername || (await resolveUniqueUsername(trx, requestedUsername || usernameBaseFromEmail(email), {
217
- excludeUserId: existing.id
218
- }));
219
- await trx("users").where({ id: normalizeDbRecordId(existing.id, { fallback: null }) }).update({
220
- email,
221
- display_name: displayName,
222
- username
223
- });
224
- } else {
225
- const username = await resolveUniqueUsername(trx, requestedUsername || usernameBaseFromEmail(email));
226
- await trx("users").insert({
227
- auth_provider: identity.provider,
228
- auth_provider_user_sid: identity.providerUserId,
217
+ const username = existingUsername || (
218
+ await resolveUniqueUsername(
219
+ trx,
220
+ requestedUsername || usernameBaseFromEmail(email),
221
+ { excludeUserId: existing.id }
222
+ )
223
+ );
224
+ return resourceRuntime.updateById(
225
+ normalizeDbRecordId(existing.id, { fallback: null }),
226
+ {
227
+ email,
228
+ displayName,
229
+ username
230
+ },
231
+ {
232
+ trx,
233
+ include: "none"
234
+ }
235
+ );
236
+ }
237
+
238
+ const username = await resolveUniqueUsername(
239
+ trx,
240
+ requestedUsername || usernameBaseFromEmail(email)
241
+ );
242
+ return resourceRuntime.create(
243
+ {
244
+ authProvider: identity.provider,
245
+ authProviderUserSid: identity.providerUserId,
229
246
  email,
230
- display_name: displayName,
247
+ displayName,
231
248
  username
232
- });
233
- }
249
+ },
250
+ {
251
+ trx,
252
+ include: "none"
253
+ }
254
+ );
234
255
  } catch (error) {
235
256
  if (duplicateTargetsEmail(error)) {
236
257
  throw createDuplicateEmailConflictError();
@@ -244,7 +265,7 @@ function createRepository(knex) {
244
265
  }
245
266
 
246
267
  const resolved = await trx("users").where(where).first();
247
- return mapProfileRow(resolved);
268
+ return resolved ? normalizeProfileRecord(resolved) : null;
248
269
  };
249
270
 
250
271
  if (options?.trx) {
@@ -257,6 +278,7 @@ function createRepository(knex) {
257
278
  return Object.freeze({
258
279
  withTransaction,
259
280
  findById,
281
+ findByEmail,
260
282
  findByIdentity,
261
283
  updateDisplayNameById,
262
284
  updateAvatarById,
@@ -265,4 +287,4 @@ function createRepository(knex) {
265
287
  });
266
288
  }
267
289
 
268
- export { createRepository, mapProfileRow, normalizeIdentity };
290
+ export { createRepository };
@@ -25,8 +25,9 @@ function mapRow(row) {
25
25
  };
26
26
 
27
27
  for (const field of userSettingsFields) {
28
- const value = Object.hasOwn(row, field.dbColumn)
29
- ? row[field.dbColumn]
28
+ const column = field.repository.column;
29
+ const value = Object.hasOwn(row, column)
30
+ ? row[column]
30
31
  : field.resolveDefault({
31
32
  settings: mapped,
32
33
  row
@@ -66,7 +67,7 @@ function createInsertPayload(userId) {
66
67
  const defaultValue = field.resolveDefault({
67
68
  settings: resolvedDefaults
68
69
  });
69
- payload[field.dbColumn] = field.normalizeInput(defaultValue, {
70
+ payload[field.repository.column] = field.normalizeInput(defaultValue, {
70
71
  payload: resolvedDefaults,
71
72
  settings: resolvedDefaults
72
73
  });
@@ -132,14 +133,14 @@ function createRepository(knex) {
132
133
  updated_at: nowDb()
133
134
  };
134
135
 
135
- for (const field of userSettingsFields) {
136
- if (!Object.hasOwn(source, field.key)) {
137
- continue;
138
- }
139
- dbPatch[field.dbColumn] = field.normalizeInput(source[field.key], {
140
- payload: source,
141
- settings: ensured
142
- });
136
+ for (const field of userSettingsFields) {
137
+ if (!Object.hasOwn(source, field.key)) {
138
+ continue;
139
+ }
140
+ dbPatch[field.repository.column] = field.normalizeInput(source[field.key], {
141
+ payload: source,
142
+ settings: ensured
143
+ });
143
144
  }
144
145
 
145
146
  if (Object.hasOwn(source, "passwordSignInEnabled")) {
@@ -0,0 +1,203 @@
1
+ import { Type } from "typebox";
2
+ import { normalizeDbRecordId, toIsoString, toNullableDateTime } from "@jskit-ai/database-runtime/shared";
3
+ import {
4
+ createCursorListValidator,
5
+ normalizeObjectInput,
6
+ recordIdSchema
7
+ } from "@jskit-ai/kernel/shared/validators";
8
+ import {
9
+ normalizeIfPresent,
10
+ normalizeLowerText,
11
+ normalizeText,
12
+ normalizeOrNull
13
+ } from "@jskit-ai/kernel/shared/support/normalize";
14
+
15
+ const USERNAME_MAX_LENGTH = 120;
16
+
17
+ function normalizeUsername(value) {
18
+ const normalized = normalizeLowerText(value)
19
+ .replace(/[^a-z0-9]+/g, "-")
20
+ .replace(/^-+|-+$/g, "")
21
+ .slice(0, USERNAME_MAX_LENGTH);
22
+
23
+ return normalized || "";
24
+ }
25
+
26
+ function normalizeNullableString(value) {
27
+ if (value === null || value === undefined) {
28
+ return null;
29
+ }
30
+
31
+ return normalizeText(value);
32
+ }
33
+
34
+ function normalizeNullableVersion(value) {
35
+ if (value === null || value === undefined || value === "") {
36
+ return null;
37
+ }
38
+
39
+ return String(value);
40
+ }
41
+
42
+ function normalizeProfileRecord(payload = {}) {
43
+ const source = normalizeObjectInput(payload);
44
+ const id = normalizeIfPresent(source.id, (value) => normalizeDbRecordId(value, { fallback: null }));
45
+ const displayName = normalizeText(source.displayName ?? source.display_name);
46
+ const email = normalizeLowerText(source.email);
47
+ const username = normalizeUsername(source.username);
48
+
49
+ return {
50
+ id,
51
+ authProvider: normalizeLowerText(source.authProvider ?? source.auth_provider),
52
+ authProviderUserSid: normalizeText(source.authProviderUserSid ?? source.auth_provider_user_sid),
53
+ email,
54
+ username,
55
+ displayName,
56
+ avatarStorageKey: normalizeOrNull(source.avatarStorageKey ?? source.avatar_storage_key, normalizeNullableString),
57
+ avatarVersion: normalizeNullableVersion(source.avatarVersion ?? source.avatar_version),
58
+ avatarUpdatedAt: normalizeOrNull(source.avatarUpdatedAt ?? source.avatar_updated_at, toIsoString),
59
+ createdAt: normalizeIfPresent(source.createdAt ?? source.created_at, toIsoString)
60
+ };
61
+ }
62
+
63
+ function normalizeCreatePayload(payload = {}) {
64
+ const source = normalizeObjectInput(payload);
65
+ const normalized = {};
66
+
67
+ if (Object.hasOwn(source, "authProvider") || Object.hasOwn(source, "provider")) {
68
+ normalized.authProvider = normalizeLowerText(source.authProvider ?? source.provider);
69
+ }
70
+ if (Object.hasOwn(source, "authProviderUserSid") || Object.hasOwn(source, "providerUserId")) {
71
+ normalized.authProviderUserSid = normalizeText(source.authProviderUserSid ?? source.providerUserId);
72
+ }
73
+ if (Object.hasOwn(source, "email")) {
74
+ normalized.email = normalizeLowerText(source.email);
75
+ }
76
+ if (Object.hasOwn(source, "username")) {
77
+ normalized.username = normalizeUsername(source.username);
78
+ }
79
+ if (Object.hasOwn(source, "displayName")) {
80
+ normalized.displayName = normalizeText(source.displayName);
81
+ }
82
+ if (Object.hasOwn(source, "avatarStorageKey")) {
83
+ normalized.avatarStorageKey = normalizeNullableString(source.avatarStorageKey);
84
+ }
85
+ if (Object.hasOwn(source, "avatarVersion")) {
86
+ normalized.avatarVersion = normalizeNullableVersion(source.avatarVersion);
87
+ }
88
+ if (Object.hasOwn(source, "avatarUpdatedAt")) {
89
+ normalized.avatarUpdatedAt = toNullableDateTime(source.avatarUpdatedAt);
90
+ }
91
+
92
+ return normalized;
93
+ }
94
+
95
+ const recordOutputSchema = Type.Object(
96
+ {
97
+ id: recordIdSchema,
98
+ authProvider: Type.String({ minLength: 1 }),
99
+ authProviderUserSid: Type.String({ minLength: 1 }),
100
+ email: Type.String({ minLength: 1 }),
101
+ username: Type.String({ minLength: 1 }),
102
+ displayName: Type.String({ minLength: 1 }),
103
+ avatarStorageKey: Type.Union([Type.String(), Type.Null()]),
104
+ avatarVersion: Type.Union([Type.String(), Type.Null()]),
105
+ avatarUpdatedAt: Type.Union([Type.String({ format: "date-time", minLength: 1 }), Type.Null()]),
106
+ createdAt: Type.String({ format: "date-time", minLength: 1 })
107
+ },
108
+ { additionalProperties: false }
109
+ );
110
+
111
+ const createBodySchema = Type.Object(
112
+ {
113
+ authProvider: Type.String({ minLength: 1, maxLength: 64 }),
114
+ authProviderUserSid: Type.String({ minLength: 1, maxLength: 191 }),
115
+ email: Type.String({ minLength: 1, maxLength: 255 }),
116
+ username: Type.Optional(Type.String({ minLength: 1, maxLength: USERNAME_MAX_LENGTH })),
117
+ displayName: Type.String({ minLength: 1, maxLength: 160 }),
118
+ avatarStorageKey: Type.Optional(Type.Union([Type.String({ maxLength: 512 }), Type.Null()])),
119
+ avatarVersion: Type.Optional(Type.Union([Type.String({ maxLength: 64 }), Type.Null()])),
120
+ avatarUpdatedAt: Type.Optional(Type.Union([Type.String({ format: "date-time", minLength: 1 }), Type.Null()]))
121
+ },
122
+ {
123
+ additionalProperties: false,
124
+ required: []
125
+ }
126
+ );
127
+
128
+ const patchBodySchema = Type.Partial(createBodySchema, {
129
+ additionalProperties: false
130
+ });
131
+
132
+ const recordOutputValidator = Object.freeze({
133
+ schema: recordOutputSchema,
134
+ normalize: normalizeProfileRecord
135
+ });
136
+
137
+ const createBodyValidator = Object.freeze({
138
+ schema: createBodySchema,
139
+ normalize: normalizeCreatePayload
140
+ });
141
+
142
+ const patchBodyValidator = Object.freeze({
143
+ schema: patchBodySchema,
144
+ normalize: normalizeCreatePayload
145
+ });
146
+
147
+ const resource = Object.freeze({
148
+ namespace: "userProfiles",
149
+ tableName: "users",
150
+ idColumn: "id",
151
+ operations: Object.freeze({
152
+ list: Object.freeze({
153
+ method: "GET",
154
+ outputValidator: createCursorListValidator(recordOutputValidator)
155
+ }),
156
+ view: Object.freeze({
157
+ method: "GET",
158
+ outputValidator: recordOutputValidator
159
+ }),
160
+ create: Object.freeze({
161
+ method: "POST",
162
+ bodyValidator: createBodyValidator,
163
+ outputValidator: recordOutputValidator
164
+ }),
165
+ patch: Object.freeze({
166
+ method: "PATCH",
167
+ bodyValidator: patchBodyValidator,
168
+ outputValidator: recordOutputValidator
169
+ })
170
+ }),
171
+ fieldMeta: Object.freeze([
172
+ Object.freeze({
173
+ key: "authProvider",
174
+ repository: { column: "auth_provider" }
175
+ }),
176
+ Object.freeze({
177
+ key: "authProviderUserSid",
178
+ repository: { column: "auth_provider_user_sid" }
179
+ }),
180
+ Object.freeze({
181
+ key: "displayName",
182
+ repository: { column: "display_name" }
183
+ }),
184
+ Object.freeze({
185
+ key: "avatarStorageKey",
186
+ repository: { column: "avatar_storage_key" }
187
+ }),
188
+ Object.freeze({
189
+ key: "avatarVersion",
190
+ repository: { column: "avatar_version" }
191
+ }),
192
+ Object.freeze({
193
+ key: "avatarUpdatedAt",
194
+ repository: { column: "avatar_updated_at" }
195
+ }),
196
+ Object.freeze({
197
+ key: "createdAt",
198
+ repository: { column: "created_at" }
199
+ })
200
+ ])
201
+ });
202
+
203
+ export { resource };
@@ -1,10 +1,10 @@
1
1
  import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
- import { normalizeIdentity } from "../repositories/usersRepository.js";
2
+ import { normalizeIdentity } from "../support/identity.js";
3
3
 
4
- async function resolveUserProfile(usersRepository, user) {
4
+ async function resolveUserProfile(userProfilesRepository, user) {
5
5
  const identity = normalizeIdentity(user);
6
6
  if (identity) {
7
- const profile = await usersRepository.findByIdentity(identity);
7
+ const profile = await userProfilesRepository.findByIdentity(identity);
8
8
  if (profile) {
9
9
  return profile;
10
10
  }
@@ -12,7 +12,7 @@ async function resolveUserProfile(usersRepository, user) {
12
12
 
13
13
  const userId = normalizeRecordId(user?.id, { fallback: null });
14
14
  if (userId) {
15
- const profileById = await usersRepository.findById(userId);
15
+ const profileById = await userProfilesRepository.findById(userId);
16
16
  if (profileById) {
17
17
  return profileById;
18
18
  }
@@ -1,6 +1,6 @@
1
1
  import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
2
2
  import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
3
- import { normalizeIdentity } from "../repositories/usersRepository.js";
3
+ import { normalizeIdentity } from "../support/identity.js";
4
4
 
5
5
  function buildNormalizedIdentityKey(identityLike) {
6
6
  const identity = normalizeIdentity(identityLike);
@@ -64,15 +64,15 @@ function normalizeLifecycleContributors(entries = []) {
64
64
  );
65
65
  }
66
66
 
67
- function createService({ usersRepository, lifecycleContributors = [], userSettingsRepository = null } = {}) {
68
- if (!usersRepository || typeof usersRepository.findByIdentity !== "function") {
69
- throw new Error("authProfileSyncService requires usersRepository.findByIdentity().");
67
+ function createService({ userProfilesRepository, lifecycleContributors = [], userSettingsRepository = null } = {}) {
68
+ if (!userProfilesRepository || typeof userProfilesRepository.findByIdentity !== "function") {
69
+ throw new Error("authProfileSyncService requires userProfilesRepository.findByIdentity().");
70
70
  }
71
- if (typeof usersRepository.upsert !== "function") {
72
- throw new Error("authProfileSyncService requires usersRepository.upsert().");
71
+ if (typeof userProfilesRepository.upsert !== "function") {
72
+ throw new Error("authProfileSyncService requires userProfilesRepository.upsert().");
73
73
  }
74
- if (typeof usersRepository.withTransaction !== "function") {
75
- throw new Error("authProfileSyncService requires usersRepository.withTransaction().");
74
+ if (typeof userProfilesRepository.withTransaction !== "function") {
75
+ throw new Error("authProfileSyncService requires userProfilesRepository.withTransaction().");
76
76
  }
77
77
  if (!userSettingsRepository || typeof userSettingsRepository.ensureForUserId !== "function") {
78
78
  throw new Error("authProfileSyncService requires userSettingsRepository.ensureForUserId().");
@@ -82,7 +82,7 @@ function createService({ usersRepository, lifecycleContributors = [], userSettin
82
82
 
83
83
  async function findByIdentity(identityLike, options = {}) {
84
84
  const normalized = buildNormalizedIdentityKey(identityLike);
85
- return usersRepository.findByIdentity(
85
+ return userProfilesRepository.findByIdentity(
86
86
  {
87
87
  provider: normalized.authProvider,
88
88
  providerUserId: normalized.authProviderUserSid
@@ -93,7 +93,7 @@ function createService({ usersRepository, lifecycleContributors = [], userSettin
93
93
 
94
94
  async function upsertByIdentity(profileLike, options = {}) {
95
95
  const normalized = buildNormalizedIdentityProfile(profileLike);
96
- return usersRepository.upsert(
96
+ return userProfilesRepository.upsert(
97
97
  {
98
98
  authProvider: normalized.authProvider,
99
99
  authProviderUserSid: normalized.authProviderUserSid,
@@ -144,7 +144,7 @@ function createService({ usersRepository, lifecycleContributors = [], userSettin
144
144
  return runSync(options.trx);
145
145
  }
146
146
 
147
- return usersRepository.withTransaction((trx) => runSync(trx));
147
+ return userProfilesRepository.withTransaction((trx) => runSync(trx));
148
148
  }
149
149
 
150
150
  return Object.freeze({
@@ -0,0 +1,17 @@
1
+ import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function normalizeIdentity(identityLike) {
4
+ const source = identityLike && typeof identityLike === "object" ? identityLike : {};
5
+ const provider = normalizeLowerText(source.provider || source.authProvider);
6
+ const providerUserId = normalizeText(source.providerUserId || source.authProviderUserSid);
7
+ if (!provider || !providerUserId) {
8
+ return null;
9
+ }
10
+
11
+ return {
12
+ provider,
13
+ providerUserId
14
+ };
15
+ }
16
+
17
+ export { normalizeIdentity };
@@ -9,8 +9,8 @@ function registerUsersBootstrap(app) {
9
9
 
10
10
  registerBootstrapPayloadContributor(app, "users.core.bootstrap.payloadContributor", (scope) => {
11
11
  return createUsersBootstrapContributor({
12
- usersRepository: scope.make("usersRepository"),
13
- userSettingsRepository: scope.make("userSettingsRepository"),
12
+ userProfilesRepository: scope.make("internal.repository.user-profiles"),
13
+ userSettingsRepository: scope.make("internal.repository.user-settings"),
14
14
  appConfig: resolveAppConfig(scope),
15
15
  authService: scope.make("authService")
16
16
  });