• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / server / #8050

16 Aug 2024 11:21AM UTC coverage: 13.92%. First build
#8050

Pull #4411

travis-ci

Pull Request #4411: Type added to authorization policy entity

80 of 4158 branches covered (1.92%)

Branch coverage included in aggregate %.

61 of 116 new or added lines in 50 files covered. (52.59%)

1945 of 10389 relevant lines covered (18.72%)

3.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.34
/src/domain/community/user/user.service.ts
1
import { LogContext, ProfileType } from '@common/enums';
28✔
2
import {
28✔
3
  EntityNotFoundException,
4
  ForbiddenException,
5
  RelationshipNotFoundException,
6
  UserAlreadyRegisteredException,
7
  UserRegistrationInvalidEmail,
8
  ValidationException,
28✔
9
} from '@common/exceptions';
10
import { FormatNotSupportedException } from '@common/exceptions/format.not.supported.exception';
11
import { AgentInfo } from '@core/authentication.agent.info/agent.info';
12
import { AgentService } from '@domain/agent/agent/agent.service';
13
import { AuthorizationPolicy } from '@domain/common/authorization-policy';
14
import { RoomService } from '@domain/communication/room/room.service';
15
import { ProfileService } from '@domain/common/profile/profile.service';
16
import {
17
  CreateUserInput,
28✔
18
  DeleteUserInput,
19
  UpdateUserInput,
20
} from '@domain/community/user';
28✔
21
import { Inject, Injectable, LoggerService } from '@nestjs/common';
22
import { CACHE_MANAGER } from '@nestjs/cache-manager';
28✔
23
import { InjectRepository } from '@nestjs/typeorm';
24
import { CommunicationAdapter } from '@services/adapters/communication-adapter/communication.adapter';
28✔
25
import { Cache, CachingConfig } from 'cache-manager';
28✔
26
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
28✔
27
import { FindOneOptions, Repository } from 'typeorm';
28
import { DirectRoomResult } from '../../communication/communication/dto/communication.dto.send.direct.message.user.result';
29
import { NamingService } from '@services/infrastructure/naming/naming.service';
30
import { limitAndShuffle } from '@common/utils/limitAndShuffle';
31
import { IProfile } from '@domain/common/profile/profile.interface';
32
import { PaginationArgs } from '@core/pagination';
33
import { applyUserFilter } from '@core/filtering/filters';
28✔
34
import { UserFilterInput } from '@core/filtering/input-types';
28✔
35
import { getPaginationResults } from '@core/pagination/pagination.fn';
28✔
36
import { IPaginatedType } from '@core/pagination/paginated.type';
28✔
37
import { CreateProfileInput } from '@domain/common/profile/dto/profile.dto.create';
38
import { validateEmail } from '@common/utils';
28✔
39
import { RoleSetRoleSelectionCredentials } from '../../access/role-set/dto/role.set.dto.role.selection.credentials';
28✔
40
import { RoleSetRoleWithParentCredentials } from '../../access/role-set/dto/role.set.dto.role.with.parent.credentials';
41
import { TagsetReservedName } from '@common/enums/tagset.reserved.name';
28✔
42
import { contributorDefaults } from '../contributor/contributor.defaults';
28✔
43
import { UsersQueryArgs } from './dto/users.query.args';
28✔
44
import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service';
45
import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface';
28✔
46
import { StorageAggregatorService } from '@domain/storage/storage-aggregator/storage.aggregator.service';
47
import { UpdateUserPlatformSettingsInput } from './dto/user.dto.update.platform.settings';
48
import { IAccount } from '@domain/space/account/account.interface';
49
import { User } from './user.entity';
28✔
50
import { IUser } from './user.interface';
51
import { StorageAggregatorType } from '@common/enums/storage.aggregator.type';
28✔
52
import { AgentType } from '@common/enums/agent.type';
53
import { ContributorService } from '../contributor/contributor.service';
54
import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type';
28✔
55
import { AccountType } from '@common/enums/account.type';
28✔
56
import { KratosService } from '@services/infrastructure/kratos/kratos.service';
57
import { IRoom } from '@domain/communication/room/room.interface';
58
import { RoomType } from '@common/enums/room.type';
28✔
59
import { UserSettingsService } from '../user-settings/user.settings.service';
28✔
60
import { UpdateUserSettingsEntityInput } from '../user-settings/dto/user.settings.dto.update';
28✔
61
import { AccountLookupService } from '@domain/space/account.lookup/account.lookup.service';
62
import { AccountHostService } from '@domain/space/account.host/account.host.service';
28✔
63
import { RoomLookupService } from '@domain/communication/room-lookup/room.lookup.service';
64
import { UserLookupService } from '../user-lookup/user.lookup.service';
28✔
65
import { AgentInfoCacheService } from '@core/authentication.agent.info/agent.info.cache.service';
28✔
66
import { VisualType } from '@common/enums/visual.type';
28✔
67
import { InstrumentService } from '@src/apm/decorators';
68
import { CreateUserSettingsInput } from '../user-settings/dto/user.settings.dto.create';
28✔
69

28✔
70
@InstrumentService()
28✔
71
@Injectable()
72
export class UserService {
73
  cacheOptions: CachingConfig = { ttl: 300 };
28✔
74

1✔
75
  constructor(
76
    private profileService: ProfileService,
77
    private communicationAdapter: CommunicationAdapter,
1✔
78
    private roomService: RoomService,
1✔
79
    private roomLookupService: RoomLookupService,
1✔
80
    private namingService: NamingService,
1✔
81
    private agentService: AgentService,
1✔
82
    private agentInfoCacheService: AgentInfoCacheService,
1✔
83
    private authorizationPolicyService: AuthorizationPolicyService,
1✔
84
    private storageAggregatorService: StorageAggregatorService,
1✔
85
    private accountLookupService: AccountLookupService,
1✔
86
    private userLookupService: UserLookupService,
1✔
87
    private accountHostService: AccountHostService,
88
    private userSettingsService: UserSettingsService,
1✔
89
    private contributorService: ContributorService,
90
    private kratosService: KratosService,
1✔
91
    @InjectRepository(User)
1✔
92
    private userRepository: Repository<User>,
93
    @Inject(WINSTON_MODULE_NEST_PROVIDER)
94
    private readonly logger: LoggerService,
95
    @Inject(CACHE_MANAGER) private readonly cacheManager: Cache
×
96
  ) {}
97

98
  private getUserCommunicationIdCacheKey(communicationId: string): string {
99
    return `@user:communicationId:${communicationId}`;
×
100
  }
101

102
  private async setUserCache(user: IUser) {
103
    await this.cacheManager.set(
104
      this.getUserCommunicationIdCacheKey(user.email),
105
      user,
106
      this.cacheOptions
×
107
    );
108
  }
109

110
  private async clearUserCache(user: IUser) {
111
    await this.cacheManager.del(
112
      this.getUserCommunicationIdCacheKey(user.communicationID)
×
113
    );
114
    await this.agentInfoCacheService.deleteAgentInfoFromCache(user.email);
×
115
  }
×
116

117
  async createUser(
×
118
    userData: CreateUserInput,
119
    agentInfo?: AgentInfo
120
  ): Promise<IUser> {
×
121
    if (userData.nameID) {
122
      // Convert nameID to lower case
×
NEW
123
      userData.nameID = userData.nameID.toLowerCase();
×
124
      await this.isUserNameIdAvailableOrFail(userData.nameID);
125
    } else {
×
126
      userData.nameID = await this.createUserNameID(userData);
127
    }
128

×
129
    await this.validateUserProfileCreationRequest(userData);
130

131
    let user: IUser = User.create({
132
      ...userData,
×
133
      accountUpn: userData.accountUpn ?? userData.email,
134
    });
135
    user.authorization = new AuthorizationPolicy(AuthorizationPolicyType.USER);
136
    user.settings = this.userSettingsService.createUserSettings(
137
      this.getDefaultUserSettings()
138
    );
139

×
140
    if (!user.serviceProfile) {
×
141
      user.serviceProfile = false;
×
142
    }
143

144
    const profileData = await this.extendProfileDataWithReferences(
145
      userData.profileData
146
    );
×
147
    user.storageAggregator =
148
      await this.storageAggregatorService.createStorageAggregator(
149
        StorageAggregatorType.USER
150
      );
151
    // Do not create the guidance room here, it will be created on demand
×
152

153
    user.profile = await this.profileService.createProfile(
154
      profileData,
155
      ProfileType.USER,
×
156
      user.storageAggregator
157
    );
158

159
    await this.profileService.addOrUpdateTagsetOnProfile(user.profile, {
160
      name: TagsetReservedName.SKILLS,
×
161
      tags: [],
162
    });
163
    await this.profileService.addOrUpdateTagsetOnProfile(user.profile, {
164
      name: TagsetReservedName.KEYWORDS,
×
165
      tags: [],
166
    });
167
    await this.contributorService.addAvatarVisualToContributorProfile(
168
      user.profile,
169
      userData.profileData,
×
170
      agentInfo,
171
      userData.firstName,
172
      userData.lastName
173
    );
174

×
175
    user.agent = await this.agentService.createAgent({
176
      type: AgentType.USER,
177
    });
178

179
    this.logger.verbose?.(
×
180
      `Created a new user with email: ${user.email}`,
181
      LogContext.COMMUNITY
×
182
    );
×
183

×
184
    const account = await this.accountHostService.createAccount(
185
      AccountType.USER
186
    );
×
187
    user.accountID = account.id;
188

189
    user = await this.save(user);
×
190

191
    await this.contributorService.ensureAvatarIsStoredInLocalStorageBucket(
×
192
      user.profile.id,
×
193
      user.id
194
    );
×
195
    // Reload to ensure have the updated avatar URL
196
    user = await this.getUserOrFail(user.id);
197

×
198
    // all users need to be registered for communications at the absolute beginning
199
    // there are cases where a user could be messaged before they actually log-in
200
    // which will result in failure in communication (either missing user or unsent messages)
×
201
    // register the user asynchronously - we don't want to block the creation operation
202
    const communicationID = await this.communicationAdapter.tryRegisterNewUser(
×
203
      user.email
204
    );
205

206
    try {
207
      if (!communicationID) {
208
        this.logger.warn(
209
          `User registration failed on user creation ${user.id}.`
×
210
        );
×
211
        return user;
×
212
      }
213

214
      user.communicationID = communicationID;
215

216
      await this.save(user);
×
217
      await this.setUserCache(user);
×
218
    } catch (e: any) {
219
      this.logger.error(e, e?.stack, LogContext.USER);
220
    }
×
221

×
222
    await this.setUserCache(user);
×
223

×
224
    return user;
225
  }
×
226

227
  private getDefaultUserSettings(): CreateUserSettingsInput {
228
    const settings: CreateUserSettingsInput = {
×
229
      communication: {
×
230
        allowOtherUsersToSendMessages: true,
231
      },
232
      privacy: {
233
        // Note: not currently used but will be near term.
234
        contributionRolesPubliclyVisible: true,
×
235
      },
236
      notification: {
237
        organization: {
238
          adminMessageReceived: { email: true, inApp: true },
239
          adminMentioned: { email: true, inApp: true },
×
240
        },
241
        platform: {
242
          forumDiscussionCreated: { email: true, inApp: false },
243
          forumDiscussionComment: { email: true, inApp: true },
×
244
          admin: {
×
245
            userProfileCreated: { email: false, inApp: false },
×
246
            userProfileRemoved: { email: false, inApp: false },
247
            spaceCreated: { email: false, inApp: false },
248
            userGlobalRoleChanged: { email: false, inApp: false },
249
          },
250
        },
×
251
        space: {
252
          admin: {
253
            communityApplicationReceived: { email: true, inApp: true },
254
            communityNewMember: { email: true, inApp: true },
×
255
            communicationMessageReceived: { email: true, inApp: true },
256
            collaborationCalloutContributionCreated: {
257
              email: false,
258
              inApp: true,
259
            },
×
260
          },
×
261
          communicationUpdates: { email: true, inApp: true },
262
          collaborationCalloutContributionCreated: {
×
263
            email: false,
×
264
            inApp: true,
×
265
          },
×
266
          collaborationCalloutPostContributionComment: {
267
            email: false,
×
268
            inApp: true,
269
          },
270
          collaborationCalloutComment: { email: false, inApp: true },
271
          collaborationCalloutPublished: { email: true, inApp: true },
×
272
        },
273
        user: {
274
          mentioned: { email: true, inApp: true },
275
          commentReply: { email: false, inApp: true },
×
276
          messageReceived: { email: true, inApp: true },
277
          membership: {
278
            spaceCommunityInvitationReceived: { email: true, inApp: true },
279
            spaceCommunityJoined: { email: true, inApp: true },
×
280
          },
281
        },
×
282
        virtualContributor: {
×
283
          adminSpaceCommunityInvitation: { email: true, inApp: true },
×
284
        },
×
285
      },
286
    };
×
287
    return settings;
288
  }
289

290
  public async updateUserSettings(
×
291
    user: IUser,
292
    settingsData: UpdateUserSettingsEntityInput
293
  ): Promise<IUser> {
294
    user.settings = this.userSettingsService.updateSettings(
295
      user.settings,
×
296
      settingsData
297
    );
×
298

299
    return await this.save(user);
300
  }
301

302
  private async extendProfileDataWithReferences(
×
303
    profileData?: CreateProfileInput
×
304
  ): Promise<CreateProfileInput> {
×
305
    // ensure the result + references are there
306
    let result = profileData;
307
    if (!result) {
308
      result = {
309
        referencesData: [],
×
310
        displayName: '',
×
311
      };
312
    }
313
    if (!result.referencesData) {
314
      result.referencesData = [];
315
    }
×
316
    // Get the template to populate with
317
    const referenceTemplates = contributorDefaults.references;
318
    if (referenceTemplates) {
319
      for (const referenceTemplate of referenceTemplates) {
×
320
        const existingRef = result.referencesData?.find(
321
          reference =>
322
            reference.name.toLowerCase() ===
323
            referenceTemplate.name.toLowerCase()
324
        );
325
        if (!existingRef) {
×
326
          const newRefData = {
327
            name: referenceTemplate.name,
328
            uri: referenceTemplate.uri,
329
            description: referenceTemplate.description,
×
330
          };
331
          result.referencesData?.push(newRefData);
332
        }
333
      }
334
    }
335

336
    return result;
337
  }
338

339
  async createUserFromAgentInfo(agentInfo: AgentInfo): Promise<IUser> {
340
    // Extra check that there is valid data + no user with the email
341
    const email = agentInfo.email;
342
    if (!email || email.length === 0) {
343
      throw new UserRegistrationInvalidEmail(
344
        `Invalid email provided: ${email}`
345
      );
346
    }
347

348
    if (await this.userLookupService.isRegisteredUser(email)) {
349
      throw new UserAlreadyRegisteredException(
350
        `User with email: ${email} already registered`
351
      );
352
    }
353

354
    const userData: CreateUserInput = {
355
      email: email,
356
      firstName: agentInfo.firstName,
357
      lastName: agentInfo.lastName,
358
      accountUpn: email,
359
      profileData: {
360
        visuals: [
×
361
          {
362
            name: VisualType.AVATAR,
363
            uri: agentInfo.avatarURL,
364
          },
365
        ],
366
        displayName: `${agentInfo.firstName} ${agentInfo.lastName}`,
×
367
      },
×
368
    };
×
369

370
    return await this.createUser(userData, agentInfo);
371
  }
372

373
  private async validateUserProfileCreationRequest(
×
374
    userData: CreateUserInput
×
375
  ): Promise<boolean> {
376
    const userCheck = await this.userLookupService.isRegisteredUser(
377
      userData.email
378
    );
×
379
    if (userCheck)
380
      throw new ValidationException(
381
        `User profile with the specified email (${userData.email}) already exists`,
×
382
        LogContext.COMMUNITY
×
383
      );
384
    // Trim values to remove space issues
385
    userData.email = userData.email.trim();
386
    return true;
387
  }
388

389
  private async isUserNameIdAvailableOrFail(nameID: string) {
×
390
    const userCount = await this.userRepository.countBy({
×
391
      nameID: nameID,
392
    });
393
    if (userCount != 0)
394
      throw new ValidationException(
395
        `The provided nameID is already taken: ${nameID}`,
×
396
        LogContext.COMMUNITY
397
      );
398
  }
399

400
  async deleteUser(deleteData: DeleteUserInput): Promise<IUser> {
401
    const userID = deleteData.ID;
×
402
    const user = await this.getUserOrFail(userID, {
×
403
      relations: {
404
        profile: true,
405
        agent: true,
406
        storageAggregator: true,
407
        guidanceRoom: true,
408
        settings: true,
409
      },
410
    });
411

×
412
    if (
×
413
      !user.profile ||
×
414
      !user.storageAggregator ||
415
      !user.agent ||
416
      !user.authorization ||
417
      !user.settings
418
    ) {
×
419
      throw new RelationshipNotFoundException(
420
        `User entity missing required child entities when deleting: ${userID}`,
×
421
        LogContext.COMMUNITY
422
      );
×
423
    }
×
424

425
    // TODO: give additional feedback?
426
    const accountHasResources =
×
427
      await this.accountLookupService.areResourcesInAccount(user.accountID);
×
428
    if (accountHasResources) {
429
      throw new ForbiddenException(
430
        'Unable to delete User: account contains one or more resources',
431
        LogContext.SPACES
432
      );
×
433
    }
×
434
    const { id } = user;
435

436
    await this.clearUserCache(user);
×
437

×
438
    await this.profileService.deleteProfile(user.profile.id);
439

440
    await this.agentService.deleteAgent(user.agent.id);
×
441

×
442
    await this.authorizationPolicyService.delete(user.authorization);
443

444
    await this.storageAggregatorService.delete(user.storageAggregator.id);
×
445

446
    await this.userSettingsService.deleteUserSettings(user.settings.id);
447

448
    if (user.guidanceRoom) {
×
449
      await this.roomService.deleteRoom(user.guidanceRoom);
450
    }
451

452
    if (deleteData.deleteIdentity) {
453
      await this.kratosService.deleteIdentityByEmail(user.email);
454
    }
455

×
456
    const result = await this.userRepository.remove(user as User);
457

458
    // Note: Should we unregister the user from communications?
459

460
    return {
461
      ...result,
462
      id,
463
    };
×
464
  }
×
465

466
  public async getAccount(user: IUser): Promise<IAccount> {
467
    return await this.accountLookupService.getAccountOrFail(user.accountID);
468
  }
469

470
  async getUserOrFail(
×
471
    userID: string,
472
    options?: FindOneOptions<User>
473
  ): Promise<IUser | never> {
474
    if (userID === '') {
475
      throw new EntityNotFoundException(
476
        `No userID provided: ${userID}`,
477
        LogContext.COMMUNITY
×
478
      );
×
479
    }
480
    const user = await this.userLookupService.getUserByUUID(userID, options);
481

482
    if (!user) {
483
      throw new EntityNotFoundException(
×
484
        `Unable to find user with given ID: ${userID}`,
485
        LogContext.COMMUNITY
×
486
      );
×
487
    }
488

489
    return user;
490
  }
491

492
  async getUserByEmail(
493
    email: string,
494
    options?: FindOneOptions<User>
495
  ): Promise<IUser | never | null> {
×
496
    if (!validateEmail(email)) {
×
497
      throw new FormatNotSupportedException(
498
        `Incorrect format of the user email: ${email}`,
×
499
        LogContext.COMMUNITY
×
500
      );
501
    }
502

503
    return this.userRepository.findOne({
×
504
      where: { email: email },
505
      ...options,
506
    });
×
507
  }
508

×
509
  async save(user: IUser): Promise<IUser> {
510
    return await this.userRepository.save(user);
511
  }
×
512

513
  async getUsersForQuery(args: UsersQueryArgs): Promise<IUser[]> {
514
    const limit = args.limit;
×
515
    const shuffle = args.shuffle || false;
516

517
    this.logger.verbose?.(
518
      `Querying all users with limit: ${limit} and shuffle: ${shuffle}`,
519
      LogContext.COMMUNITY
520
    );
521
    const credentialsFilter = args.filter?.credentials;
×
522
    let users: User[] = [];
523
    if (credentialsFilter) {
×
524
      users = await this.userRepository
×
525
        .createQueryBuilder('user')
526
        .leftJoinAndSelect('user.agent', 'agent')
527
        .leftJoinAndSelect('agent.credentials', 'credential')
528
        .where('credential.type IN (:credentialsFilter)')
529
        .setParameters({
530
          credentialsFilter: credentialsFilter,
×
531
        })
532
        .getMany();
×
533
    } else {
534
      users = await this.userRepository.findBy({ serviceProfile: false });
535
    }
536

537
    if (args.IDs) {
538
      users = users.filter(user => args.IDs?.includes(user.id));
539
    }
540

541
    return limitAndShuffle(users, limit, shuffle);
×
542
  }
×
543

544
  async getPaginatedUsers(
545
    paginationArgs: PaginationArgs,
546
    withTags?: boolean,
547
    filter?: UserFilterInput
548
  ): Promise<IPaginatedType<IUser>> {
549
    const qb = this.userRepository.createQueryBuilder('user');
×
550

551
    if (withTags !== undefined) {
552
      qb.leftJoin('user.profile', 'profile')
553
        .leftJoin('tagset', 'tagset', 'profile.id = tagset.profileId')
×
554
        // cannot use object or operators here
×
555
        // because typeorm cannot construct the query properly
556
        .where(`tagset.tags ${withTags ? '!=' : '='} ''`);
557
    }
558

559
    if (filter) {
560
      applyUserFilter(qb, filter);
561
    }
×
562

×
563
    return getPaginationResults(qb, paginationArgs);
564
  }
565

566
  public async getPaginatedAvailableEntryRoleUsers(
567
    entryRoleCredentials: RoleSetRoleWithParentCredentials,
568
    paginationArgs: PaginationArgs,
×
569
    filter?: UserFilterInput
570
  ): Promise<IPaginatedType<IUser>> {
571
    const currentEntryRoleUsers =
572
      await this.userLookupService.usersWithCredential(
573
        entryRoleCredentials.role
574
      );
×
575
    const qb = this.userRepository.createQueryBuilder('user').select();
×
576

577
    if (entryRoleCredentials.parentRoleSetRole) {
×
578
      qb.leftJoin('user.agent', 'agent')
×
579
        .leftJoin('agent.credentials', 'credential')
580
        .addSelect(['credential.type', 'credential.resourceID'])
581
        .where('credential.type = :type')
582
        .andWhere('credential.resourceID = :resourceID')
×
583
        .setParameters({
584
          type: entryRoleCredentials.parentRoleSetRole.type,
585
          resourceID: entryRoleCredentials.parentRoleSetRole.resourceID,
×
586
        });
587
    }
×
588

×
589
    if (currentEntryRoleUsers.length > 0) {
590
      const hasWhere =
591
        qb.expressionMap.wheres && qb.expressionMap.wheres.length > 0;
×
592

593
      qb[hasWhere ? 'andWhere' : 'where'](
594
        'NOT user.id IN (:memberUsers)'
595
      ).setParameters({
×
596
        memberUsers: currentEntryRoleUsers.map(user => user.id),
597
      });
598
    }
599

×
600
    if (filter) {
×
601
      applyUserFilter(qb, filter);
×
602
    }
603

604
    return getPaginationResults(qb, paginationArgs);
605
  }
606

607
  public async getPaginatedAvailableElevatedRoleUsers(
×
608
    roleSetCredentials: RoleSetRoleSelectionCredentials,
609
    paginationArgs: PaginationArgs,
610
    filter?: UserFilterInput
611
  ): Promise<IPaginatedType<IUser>> {
×
612
    const currentElevatedRoleUsers =
×
613
      await this.userLookupService.usersWithCredential(
614
        roleSetCredentials.elevatedRole
615
      );
616
    const qb = this.userRepository
617
      .createQueryBuilder('user')
×
618
      .select()
619
      .leftJoin('user.agent', 'agent')
620
      .leftJoin('agent.credentials', 'credential')
621
      .addSelect(['credential.type', 'credential.resourceID'])
622
      .where('credential.type = :type')
623
      .andWhere('credential.resourceID = :resourceID')
×
624
      .setParameters({
625
        type: roleSetCredentials.entryRole.type,
626
        resourceID: roleSetCredentials.entryRole.resourceID,
627
      });
×
628

×
629
    if (currentElevatedRoleUsers.length > 0) {
630
      qb.andWhere('NOT user.id IN (:leadUsers)').setParameters({
631
        leadUsers: currentElevatedRoleUsers.map(user => user.id),
632
      });
633
    }
×
634

635
    if (filter) {
636
      applyUserFilter(qb, filter);
637
    }
×
638

639
    return getPaginationResults(qb, paginationArgs);
640
  }
641

×
642
  async updateUser(userInput: UpdateUserInput): Promise<IUser> {
×
643
    const user = await this.getUserOrFail(userInput.ID, {
644
      relations: { profile: true },
645
    });
646

647
    if (userInput.nameID) {
×
648
      if (userInput.nameID.toLowerCase() !== user.nameID.toLowerCase()) {
649
        // new NameID, check for uniqueness
650
        await this.isUserNameIdAvailableOrFail(userInput.nameID);
651
        user.nameID = userInput.nameID;
×
652
      }
×
653
    }
654

×
655
    if (userInput.firstName !== undefined) {
656
      user.firstName = userInput.firstName;
657
    }
658
    if (userInput.lastName !== undefined) {
×
659
      user.lastName = userInput.lastName;
×
660
    }
×
661
    if (userInput.phone !== undefined) {
×
662
      user.phone = userInput.phone;
663
    }
664

665
    if (userInput.serviceProfile !== undefined) {
666
      user.serviceProfile = userInput.serviceProfile;
667
    }
668

669
    if (userInput.profileData) {
670
      user.profile = await this.profileService.updateProfile(
671
        user.profile,
×
672
        userInput.profileData
673
      );
674
    }
×
675

×
676
    const response = await this.save(user);
677
    await this.setUserCache(response);
678
    return response;
×
679
  }
680

681
  public async updateUserPlatformSettings(
682
    updateData: UpdateUserPlatformSettingsInput
683
  ): Promise<IUser> {
684
    const user = await this.getUserOrFail(updateData.userID);
685

×
686
    if (updateData.nameID) {
×
687
      if (updateData.nameID !== user.nameID) {
×
688
        // updating the nameID, check new value is allowed
689
        await this.isUserNameIdAvailableOrFail(updateData.nameID);
690

×
691
        user.nameID = updateData.nameID;
692
      }
693
    }
694

695
    if (updateData.email) {
696
      if (updateData.email !== user.email) {
697
        const userCheck = await this.userLookupService.isRegisteredUser(
698
          updateData.email
×
699
        );
700
        if (userCheck) {
701
          throw new ValidationException(
×
702
            `User profile with the specified email (${updateData.email}) already exists`,
703
            LogContext.COMMUNITY
×
704
          );
×
705
        }
706

707
        user.email = updateData.email;
708
      }
709
    }
710

711
    return await this.save(user);
712
  }
713

714
  async getProfile(user: IUser): Promise<IProfile> {
715
    const userWithProfile = await this.getUserOrFail(user.id, {
×
716
      relations: { profile: true },
717
    });
×
718
    const profile = userWithProfile.profile;
719
    if (!profile)
×
720
      throw new RelationshipNotFoundException(
721
        `Unable to load Profile for User: ${user.id} `,
722
        LogContext.COMMUNITY
×
723
      );
724

725
    return profile;
726
  }
×
727

×
728
  async getStorageAggregatorOrFail(
729
    userID: string
730
  ): Promise<IStorageAggregator> {
×
731
    const userWithStorage = await this.getUserOrFail(userID, {
732
      relations: {
733
        storageAggregator: true,
734
      },
735
    });
736
    const storageAggregator = userWithStorage.storageAggregator;
737

738
    if (!storageAggregator) {
×
739
      throw new EntityNotFoundException(
740
        `Unable to find storageAggregator for User with nameID: ${userWithStorage.nameID}`,
741
        LogContext.COMMUNITY
×
742
      );
743
    }
744

745
    return storageAggregator;
746
  }
747

748
  async getGuidanceRoom(userID: string): Promise<IRoom | undefined> {
749
    const userWithGuidanceRoom = await this.getUserOrFail(userID, {
750
      relations: {
751
        guidanceRoom: true,
752
      },
753
    });
754
    return userWithGuidanceRoom.guidanceRoom;
×
755
  }
×
756

×
757
  public async createGuidanceRoom(userId: string): Promise<IRoom> {
758
    const user = await this.getUserOrFail(userId, {
759
      relations: {
760
        guidanceRoom: true,
×
761
      },
×
762
    });
763

764
    if (user.guidanceRoom) {
×
765
      throw new Error(
766
        `Guidance room already exists for user with ID: ${userId}`
767
      );
768
    }
×
769

770
    const room = await this.roomService.createRoom(
771
      `${user.communicationID}-guidance`,
772
      RoomType.GUIDANCE
×
773
    );
×
774

775
    user.guidanceRoom = room;
×
776
    await this.save(user);
×
777

778
    return room;
779
  }
780

×
781
  public async getDirectRooms(user: IUser): Promise<DirectRoomResult[]> {
×
782
    const directRooms = await this.communicationAdapter.userGetDirectRooms(
783
      user.communicationID
×
784
    );
×
785

786
    await this.roomLookupService.populateRoomsMessageSenders(directRooms);
×
787

×
788
    return directRooms;
789
  }
790

×
791
  private async createUserNameID(userData: CreateUserInput): Promise<string> {
×
792
    let base = '';
793
    if (userData.firstName && userData.lastName) {
794
      base = `${userData.firstName}-${userData.lastName}`;
×
795
    } else if (userData.firstName) {
×
796
      base = `${userData.firstName}`;
797
    } else if (userData.lastName) {
798
      base = `${userData.lastName}`;
799
    } else if (userData.profileData?.displayName) {
800
      base = userData.profileData.displayName;
801
    } else {
×
802
      base = userData.email.split('@')[0];
×
803
    }
×
804
    const reservedNameIDs =
805
      await this.namingService.getReservedNameIDsInUsers(); // This will need to be smarter later
806
    return this.namingService.createNameIdAvoidingReservedNameIDs(
807
      base,
808
      reservedNameIDs
809
    );
×
810
  }
811
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc