All files / libs/tenants/users/core/src/lib/usecases update-user-after-connection.use-case.ts

80.53% Statements 91/113
45.45% Branches 5/11
100% Functions 2/2
80.53% Lines 91/113

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 1141x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x                                             5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 4x 3x 3x 4x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x  
import { Injectable } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
 
import { User, type Company } from '@amalia/core/models';
import { assert } from '@amalia/ext/typescript';
import { IdentityProviderService } from '@amalia/kernel/auth/core';
import { type AuthenticatedContext } from '@amalia/kernel/auth/types';
import { RecordObject, RecordType } from '@amalia/tenants/monitoring/audit/types';
import { AvatarsService } from '@amalia/tenants/users/profile/core';
import { formatUserFullName, type UserContract } from '@amalia/tenants/users/types';
 
import { AppUsersRepository } from '../appUsers.repository';
import { UserSignedInEvent } from '../events/UserSignedInEvent';
 
@Injectable()
export class UpdateUserAfterConnectionUseCase {
  public constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    private readonly eventBus: EventBus,
    private readonly appUsersRepository: AppUsersRepository,
    private readonly identityProviderService: IdentityProviderService,
    private readonly avatarsService: AvatarsService,
  ) {}
 
  public async execute({
    company,
    authenticatedContext,
    token,
  }: {
    company: Company;
    authenticatedContext: AuthenticatedContext;
    token: string;
  }) {
    const userFromDb = await this.appUsersRepository.findUserByEmail(authenticatedContext.user.email);
 
    assert(userFromDb, 'User not found');
 
    if (authenticatedContext.impersonation) {
      this.eventBus.publish(
        new UserSignedInEvent(company, userFromDb, {
          authenticatedContext: { ...authenticatedContext, user: userFromDb },
          object: RecordObject.AUTHENTICATION,
          type: RecordType.LOG,
          values: {
            target: {
              id: userFromDb.id,
              name: `${formatUserFullName(userFromDb)} (impersonated)`,
            },
            newValues: {
              action: 'Sign in',
              impersonator: authenticatedContext.impersonation.impersonator.email,
            },
          },
        }),
      );
      return {
        previousLastConnection: null,
        user: userFromDb as UserContract,
      };
    }
 
    // If the user is not an impersonation, we're going to update his last connection date, and his avatar if needed.
 
    // If it's my first connection, log the joinedAt.
    if (!userFromDb.joinedAt) {
      userFromDb.joinedAt = new Date();
    }
 
    // User could have uploaded its own avatar.
    // We fall back to the one from auth0 if the user doesn't have one, or if it's the default one.
    const shouldUpdatePicture = !userFromDb.pictureURL || this.avatarsService.isDefaultAvatar(userFromDb.pictureURL);
    if (shouldUpdatePicture) {
      const { picture: auth0Picture } = await this.identityProviderService.getUserInfo(token);
      if (auth0Picture) {
        userFromDb.pictureURL = auth0Picture;
      }
    }
 
    // Update the last connection date.
    const previousLastConnection = userFromDb.lastConnection || null;
    userFromDb.lastConnection = new Date();
 
    await Promise.all([
      // Save the user with updated fields.
      this.userRepository.save(userFromDb),
      // Log the connection.
      this.eventBus.publish(
        new UserSignedInEvent(company, userFromDb, {
          authenticatedContext: {
            ...authenticatedContext,
            user: userFromDb,
          },
          object: RecordObject.AUTHENTICATION,
          type: RecordType.LOG,
          values: {
            target: {
              id: userFromDb.id,
              name: formatUserFullName(userFromDb),
            },
            newValues: {
              action: 'Sign in',
            },
          },
        }),
      ),
    ]);
 
    return { previousLastConnection, user: userFromDb };
  }
}