Vizzuality/landgriffon

View on GitHub
api/src/modules/indicators/indicators.service.ts

Summary

Maintainability
D
1 day
Test Coverage
A
90%
import {
  BadRequestException,
  Injectable,
  NotFoundException,
  ServiceUnavailableException,
} from '@nestjs/common';
import {
  AppBaseService,
  JSONAPISerializerConfig,
} from 'utils/app-base.service';
import {
  Indicator,
  INDICATOR_STATUS,
  indicatorResource,
} from 'modules/indicators/indicator.entity';
import { AppInfoDTO } from 'dto/info.dto';
import { IndicatorRepository } from 'modules/indicators/indicator.repository';
import { CreateIndicatorDto } from 'modules/indicators/dto/create.indicator.dto';
import { UpdateIndicatorDto } from 'modules/indicators/dto/update.indicator.dto';
import { FindManyOptions, FindOptionsWhere, In } from 'typeorm';
import { getDiffForEntitiesToBeActivated } from 'utils/helpers/array-diff.helper';

@Injectable()
export class IndicatorsService extends AppBaseService<
  Indicator,
  CreateIndicatorDto,
  UpdateIndicatorDto,
  AppInfoDTO
> {
  constructor(protected readonly indicatorRepository: IndicatorRepository) {
    super(
      indicatorRepository,
      indicatorResource.name.singular,
      indicatorResource.name.plural,
    );
  }

  get serializerConfig(): JSONAPISerializerConfig<Indicator> {
    return {
      attributes: [
        'id',
        'name',
        'description',
        'category',
        'unit',
        'status',
        'metadata',
        'nameCode',
      ],
      keyForAttribute: 'camelCase',
    };
  }

  async getIndicatorById(id: string): Promise<Indicator> {
    const found: Indicator | null = await this.indicatorRepository.findOne({
      where: { id },
    });

    if (!found) {
      throw new NotFoundException(`Indicator with ID "${id}" not found`);
    }

    return found;
  }

  async getIndicatorsById(ids: string[]): Promise<Indicator[]> {
    const indicators: Indicator[] = await this.indicatorRepository.findBy({
      id: In(ids),
    });

    if (!indicators.length) {
      throw new NotFoundException(
        'No Indicator has been found with provided IDs',
      );
    }
    return indicators;
  }

  async findAllIndicators(
    whereOptions?: FindOptionsWhere<Indicator>,
  ): Promise<Indicator[]> {
    const findOptions: FindManyOptions<Indicator> = whereOptions
      ? { cache: 1000, where: whereOptions }
      : { cache: 1000 };

    return this.indicatorRepository.find(findOptions);
  }

  async activateIndicators(
    indicatorsFromSheet: CreateIndicatorDto[],
  ): Promise<Indicator[]> {
    const nameCodesToActivateIndicatorsBy: string[] = indicatorsFromSheet
      .filter((i: CreateIndicatorDto) => i.status === INDICATOR_STATUS.ACTIVE)
      .map((i: CreateIndicatorDto) => i.nameCode);
    this.logger.log(
      `Found ${nameCodesToActivateIndicatorsBy.length} to activate`,
    );
    const indicatorsFoundByProvidedNameCodes: Indicator[] =
      await this.indicatorRepository.find({
        where: {
          nameCode: In(nameCodesToActivateIndicatorsBy),
        },
      });
    if (!indicatorsFoundByProvidedNameCodes.length) {
      throw new ServiceUnavailableException(
        'No Indicators found matching provided NameCodes. Unable to calculate impact. Aborting Import',
      );
    }
    if (
      indicatorsFoundByProvidedNameCodes.length !==
      nameCodesToActivateIndicatorsBy.length
    ) {
      const codesWithNoMatchingIndicatorInDb: string[] =
        getDiffForEntitiesToBeActivated(
          nameCodesToActivateIndicatorsBy,
          indicatorsFoundByProvidedNameCodes.map((i: Indicator) => i.nameCode),
        );
      this.logger.warn(`Mismatch in indicators meant to be activated: `);
      this.logger.warn(
        `Provided nameCodes with no matching Indicators in DB: ${codesWithNoMatchingIndicatorInDb.join(
          ', ',
        )}`,
      );
      this.logger.warn(`Activating found Indicators...`);
    }

    await this.indicatorRepository.update(
      { nameCode: In(nameCodesToActivateIndicatorsBy) },
      { status: INDICATOR_STATUS.ACTIVE },
    );
    return this.indicatorRepository.find({
      where: { status: INDICATOR_STATUS.ACTIVE },
    });
  }

  /**
   * @description: Reset all present Indicators to status inactive, to be actived by a spreadsheet (import)
   *               as requested by the user
   * @note: TypeORM does not seem to support bulk updates without filtering criteria
   */
  async deactivateAllIndicators(): Promise<void> {
    this.logger.log(`Setting all Indicators to Inactive...`);
    const allIndicators: Indicator[] = await this.indicatorRepository.find();
    await this.indicatorRepository.save(
      allIndicators.map((i: Indicator) => ({
        ...i,
        status: INDICATOR_STATUS.INACTIVE,
      })),
    );
  }

  async areRequestedIndicatorsActive(indicatorIds: string[]): Promise<void> {
    const inactiveSelectedIndicators: Indicator[] =
      await this.findAllIndicators({
        id: In(indicatorIds),
        status: INDICATOR_STATUS.INACTIVE,
      });

    if (inactiveSelectedIndicators.length) {
      const inactiveIndicatorsNames: string[] = inactiveSelectedIndicators.map(
        (indicator: Indicator) => indicator.name,
      );
      throw new BadRequestException(
        `Requested Indicators are not activated: ${inactiveIndicatorsNames.join(
          ', ',
        )}`,
      );
    }
  }
}