@jskit-ai/users-core 0.1.145 → 0.1.147

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-core",
4
- version: "0.1.145",
4
+ version: "0.1.147",
5
5
  kind: "runtime",
6
6
  description: "Users/account runtime plus HTTP routes for account features.",
7
7
  dependsOn: [
@@ -146,16 +146,16 @@ export default Object.freeze({
146
146
  mutations: {
147
147
  dependencies: {
148
148
  runtime: {
149
- "@jskit-ai/auth-core": "0.1.132",
150
- "@jskit-ai/crud-core": "0.1.143",
151
- "@jskit-ai/database-runtime": "0.1.133",
152
- "@jskit-ai/http-runtime": "0.1.132",
153
- "@jskit-ai/json-rest-api-core": "0.1.78",
154
- "@jskit-ai/kernel": "0.1.134",
155
- "@jskit-ai/resource-core": "0.1.77",
156
- "@jskit-ai/resource-crud-core": "0.1.77",
149
+ "@jskit-ai/auth-core": "0.1.133",
150
+ "@jskit-ai/crud-core": "0.1.144",
151
+ "@jskit-ai/database-runtime": "0.1.134",
152
+ "@jskit-ai/http-runtime": "0.1.133",
153
+ "@jskit-ai/json-rest-api-core": "0.1.79",
154
+ "@jskit-ai/kernel": "0.1.135",
155
+ "@jskit-ai/resource-core": "0.1.78",
156
+ "@jskit-ai/resource-crud-core": "0.1.78",
157
157
  "@local/users": "file:packages/users",
158
- "@jskit-ai/uploads-runtime": "0.1.110"
158
+ "@jskit-ai/uploads-runtime": "0.1.111"
159
159
  },
160
160
  dev: {}
161
161
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-core",
3
- "version": "0.1.145",
3
+ "version": "0.1.147",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -11,14 +11,14 @@
11
11
  "./shared/resources/userSettingsResource": "./src/shared/resources/userSettingsResource.js"
12
12
  },
13
13
  "dependencies": {
14
- "@jskit-ai/auth-core": "0.1.132",
15
- "@jskit-ai/database-runtime": "0.1.133",
16
- "@jskit-ai/http-runtime": "0.1.132",
17
- "@jskit-ai/json-rest-api-core": "0.1.78",
18
- "@jskit-ai/kernel": "0.1.134",
19
- "@jskit-ai/resource-crud-core": "0.1.77",
20
- "@jskit-ai/resource-core": "0.1.77",
21
- "@jskit-ai/uploads-runtime": "0.1.110",
14
+ "@jskit-ai/auth-core": "0.1.133",
15
+ "@jskit-ai/database-runtime": "0.1.134",
16
+ "@jskit-ai/http-runtime": "0.1.133",
17
+ "@jskit-ai/json-rest-api-core": "0.1.79",
18
+ "@jskit-ai/kernel": "0.1.135",
19
+ "@jskit-ai/resource-crud-core": "0.1.78",
20
+ "@jskit-ai/resource-core": "0.1.78",
21
+ "@jskit-ai/uploads-runtime": "0.1.111",
22
22
  "json-rest-schema": "1.x.x"
23
23
  }
24
24
  }
@@ -135,7 +135,7 @@ function bootAccountProfileRoutes(app) {
135
135
  stream: filePart.stream,
136
136
  mimeType: filePart.mimeType,
137
137
  fileName: filePart.fileName,
138
- uploadDimension
138
+ ...(uploadDimension !== undefined ? { uploadDimension } : {})
139
139
  }
140
140
  });
141
141
 
@@ -116,11 +116,12 @@ function bootAccountSecurityRoutes(app) {
116
116
  }
117
117
  },
118
118
  async function (request, reply) {
119
+ const query = request.input.query || {};
119
120
  const result = await request.executeAction({
120
121
  actionId: "settings.security.oauth.link.start",
121
122
  input: {
122
123
  provider: request.input.params.provider,
123
- returnTo: request.input.query.returnTo
124
+ ...(Object.hasOwn(query, "returnTo") ? { returnTo: query.returnTo } : {})
124
125
  }
125
126
  });
126
127
 
@@ -174,8 +174,8 @@ function createRepository({ api, knex } = {}) {
174
174
 
175
175
  const withTransaction = createWithTransaction(knex);
176
176
 
177
- async function queryProfiles(filters = {}, options = {}) {
178
- return extractJsonRestCollectionRows(
177
+ async function queryFirst(filters = {}, options = {}) {
178
+ const rows = extractJsonRestCollectionRows(
179
179
  await api.resources.userProfiles.query(
180
180
  {
181
181
  queryParams: {
@@ -187,10 +187,8 @@ function createRepository({ api, knex } = {}) {
187
187
  createJsonRestContext(options?.context || null)
188
188
  )
189
189
  );
190
- }
191
190
 
192
- async function queryFirst(filters = {}, options = {}) {
193
- return (await queryProfiles(filters, options))[0] || null;
191
+ return rows[0] || null;
194
192
  }
195
193
 
196
194
  async function findById(userId, options = {}) {
@@ -211,21 +209,6 @@ function createRepository({ api, knex } = {}) {
211
209
  return normalizeProfileRecord(await queryFirst({ email: normalizedEmail }, options));
212
210
  }
213
211
 
214
- async function findByUsername(username, options = {}) {
215
- const normalizedUsername = normalizeUsername(username);
216
- if (!normalizedUsername) {
217
- return null;
218
- }
219
-
220
- const rows = await queryProfiles({ username: normalizedUsername }, options);
221
- if (rows.length > 1) {
222
- const error = new Error("Application login matches more than one user profile.");
223
- error.code = "USER_PROFILE_LOGIN_AMBIGUOUS";
224
- throw error;
225
- }
226
- return normalizeProfileRecord(rows[0] || null);
227
- }
228
-
229
212
  async function findByIdentity(identityLike, options = {}) {
230
213
  const identity = normalizeIdentity(identityLike);
231
214
  if (!identity) {
@@ -433,7 +416,6 @@ function createRepository({ api, knex } = {}) {
433
416
  findById,
434
417
  findByEmail,
435
418
  findByIdentity,
436
- findByUsername,
437
419
  updateDisplayNameById,
438
420
  updateAvatarById,
439
421
  clearAvatarById,
@@ -68,9 +68,6 @@ function createService({ userProfilesRepository, lifecycleContributors = [], use
68
68
  if (!userProfilesRepository || typeof userProfilesRepository.findByIdentity !== "function") {
69
69
  throw new Error("authProfileSyncService requires userProfilesRepository.findByIdentity().");
70
70
  }
71
- if (typeof userProfilesRepository.findByUsername !== "function") {
72
- throw new Error("authProfileSyncService requires userProfilesRepository.findByUsername().");
73
- }
74
71
  if (typeof userProfilesRepository.upsert !== "function") {
75
72
  throw new Error("authProfileSyncService requires userProfilesRepository.upsert().");
76
73
  }
@@ -94,10 +91,6 @@ function createService({ userProfilesRepository, lifecycleContributors = [], use
94
91
  );
95
92
  }
96
93
 
97
- async function findByLogin(login, options = {}) {
98
- return userProfilesRepository.findByUsername(normalizeLowerText(login), options);
99
- }
100
-
101
94
  async function upsertByIdentity(profileLike, options = {}) {
102
95
  const normalized = buildNormalizedIdentityProfile(profileLike);
103
96
  return userProfilesRepository.upsert(
@@ -156,7 +149,6 @@ function createService({ userProfilesRepository, lifecycleContributors = [], use
156
149
 
157
150
  return Object.freeze({
158
151
  findByIdentity,
159
- findByLogin,
160
152
  upsertByIdentity,
161
153
  syncIdentityProfile
162
154
  });
@@ -12,9 +12,6 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
12
12
  calls.push({ step: "find", trx: options.trx || null });
13
13
  return null;
14
14
  },
15
- async findByUsername() {
16
- return null;
17
- },
18
15
  async upsert(payload, options = {}) {
19
16
  calls.push({ step: "upsert", trx: options.trx || null });
20
17
  return {
@@ -81,9 +78,6 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
81
78
  displayName: "Tony"
82
79
  };
83
80
  },
84
- async findByUsername() {
85
- return null;
86
- },
87
81
  async upsert() {
88
82
  upsertCalls += 1;
89
83
  return null;
@@ -129,9 +123,6 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
129
123
  capturedIdentity = identity;
130
124
  return null;
131
125
  },
132
- async findByUsername() {
133
- return null;
134
- },
135
126
  async upsert() {
136
127
  return null;
137
128
  },
@@ -156,42 +147,3 @@ test("authProfileSyncService.findByIdentity normalizes provider identity input",
156
147
  providerUserId: "user-1"
157
148
  });
158
149
  });
159
-
160
- test("authProfileSyncService.findByLogin resolves the normalized application username", async () => {
161
- let capturedUsername = null;
162
- const expectedProfile = {
163
- id: "7",
164
- authProvider: "local",
165
- authProviderUserSid: "local-user-7",
166
- email: "ada@example.com",
167
- username: "merc",
168
- displayName: "Ada"
169
- };
170
- const service = createService({
171
- userProfilesRepository: {
172
- async findByIdentity() {
173
- return null;
174
- },
175
- async findByUsername(username) {
176
- capturedUsername = username;
177
- return expectedProfile;
178
- },
179
- async upsert() {
180
- return null;
181
- },
182
- async withTransaction(work) {
183
- return work({ trxId: "tx-4" });
184
- }
185
- },
186
- userSettingsRepository: {
187
- async ensureForUserId() {
188
- return { userId: "7" };
189
- }
190
- }
191
- });
192
-
193
- const profile = await service.findByLogin(" MERC ");
194
-
195
- assert.equal(capturedUsername, "merc");
196
- assert.equal(profile, expectedProfile);
197
- });
@@ -328,64 +328,6 @@ test("userProfilesRepository.findByEmail normalizes email lookup", async () => {
328
328
  assert.equal(profile?.displayName, "Ada Example");
329
329
  });
330
330
 
331
- test("userProfilesRepository.findByUsername normalizes login lookup", async () => {
332
- const { api, calls } = createUserProfilesApiStub({
333
- id: 7,
334
- authProvider: "local",
335
- authProviderUserSid: "local-user-7",
336
- email: "ada@example.com",
337
- username: "merc-user",
338
- displayName: "Ada Example"
339
- });
340
- const repository = createUserProfilesRepository({
341
- api,
342
- knex: createKnexStub()
343
- });
344
-
345
- const profile = await repository.findByUsername(" Merc User ");
346
-
347
- assert.deepEqual(calls, [{ username: "merc-user" }]);
348
- assert.equal(profile?.id, "7");
349
- assert.equal(profile?.username, "merc-user");
350
- });
351
-
352
- test("userProfilesRepository.findByUsername rejects ambiguous login data", async () => {
353
- const repository = createUserProfilesRepository({
354
- knex: createKnexStub(),
355
- api: {
356
- resources: {
357
- userProfiles: {
358
- async query() {
359
- return asCollectionDocument([
360
- {
361
- id: "7",
362
- authProvider: "local",
363
- authProviderUserSid: "local-user-7",
364
- email: "ada@example.com",
365
- username: "merc",
366
- displayName: "Ada"
367
- },
368
- {
369
- id: "8",
370
- authProvider: "local",
371
- authProviderUserSid: "local-user-8",
372
- email: "grace@example.com",
373
- username: "merc",
374
- displayName: "Grace"
375
- }
376
- ]);
377
- }
378
- }
379
- }
380
- }
381
- });
382
-
383
- await assert.rejects(
384
- () => repository.findByUsername("merc"),
385
- (error) => error?.code === "USER_PROFILE_LOGIN_AMBIGUOUS"
386
- );
387
- });
388
-
389
331
  test("userProfilesRepository.findByIdentity reads existing profiles from collection documents", async () => {
390
332
  const { api, calls } = createUserProfilesApiStub({
391
333
  id: 7,
@@ -228,6 +228,54 @@ test("account route handlers build action input from request.input", async () =>
228
228
  assert.equal(calls[7].actionId, "settings.security.sessions.logout_others");
229
229
  });
230
230
 
231
+ test("account route handlers preserve omitted optional action fields", async () => {
232
+ const routes = await registerRoutes();
233
+ const calls = [];
234
+ const executeAction = async (payload) => {
235
+ calls.push(payload);
236
+ if (payload.actionId === "settings.security.oauth.link.start") {
237
+ return { url: "/oauth/link" };
238
+ }
239
+ return {};
240
+ };
241
+
242
+ await findRoute(routes, { method: "GET", path: "/api/settings/security/oauth/:provider/start" }).handler(
243
+ createActionRequest({
244
+ input: {
245
+ params: { provider: "github" },
246
+ query: {}
247
+ },
248
+ executeAction
249
+ }),
250
+ createReplyDouble()
251
+ );
252
+
253
+ const avatarStream = {};
254
+ await findRoute(routes, { method: "POST", path: "/api/settings/profile/avatar" }).handler(
255
+ createActionRequest({
256
+ file: async () => ({
257
+ fieldname: "avatar",
258
+ filename: "avatar.png",
259
+ mimetype: "image/png",
260
+ encoding: "7bit",
261
+ file: avatarStream,
262
+ fields: {}
263
+ }),
264
+ executeAction
265
+ }),
266
+ createReplyDouble()
267
+ );
268
+
269
+ assert.deepEqual(calls[0].input, { provider: "github" });
270
+ assert.equal(Object.hasOwn(calls[0].input, "returnTo"), false);
271
+ assert.deepEqual(calls[1].input, {
272
+ stream: avatarStream,
273
+ mimeType: "image/png",
274
+ fileName: "avatar.png"
275
+ });
276
+ assert.equal(Object.hasOwn(calls[1].input, "uploadDimension"), false);
277
+ });
278
+
231
279
  test("account settings jsonapi transport resolves response resource id from request user", async () => {
232
280
  const routes = await registerRoutes();
233
281
  const settingsRoute = findRoute(routes, { method: "GET", path: "/api/settings" });