@unchainedshop/core-users 3.0.0-alpha7 → 3.0.0-rc1

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 (51) hide show
  1. package/lib/module/configureUsersModule.d.ts +10 -68
  2. package/lib/module/configureUsersModule.d.ts.map +1 -1
  3. package/lib/module/configureUsersModule.js +57 -52
  4. package/lib/module/configureUsersModule.js.map +1 -1
  5. package/lib/module/configureUsersModule_BACKUP_57123.d.ts +73 -0
  6. package/lib/module/configureUsersModule_BACKUP_57123.d.ts.map +1 -0
  7. package/lib/module/configureUsersModule_BACKUP_57123.js +567 -0
  8. package/lib/module/configureUsersModule_BACKUP_57123.js.map +1 -0
  9. package/lib/module/configureUsersModule_BASE_57123.d.ts +131 -0
  10. package/lib/module/configureUsersModule_BASE_57123.d.ts.map +1 -0
  11. package/lib/module/configureUsersModule_BASE_57123.js +563 -0
  12. package/lib/module/configureUsersModule_BASE_57123.js.map +1 -0
  13. package/lib/module/configureUsersModule_LOCAL_57123.d.ts +131 -0
  14. package/lib/module/configureUsersModule_LOCAL_57123.d.ts.map +1 -0
  15. package/lib/module/configureUsersModule_LOCAL_57123.js +564 -0
  16. package/lib/module/configureUsersModule_LOCAL_57123.js.map +1 -0
  17. package/lib/module/configureUsersModule_REMOTE_57123.d.ts +74 -0
  18. package/lib/module/configureUsersModule_REMOTE_57123.d.ts.map +1 -0
  19. package/lib/module/configureUsersModule_REMOTE_57123.js +567 -0
  20. package/lib/module/configureUsersModule_REMOTE_57123.js.map +1 -0
  21. package/lib/module/configureUsersWebAuthnModule.d.ts.map +1 -1
  22. package/lib/module/configureUsersWebAuthnModule.js.map +1 -1
  23. package/lib/types.d.ts +5 -2
  24. package/lib/types.d.ts.map +1 -1
  25. package/lib/users-index.d.ts +0 -1
  26. package/lib/users-index.d.ts.map +1 -1
  27. package/lib/users-index.js +0 -1
  28. package/lib/users-index.js.map +1 -1
  29. package/package.json +14 -17
  30. package/src/module/configureUsersModule.ts +77 -72
  31. package/src/module/configureUsersWebAuthnModule.ts +0 -1
  32. package/src/types.ts +5 -2
  33. package/src/users-index.ts +0 -1
  34. package/tests/mock/user-mock.ts +2 -3
  35. package/tsconfig.json +5 -7
  36. package/lib/services/migrateUserDataService.d.ts +0 -4
  37. package/lib/services/migrateUserDataService.d.ts.map +0 -1
  38. package/lib/services/migrateUserDataService.js +0 -18
  39. package/lib/services/migrateUserDataService.js.map +0 -1
  40. package/lib/services/updateUserAvatarAfterUploadService.d.ts +0 -15
  41. package/lib/services/updateUserAvatarAfterUploadService.d.ts.map +0 -1
  42. package/lib/services/updateUserAvatarAfterUploadService.js +0 -24
  43. package/lib/services/updateUserAvatarAfterUploadService.js.map +0 -1
  44. package/lib/services/userServices.d.ts +0 -8
  45. package/lib/services/userServices.d.ts.map +0 -1
  46. package/lib/services/userServices.js +0 -7
  47. package/lib/services/userServices.js.map +0 -1
  48. package/src/module/sha256.ts +0 -5
  49. package/src/services/migrateUserDataService.ts +0 -37
  50. package/src/services/updateUserAvatarAfterUploadService.ts +0 -42
  51. package/src/services/userServices.ts +0 -15
package/lib/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { TimestampFields, Address, Contact, mongodb } from '@unchainedshop/mongodb';
1
+ import { TimestampFields, Address, Contact, mongodb } from '@unchainedshop/mongodb';
2
+ import { DateFilterInput } from '@unchainedshop/utils';
2
3
  export interface PushSubscription {
3
4
  _id: string;
4
5
  userAgent: string;
@@ -47,7 +48,7 @@ export interface PushSubscriptionObject {
47
48
  };
48
49
  }
49
50
  export type User = {
50
- _id?: string;
51
+ _id: string;
51
52
  deleted?: Date;
52
53
  avatarId?: string;
53
54
  emails: Array<Email>;
@@ -67,6 +68,8 @@ export type User = {
67
68
  export type UserQuery = mongodb.Filter<User> & {
68
69
  includeGuests?: boolean;
69
70
  queryString?: string;
71
+ emailVerified?: boolean;
72
+ lastLogin?: DateFilterInput;
70
73
  };
71
74
  export interface WebAuthnCredentialsCreationRequest {
72
75
  challenge: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  export * from './types.js';
2
2
  export * from './module/configureUsersModule.js';
3
- export * from './services/userServices.js';
4
3
  export * from './users-settings.js';
5
4
  //# sourceMappingURL=users-index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"users-index.d.ts","sourceRoot":"","sources":["../src/users-index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kCAAkC,CAAC;AACjD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"users-index.d.ts","sourceRoot":"","sources":["../src/users-index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kCAAkC,CAAC;AACjD,cAAc,qBAAqB,CAAC"}
@@ -1,5 +1,4 @@
1
1
  export * from './types.js';
2
2
  export * from './module/configureUsersModule.js';
3
- export * from './services/userServices.js';
4
3
  export * from './users-settings.js';
5
4
  //# sourceMappingURL=users-index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"users-index.js","sourceRoot":"","sources":["../src/users-index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kCAAkC,CAAC;AACjD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"users-index.js","sourceRoot":"","sources":["../src/users-index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,kCAAkC,CAAC;AACjD,cAAc,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,16 +1,13 @@
1
1
  {
2
2
  "name": "@unchainedshop/core-users",
3
- "version": "3.0.0-alpha7",
3
+ "version": "3.0.0-rc1",
4
4
  "main": "lib/users-index.js",
5
- "exports": {
6
- ".": "./lib/users-index.js",
7
- "./*": "./lib/*"
8
- },
9
5
  "types": "lib/users-index.d.ts",
10
6
  "type": "module",
11
7
  "scripts": {
12
- "clean": "rm -rf lib",
13
- "prepublishOnly": "npm run clean && tsc",
8
+ "clean": "tsc -b --clean",
9
+ "build": "tsc -b",
10
+ "prepublishOnly": "npm run clean && npm run build",
14
11
  "watch": "tsc -w",
15
12
  "test": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit",
16
13
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --detectOpenHandles --forceExit"
@@ -31,19 +28,19 @@
31
28
  },
32
29
  "homepage": "https://github.com/unchainedshop/unchained#readme",
33
30
  "dependencies": {
34
- "@unchainedshop/events": "^3.0.0-alpha4",
35
- "@unchainedshop/file-upload": "^3.0.0-alpha4",
36
- "@unchainedshop/logger": "^3.0.0-alpha4",
37
- "@unchainedshop/roles": "^3.0.0-alpha4",
38
- "@unchainedshop/utils": "^3.0.0-alpha4",
39
- "bcryptjs": "^2.4.3",
40
- "fido2-lib": "^3.5.3",
41
- "uuid": "^10.0.0"
31
+ "@node-rs/bcrypt": "^1.10.7",
32
+ "@unchainedshop/events": "^3.0.0-rc1",
33
+ "@unchainedshop/file-upload": "^3.0.0-rc1",
34
+ "@unchainedshop/logger": "^3.0.0-rc1",
35
+ "@unchainedshop/mongodb": "^3.0.0-rc1",
36
+ "@unchainedshop/roles": "^3.0.0-rc1",
37
+ "@unchainedshop/utils": "^3.0.0-rc1",
38
+ "fido2-lib": "^3.5.3"
42
39
  },
43
40
  "devDependencies": {
44
- "@types/node": "^22.8.1",
41
+ "@types/node": "^22.10.2",
45
42
  "jest": "^29.7.0",
46
43
  "ts-jest": "^29.2.5",
47
- "typescript": "^5.6.3"
44
+ "typescript": "^5.7.2"
48
45
  }
49
46
  }
@@ -1,5 +1,5 @@
1
- import bcrypt from 'bcryptjs';
2
- import { ModuleInput } from '@unchainedshop/mongodb';
1
+ import bcrypt from '@node-rs/bcrypt';
2
+ import { ModuleInput, Address, Contact } from '@unchainedshop/mongodb';
3
3
  import { User, UserQuery, Email, UserLastLogin, UserProfile, UserData } from '../types.js';
4
4
  import { emit, registerEvents } from '@unchainedshop/events';
5
5
  import {
@@ -8,18 +8,12 @@ import {
8
8
  mongodb,
9
9
  generateDbObjectId,
10
10
  } from '@unchainedshop/mongodb';
11
- import { systemLocale } from '@unchainedshop/utils';
12
- import { FileDirector } from '@unchainedshop/file-upload';
13
- import { SortDirection, SortOption } from '@unchainedshop/utils';
11
+ import { systemLocale, SortDirection, SortOption, sha256 } from '@unchainedshop/utils';
14
12
  import { UsersCollection } from '../db/UsersCollection.js';
15
13
  import addMigrations from './addMigrations.js';
16
14
  import { userSettings, UserSettingsOptions } from '../users-settings.js';
17
15
  import { configureUsersWebAuthnModule, UsersWebAuthnModule } from './configureUsersWebAuthnModule.js';
18
16
  import * as pbkdf2 from './pbkdf2.js';
19
- import * as sha256 from './sha256.js';
20
- import type { Address, Contact } from '@unchainedshop/mongodb';
21
- import { UserServices } from '../services/userServices.js';
22
- import { FileServices, FilesModule } from '@unchainedshop/core-files';
23
17
 
24
18
  export type UsersModule = {
25
19
  // Submodules
@@ -76,7 +70,8 @@ export type UsersModule = {
76
70
  _id: string,
77
71
  { profile, meta }: { profile?: UserProfile; meta?: any },
78
72
  ) => Promise<User>;
79
- delete: (userId: string) => Promise<User>;
73
+ markDeleted: (userId: string) => Promise<User>;
74
+ deletePermanently: (params: { userId: string }) => Promise<User>;
80
75
  updateRoles: (_id: string, roles: Array<string>) => Promise<User>;
81
76
  updateTags: (_id: string, tags: Array<string>) => Promise<User>;
82
77
  updateUser: (
@@ -93,6 +88,9 @@ export type UsersModule = {
93
88
  },
94
89
  ) => Promise<void>;
95
90
  removePushSubscription: (userId: string, p256dh: string) => Promise<void>;
91
+ hashPassword(password: string): Promise<{
92
+ pbkdf2: string;
93
+ }>;
96
94
  };
97
95
 
98
96
  const USER_EVENTS = [
@@ -118,36 +116,44 @@ export const removeConfidentialServiceHashes = (rawUser: User): User => {
118
116
  return user;
119
117
  };
120
118
 
121
- export const buildFindSelector = ({ includeGuests, queryString, ...rest }: UserQuery) => {
119
+ export const buildFindSelector = ({
120
+ includeGuests,
121
+ queryString,
122
+ emailVerified,
123
+ lastLogin,
124
+ ...rest
125
+ }: UserQuery) => {
122
126
  const selector: mongodb.Filter<User> = { ...rest, deleted: null };
123
127
  if (!includeGuests) selector.guest = { $in: [false, null] };
128
+ if (emailVerified === true) {
129
+ selector['emails.verified'] = true;
130
+ }
131
+ if (emailVerified === false) {
132
+ // We need to use $ne here else we'd also find users with many emails where one is
133
+ // unverified
134
+ selector['emails.verified'] = { $ne: true };
135
+ }
136
+ if (lastLogin?.start) {
137
+ selector['lastLogin.timestamp'] = { $exists: true };
138
+ }
139
+ if (lastLogin?.end) {
140
+ selector['lastLogin.timestamp'].$lte = new Date(lastLogin.end);
141
+ }
142
+ if (lastLogin?.start) {
143
+ selector['lastLogin.timestamp'].$gte = new Date(lastLogin.start);
144
+ }
124
145
  if (queryString) {
125
146
  (selector as any).$text = { $search: queryString };
126
147
  }
127
148
  return selector;
128
149
  };
129
150
 
130
- FileDirector.registerFileUploadCallback<{
131
- services: {
132
- users: UserServices;
133
- files: FileServices;
134
- };
135
- modules: {
136
- users: UsersModule;
137
- files: FilesModule;
138
- };
139
- }>('user-avatars', async (file, context) => {
140
- const { services } = context;
141
- return services.users.updateUserAvatarAfterUpload({ file }, context);
142
- });
143
-
144
151
  export const configureUsersModule = async ({
145
152
  db,
146
153
  options,
147
154
  migrationRepository,
148
- }: ModuleInput<UserSettingsOptions>) => {
155
+ }: ModuleInput<UserSettingsOptions>): Promise<UsersModule> => {
149
156
  userSettings.configureSettings(options || {}, db);
150
-
151
157
  registerEvents(USER_EVENTS);
152
158
  const Users = await UsersCollection(db);
153
159
 
@@ -185,13 +191,13 @@ export const configureUsersModule = async ({
185
191
  when: Date;
186
192
  }> {
187
193
  if (!plainToken) return null;
188
- const token = await sha256.hash(plainToken);
194
+ const token = await sha256(plainToken);
189
195
  const user = await Users.findOne(
190
196
  {
191
197
  'services.email.verificationTokens': {
192
198
  $elemMatch: {
193
199
  token,
194
- when: { $gt: new Date().getTime() },
200
+ when: { $gt: new Date(new Date().getTime() - 1000 * 60 * 60) },
195
201
  },
196
202
  },
197
203
  },
@@ -231,13 +237,13 @@ export const configureUsersModule = async ({
231
237
  },
232
238
 
233
239
  async findUserByResetToken(plainToken: string): Promise<User> {
234
- const token = await sha256.hash(plainToken);
240
+ const token = await sha256(plainToken);
235
241
  const user = await Users.findOne(
236
242
  {
237
243
  'services.password.reset': {
238
244
  $elemMatch: {
239
245
  token,
240
- when: { $gt: new Date().getTime() },
246
+ when: { $gt: new Date(new Date().getTime() - 1000 * 60 * 60) },
241
247
  },
242
248
  },
243
249
  },
@@ -247,7 +253,7 @@ export const configureUsersModule = async ({
247
253
  },
248
254
 
249
255
  async findUserByToken(plainToken?: string): Promise<User> {
250
- const token = await sha256.hash(plainToken);
256
+ const token = await sha256(plainToken);
251
257
 
252
258
  if (token) {
253
259
  return Users.findOne({
@@ -276,7 +282,7 @@ export const configureUsersModule = async ({
276
282
  offset?: number;
277
283
  }): Promise<Array<User>> {
278
284
  const defaultSort = [{ key: 'created', value: SortDirection.ASC }] as SortOption[];
279
- const selector = buildFindSelector(query);
285
+ const selector = buildFindSelector({ ...query });
280
286
 
281
287
  if (query.queryString) {
282
288
  return Users.find(selector, {
@@ -295,9 +301,7 @@ export const configureUsersModule = async ({
295
301
  },
296
302
 
297
303
  async userExists({ userId }: { userId: string }): Promise<boolean> {
298
- const selector = generateDbFilterById<User>(userId);
299
- selector.deleted = null; // skip deleted users when checked for existance!
300
- const userCount = await Users.countDocuments(selector, { limit: 1 });
304
+ const userCount = await Users.countDocuments({ _id: userId, deleted: null }, { limit: 1 });
301
305
  return userCount === 1;
302
306
  },
303
307
 
@@ -410,7 +414,7 @@ export const configureUsersModule = async ({
410
414
  plainPassword: string,
411
415
  ): Promise<boolean> {
412
416
  if (bcryptHash) {
413
- const password = await sha256.hash(plainPassword);
417
+ const password = await sha256(plainPassword);
414
418
  return bcrypt.compare(password, bcryptHash);
415
419
  }
416
420
  if (pbkdf2SaltAndHash) {
@@ -451,9 +455,9 @@ export const configureUsersModule = async ({
451
455
  async sendResetPasswordEmail(userId: string, email: string, isEnrollment?: boolean): Promise<void> {
452
456
  const plainToken = crypto.randomUUID();
453
457
  const resetToken = {
454
- token: await sha256.hash(plainToken),
458
+ token: await sha256(plainToken),
455
459
  address: email,
456
- when: new Date().getTime() + 1000 * 60 * 60, // 1 hour
460
+ when: new Date(),
457
461
  };
458
462
 
459
463
  await Users.updateOne(
@@ -476,9 +480,9 @@ export const configureUsersModule = async ({
476
480
  async sendVerificationEmail(userId: string, email: string): Promise<void> {
477
481
  const plainToken = crypto.randomUUID();
478
482
  const verificationToken = {
479
- token: await sha256.hash(plainToken),
483
+ token: await sha256(plainToken),
480
484
  address: email,
481
- when: new Date().getTime() + 1000 * 60 * 60, // 1 hour
485
+ when: new Date(),
482
486
  };
483
487
 
484
488
  await Users.updateOne(
@@ -602,42 +606,38 @@ export const configureUsersModule = async ({
602
606
  return user;
603
607
  },
604
608
 
605
- delete: async (userId: string): Promise<User> => {
606
- const userFilter = generateDbFilterById(userId);
607
-
608
- const existingUser = await Users.findOne(userFilter, {
609
- projection: { emails: true, username: true },
610
- });
611
- if (!existingUser) return null;
612
-
613
- const uuid = crypto.randomUUID();
614
- const obfuscatedEmails = existingUser.emails?.flatMap(({ address, verified }) => {
615
- if (!verified) return [];
616
- return [
617
- {
618
- address: `${address}@${uuid}.unchained.local`,
619
- verified: true,
609
+ markDeleted: async (userId: string): Promise<User> => {
610
+ const user = await Users.findOneAndUpdate(
611
+ { _id: userId },
612
+ {
613
+ $set: {
614
+ username: `deleted-${Date.now()}`,
615
+ deleted: new Date(),
616
+ emails: [],
617
+ roles: [],
618
+ profile: null,
619
+ lastBillingAddress: null,
620
+ services: {},
621
+ pushSubscriptions: [],
622
+ avatarId: null,
623
+ initialPassword: false,
624
+ lastContact: null,
625
+ lastLogin: null,
620
626
  },
621
- ];
622
- });
623
-
624
- const obfuscatedUsername = existingUser.username ? `${existingUser.username}-${uuid}` : null;
625
-
626
- Users.updateOne(userFilter, {
627
- $set: {
628
- emails: obfuscatedEmails,
629
- username: obfuscatedUsername,
630
- services: {},
631
627
  },
632
- });
628
+ { returnDocument: 'after' },
629
+ );
633
630
 
634
- const user = await Users.findOneAndDelete(userFilter);
635
631
  await emit('USER_REMOVE', {
636
- user: removeConfidentialServiceHashes(user),
632
+ user,
637
633
  });
638
634
  return user;
639
635
  },
640
636
 
637
+ deletePermanently: async ({ userId }: { userId: string }): Promise<User> => {
638
+ return Users.findOneAndDelete({ _id: userId });
639
+ },
640
+
641
641
  updateProfile: async (
642
642
  userId: string,
643
643
  updatedData: { profile?: UserProfile; meta?: any },
@@ -680,6 +680,8 @@ export const configureUsersModule = async ({
680
680
  const userFilter = generateDbFilterById(_id);
681
681
  const user = await Users.findOne(userFilter, {});
682
682
 
683
+ if (!lastBillingAddress) return user;
684
+
683
685
  const modifier = {
684
686
  $set: {
685
687
  lastBillingAddress,
@@ -702,9 +704,12 @@ export const configureUsersModule = async ({
702
704
  returnDocument: 'after',
703
705
  });
704
706
 
705
- await emit('USER_UPDATE_BILLING_ADDRESS', {
706
- user: removeConfidentialServiceHashes(user),
707
- });
707
+ if (updatedUser) {
708
+ await emit('USER_UPDATE_BILLING_ADDRESS', {
709
+ user: removeConfidentialServiceHashes(updatedUser),
710
+ });
711
+ }
712
+
708
713
  return updatedUser;
709
714
  },
710
715
 
@@ -1,4 +1,3 @@
1
- /// <reference lib="dom" />
2
1
  import { ModuleInput } from '@unchainedshop/mongodb';
3
2
  import {
4
3
  Fido2Lib,
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { TimestampFields, Address, Contact, mongodb } from '@unchainedshop/mongodb';
1
+ import { TimestampFields, Address, Contact, mongodb } from '@unchainedshop/mongodb';
2
+ import { DateFilterInput } from '@unchainedshop/utils';
2
3
 
3
4
  export interface PushSubscription {
4
5
  _id: string;
@@ -55,7 +56,7 @@ export interface PushSubscriptionObject {
55
56
  }
56
57
 
57
58
  export type User = {
58
- _id?: string;
59
+ _id: string;
59
60
  deleted?: Date;
60
61
  avatarId?: string;
61
62
  emails: Array<Email>;
@@ -76,6 +77,8 @@ export type User = {
76
77
  export type UserQuery = mongodb.Filter<User> & {
77
78
  includeGuests?: boolean;
78
79
  queryString?: string;
80
+ emailVerified?: boolean;
81
+ lastLogin?: DateFilterInput;
79
82
  };
80
83
 
81
84
  export interface WebAuthnCredentialsCreationRequest {
@@ -1,4 +1,3 @@
1
1
  export * from './types.js';
2
2
  export * from './module/configureUsersModule.js';
3
- export * from './services/userServices.js';
4
3
  export * from './users-settings.js';
@@ -2,7 +2,7 @@ export default {
2
2
  _id: 'PKve0k9fLCUzn2EUi',
3
3
  guest: false,
4
4
  initialPassword: false,
5
- lastBillingAddress: {},
5
+ lastBillingAddress: null,
6
6
  profile: {
7
7
  address: {
8
8
  firstName: null,
@@ -65,9 +65,8 @@ export default {
65
65
  remotePort: '42978',
66
66
  userAgent:
67
67
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
68
- locale: 'de_CH',
68
+ locale: 'de-CH',
69
69
  countryCode: 'CH',
70
70
  },
71
71
  updated: new Date('2022-11-30T11:02:19.624Z'),
72
- updatedBy: 'PKve0k9fLCUzn2EUi',
73
72
  };
package/tsconfig.json CHANGED
@@ -1,11 +1,9 @@
1
1
  {
2
2
  "extends": "../shared/base.tsconfig.json",
3
3
  "compilerOptions": {
4
- "rootDir": "src",
5
- "outDir": "lib",
4
+ "declarationDir": "./lib",
5
+ "rootDir": "./src",
6
+ "outDir": "./lib"
6
7
  },
7
- "exclude": ["**/*.test.ts", "**/*.test.js", "tests"],
8
- "include": [
9
- "./src"
10
- ]
11
- }
8
+ "exclude": ["**/*.test.ts", "**/*.test.js", "tests", "lib"]
9
+ }
@@ -1,4 +0,0 @@
1
- import { UnchainedCore } from '@unchainedshop/core';
2
- export type MigrateUserDataService = (userIdBeforeLogin: any, userId: any, unchainedAPI: UnchainedCore) => Promise<void>;
3
- export declare const migrateUserDataService: MigrateUserDataService;
4
- //# sourceMappingURL=migrateUserDataService.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"migrateUserDataService.d.ts","sourceRoot":"","sources":["../../src/services/migrateUserDataService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,MAAM,MAAM,sBAAsB,GAAG,CACnC,iBAAiB,KAAA,EACjB,MAAM,KAAA,EACN,YAAY,EAAE,aAAa,KACxB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,eAAO,MAAM,sBAAsB,EAAE,sBA2BpC,CAAC"}
@@ -1,18 +0,0 @@
1
- import { userSettings } from '../users-settings.js';
2
- export const migrateUserDataService = async (userIdBeforeLogin, userId, unchainedAPI) => {
3
- const user = await unchainedAPI.modules.users.findUserById(userId);
4
- const userBeforeLogin = await unchainedAPI.modules.users.findUserById(userIdBeforeLogin);
5
- await unchainedAPI.services.orders.migrateOrderCarts({
6
- fromUserId: userIdBeforeLogin,
7
- toUserId: userId,
8
- shouldMerge: userSettings.mergeUserCartsOnLogin,
9
- countryContext: userBeforeLogin.lastLogin?.countryCode || user.lastLogin?.countryCode,
10
- }, unchainedAPI);
11
- await unchainedAPI.services.bookmarks.migrateBookmarks({
12
- fromUserId: userIdBeforeLogin,
13
- toUserId: userId,
14
- shouldMerge: userSettings.mergeUserCartsOnLogin,
15
- countryContext: userBeforeLogin.lastLogin?.countryCode || user.lastLogin?.countryCode,
16
- }, unchainedAPI);
17
- };
18
- //# sourceMappingURL=migrateUserDataService.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"migrateUserDataService.js","sourceRoot":"","sources":["../../src/services/migrateUserDataService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAQpD,MAAM,CAAC,MAAM,sBAAsB,GAA2B,KAAK,EACjE,iBAAiB,EACjB,MAAM,EACN,YAAY,EACZ,EAAE;IACF,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACnE,MAAM,eAAe,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAEzF,MAAM,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,CAClD;QACE,UAAU,EAAE,iBAAiB;QAC7B,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,YAAY,CAAC,qBAAqB;QAC/C,cAAc,EAAE,eAAe,CAAC,SAAS,EAAE,WAAW,IAAI,IAAI,CAAC,SAAS,EAAE,WAAW;KACtF,EACD,YAAY,CACb,CAAC;IAEF,MAAM,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CACpD;QACE,UAAU,EAAE,iBAAiB;QAC7B,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,YAAY,CAAC,qBAAqB;QAC/C,cAAc,EAAE,eAAe,CAAC,SAAS,EAAE,WAAW,IAAI,IAAI,CAAC,SAAS,EAAE,WAAW;KACtF,EACD,YAAY,CACb,CAAC;AACJ,CAAC,CAAC"}
@@ -1,15 +0,0 @@
1
- import { File, FileServices, FilesModule } from '@unchainedshop/core-files';
2
- import { UsersModule } from '../users-index.js';
3
- export type UpdateUserAvatarAfterUploadService = (params: {
4
- file: File;
5
- }, context: {
6
- modules: {
7
- files: FilesModule;
8
- users: UsersModule;
9
- };
10
- services: {
11
- files: FileServices;
12
- };
13
- }) => Promise<void>;
14
- export declare const updateUserAvatarAfterUploadService: UpdateUserAvatarAfterUploadService;
15
- //# sourceMappingURL=updateUserAvatarAfterUploadService.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"updateUserAvatarAfterUploadService.d.ts","sourceRoot":"","sources":["../../src/services/updateUserAvatarAfterUploadService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,MAAM,kCAAkC,GAAG,CAC/C,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,EACtB,OAAO,EAAE;IACP,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAC;IACpD,QAAQ,EAAE;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,CAAC;CACnC,KACE,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,eAAO,MAAM,kCAAkC,EAAE,kCA6BhD,CAAC"}
@@ -1,24 +0,0 @@
1
- import { log, LogLevel } from '@unchainedshop/logger';
2
- export const updateUserAvatarAfterUploadService = async ({ file }, context) => {
3
- const { modules, services } = context;
4
- const { userId } = file.meta;
5
- const files = await modules.files.findFiles({
6
- _id: { $ne: file._id },
7
- path: file.path,
8
- 'meta.userId': userId,
9
- });
10
- const fileIds = files.map((f) => f._id);
11
- try {
12
- if (fileIds?.length) {
13
- await services.files.removeFiles({
14
- fileIds,
15
- }, context);
16
- }
17
- }
18
- catch (e) {
19
- // cleanup error, not critical
20
- log(`could not clean up all old avatars: ${e.message}`, { level: LogLevel.Warning });
21
- }
22
- await modules.users.updateAvatar(userId, file._id);
23
- };
24
- //# sourceMappingURL=updateUserAvatarAfterUploadService.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"updateUserAvatarAfterUploadService.js","sourceRoot":"","sources":["../../src/services/updateUserAvatarAfterUploadService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAWtD,MAAM,CAAC,MAAM,kCAAkC,GAAuC,KAAK,EACzF,EAAE,IAAI,EAAE,EACR,OAAO,EACP,EAAE;IACF,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IACtC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,IAA0B,CAAC;IAEnD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAC1C,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,aAAa,EAAE,MAAM;KACtB,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAExC,IAAI,CAAC;QACH,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,MAAM,QAAQ,CAAC,KAAK,CAAC,WAAW,CAC9B;gBACE,OAAO;aACR,EACD,OAAO,CACR,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,8BAA8B;QAC9B,GAAG,CAAC,uCAAwC,CAAW,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AACrD,CAAC,CAAC"}
@@ -1,8 +0,0 @@
1
- import { UpdateUserAvatarAfterUploadService } from './updateUserAvatarAfterUploadService.js';
2
- import { MigrateUserDataService } from './migrateUserDataService.js';
3
- export interface UserServices {
4
- updateUserAvatarAfterUpload: UpdateUserAvatarAfterUploadService;
5
- migrateUserData: MigrateUserDataService;
6
- }
7
- export declare const userServices: UserServices;
8
- //# sourceMappingURL=userServices.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"userServices.d.ts","sourceRoot":"","sources":["../../src/services/userServices.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,kCAAkC,EACnC,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAA0B,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE7F,MAAM,WAAW,YAAY;IAC3B,2BAA2B,EAAE,kCAAkC,CAAC;IAChE,eAAe,EAAE,sBAAsB,CAAC;CACzC;AAED,eAAO,MAAM,YAAY,EAAE,YAG1B,CAAC"}
@@ -1,7 +0,0 @@
1
- import { updateUserAvatarAfterUploadService, } from './updateUserAvatarAfterUploadService.js';
2
- import { migrateUserDataService } from './migrateUserDataService.js';
3
- export const userServices = {
4
- updateUserAvatarAfterUpload: updateUserAvatarAfterUploadService,
5
- migrateUserData: migrateUserDataService,
6
- };
7
- //# sourceMappingURL=userServices.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"userServices.js","sourceRoot":"","sources":["../../src/services/userServices.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,GAEnC,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAA0B,MAAM,6BAA6B,CAAC;AAO7F,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,2BAA2B,EAAE,kCAAkC;IAC/D,eAAe,EAAE,sBAAsB;CACxC,CAAC"}
@@ -1,5 +0,0 @@
1
- export const hash = async (message) => {
2
- const bytes = new TextEncoder().encode(message);
3
- const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
4
- return Buffer.from(hashBuffer).toString('hex');
5
- };
@@ -1,37 +0,0 @@
1
- import { UnchainedCore } from '@unchainedshop/core';
2
- import { userSettings } from '../users-settings.js';
3
-
4
- export type MigrateUserDataService = (
5
- userIdBeforeLogin,
6
- userId,
7
- unchainedAPI: UnchainedCore,
8
- ) => Promise<void>;
9
-
10
- export const migrateUserDataService: MigrateUserDataService = async (
11
- userIdBeforeLogin,
12
- userId,
13
- unchainedAPI,
14
- ) => {
15
- const user = await unchainedAPI.modules.users.findUserById(userId);
16
- const userBeforeLogin = await unchainedAPI.modules.users.findUserById(userIdBeforeLogin);
17
-
18
- await unchainedAPI.services.orders.migrateOrderCarts(
19
- {
20
- fromUserId: userIdBeforeLogin,
21
- toUserId: userId,
22
- shouldMerge: userSettings.mergeUserCartsOnLogin,
23
- countryContext: userBeforeLogin.lastLogin?.countryCode || user.lastLogin?.countryCode,
24
- },
25
- unchainedAPI,
26
- );
27
-
28
- await unchainedAPI.services.bookmarks.migrateBookmarks(
29
- {
30
- fromUserId: userIdBeforeLogin,
31
- toUserId: userId,
32
- shouldMerge: userSettings.mergeUserCartsOnLogin,
33
- countryContext: userBeforeLogin.lastLogin?.countryCode || user.lastLogin?.countryCode,
34
- },
35
- unchainedAPI,
36
- );
37
- };