All files / libs/tenants/teams/core/src/lib/use-cases unlink-from-parent-team.use-case.ts

93.4% Statements 85/91
66.66% Branches 6/9
100% Functions 4/4
93.4% Lines 85/91

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 921x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 24x 24x 24x 24x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 8x 8x 8x 8x 8x 6x 6x 6x 16x             16x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 1x 1x 16x 16x 16x 16x 1x  
import { ForbiddenException, Logger, NotFoundException, UnprocessableEntityException } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Transactional } from 'typeorm-transactional';
 
import { Team, type Company } from '@amalia/core/models';
import { assert, toError } from '@amalia/ext/typescript';
import { canModifyTeams, defineAbilityFor } from '@amalia/kernel/auth/shared';
import { type AuthenticatedContext } from '@amalia/kernel/auth/types';
import { RecordObject, RecordType } from '@amalia/tenants/monitoring/audit/types';
 
import { TeamUpdatedEvent } from '../events/team-updated-event';
 
export class UnlinkFromParentTeamUseCase {
  private readonly logger = new Logger(UnlinkFromParentTeamUseCase.name);
 
  public constructor(
    private readonly eventBus: EventBus,
    @InjectRepository(Team)
    private readonly teamsRepository: Repository<Team>,
  ) {}
 
  @Transactional()
  public async execute({
    company,
    authenticatedContext,
    teamId,
  }: {
    company: Company;
    authenticatedContext: AuthenticatedContext;
    teamId: Team['id'];
  }) {
    this.assertCanExecute(authenticatedContext);
 
    const team = await this.teamsRepository.findOne({
      where: { id: teamId, company: { id: company.id } },
      relations: ['parentTeam'],
      select: {
        id: true,
        name: true,
        parentTeamId: true,
        parentTeam: { id: true, name: true },
      },
    });
 
    assert(team, new NotFoundException(`Team ${teamId} not found`));
    assert(team.parentTeam, new UnprocessableEntityException(`Team ${team.name} has no parent`));
 
    await this.teamsRepository.save({ id: teamId, parentTeamId: null, parentTeam: undefined });
 
    try {
      await this.publishAuditLog({ authenticatedContext, team });
    } catch (err) {
      this.logger.error({
        message: 'Failed to publish TeamUpdatedEvent',
        error: toError(err),
        team,
      });
    }
  }
 
  private async publishAuditLog({
    authenticatedContext,
    team,
  }: {
    authenticatedContext: AuthenticatedContext;
    team: Pick<Team, 'id' | 'name' | 'parentTeam'>;
  }) {
    assert(team.parentTeam, 'Parent team should be defined here');
 
    await this.eventBus.publish(
      new TeamUpdatedEvent({
        authenticatedContext,
        object: RecordObject.TEAM,
        type: RecordType.EDIT,
        values: {
          target: { id: team.id, name: team.name },
          oldValues: { parentTeam: team.parentTeam.name },
          newValues: { parentTeam: null },
        },
      }),
    );
  }
 
  private assertCanExecute(authenticatedContext: AuthenticatedContext) {
    const ability = defineAbilityFor(authenticatedContext);
 
    assert(canModifyTeams(ability), new ForbiddenException('You do not have the permission to modify teams'));
  }
}