Skip to content
Snippets Groups Projects
initialization.service.ts 25.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { Client, Q, QueryDefinition } from 'cozy-client'
    import {
      ECOGESTURE_DOCTYPE,
      PROFILE_DOCTYPE,
      EGL_DAY_DOCTYPE,
      EGL_MONTH_DOCTYPE,
      EGL_YEAR_DOCTYPE,
      ENEDIS_DAY_DOCTYPE,
      ENEDIS_MINUTE_DOCTYPE,
      ENEDIS_MONTH_DOCTYPE,
      ENEDIS_YEAR_DOCTYPE,
      GRDF_DAY_DOCTYPE,
      GRDF_MONTH_DOCTYPE,
      GRDF_YEAR_DOCTYPE,
      CHALLENGE_DOCTYPE,
      DUEL_DOCTYPE,
      QUIZ_DOCTYPE,
      EXPLORATION_DOCTYPE,
    
    } from 'doctypes'
    
    import { FluidType } from 'enum/fluid.enum'
    import {
      Dataload,
      FluidStatus,
      Profile,
      ProfileType,
    
      UserChallenge,
    } from 'models'
    
    import EcogestureService from 'services/ecogesture.service'
    import ChallengeService from 'services/challenge.service'
    import ecogestureData from 'db/ecogestureData.json'
    import challengeEntityData from 'db/challengeEntity.json'
    import duelEntityData from 'db/duelEntity.json'
    import quizEntityData from 'db/quizEntity.json'
    import explorationEntityData from 'db/explorationEntity.json'
    
    import fluidPrices from 'db/fluidPrices.json'
    
    
    import ProfileService from 'services/profile.service'
    import profileData from 'db/profileData.json'
    import KonnectorStatusService from 'services/konnectorStatus.service'
    import KonnectorService from 'services/konnector.service'
    import AccountService from 'services/account.service'
    import FluidService from 'services/fluid.service'
    import DuelService from 'services/duel.service'
    import QuizService from 'services/quiz.service'
    import ExplorationService from 'services/exploration.service'
    
    import { hashFile } from 'utils/hash'
    import { getActualAnalysisDate } from 'utils/date'
    import { TimeStep } from 'enum/timeStep.enum'
    import ConsumptionDataManager from './consumption.service'
    import { UserChallengeUpdateFlag } from 'enum/userChallenge.enum'
    import { getRoundFloat } from 'utils/math'
    import { DateTime } from 'luxon'
    import ProfileTypeEntityService from './profileTypeEntity.service'
    import TermsService from './terms.service'
    import log from 'utils/logger'
    
    import { ProfileEcogesture } from 'models/profileEcogesture.model'
    import ProfileEcogestureService from './profileEcogesture.service'
    
    import FluidPricesService from './fluidsPrices.service'
    
    
    export default class InitializationService {
      private readonly _client: Client
    
      constructor(_client: Client) {
        this._client = _client
      }
    
      /*
       * Call a query with where clause to create the index if not exist
       */
      private async createIndex(
        doctype: string,
        timestep: TimeStep
      ): Promise<object> {
        const getMongoSelector = () => {
          switch (timestep) {
            case TimeStep.YEAR:
              return {
                year: {
                  $lte: 9999,
                },
              }
            case TimeStep.MONTH:
              return {
                year: {
                  $lte: 9999,
                },
                month: {
                  $lte: 12,
                },
              }
            case TimeStep.DAY:
            case TimeStep.HALF_AN_HOUR:
              return {
                year: {
                  $lte: 9999,
                },
                month: {
                  $lte: 12,
                },
                day: {
                  $lte: 31,
                },
              }
            default:
              return {}
          }
        }
        const query: QueryDefinition = Q(doctype)
          .where(getMongoSelector())
          .limitBy(1)
        return await this._client.query(query)
      }
    
      /*
       * create index for each Doctype
       * sucess return: true
       * failure throw error
       */
      public async initIndex(): Promise<boolean> {
        try {
          const accountService = new AccountService(this._client)
          const konnectorService = new KonnectorService(this._client)
          await Promise.all([
            this.createIndex(EGL_YEAR_DOCTYPE, TimeStep.YEAR),
            this.createIndex(EGL_MONTH_DOCTYPE, TimeStep.MONTH),
            this.createIndex(EGL_DAY_DOCTYPE, TimeStep.DAY),
            this.createIndex(ENEDIS_YEAR_DOCTYPE, TimeStep.YEAR),
            this.createIndex(ENEDIS_MONTH_DOCTYPE, TimeStep.MONTH),
            this.createIndex(ENEDIS_DAY_DOCTYPE, TimeStep.DAY),
            this.createIndex(ENEDIS_MINUTE_DOCTYPE, TimeStep.HALF_AN_HOUR),
            this.createIndex(GRDF_YEAR_DOCTYPE, TimeStep.YEAR),
            this.createIndex(GRDF_MONTH_DOCTYPE, TimeStep.MONTH),
            this.createIndex(GRDF_DAY_DOCTYPE, TimeStep.DAY),
            konnectorService.createIndexKonnector(),
            accountService.createIndexAccount(),
          ])
          log.info('[Initialization] Indexes created')
          return true
        } catch (error) {
          log.error('Initialization error - initIndex: ', error)
          throw error
        }
      }
    
      /*
       * Check if profil exist
       * If not, the profil is created
       * sucess return: profil
       * failure return: null
       */
      public async initProfile(): Promise<Profile | null> {
        const profileService = new ProfileService(this._client)
        try {
          const loadedProfile = await profileService.getProfile()
          if (!loadedProfile) {
            // Population with the data
            const { data: newProfile } = await this._client.create(
              PROFILE_DOCTYPE,
              profileData[0]
            )
            if (newProfile) {
              log.info('[Initialization] Profile created')
            } else {
              throw new Error('initProfile: Profile not created')
            }
          } else {
            log.info('[Initialization] Profile loaded')
          }
          const updatedProfile = await profileService.updateProfile({
            lastConnectionDate: DateTime.local().setZone('utc', {
              keepLocalTime: true,
            }),
          })
          return updatedProfile
        } catch (error) {
          log.error('Initialization error - initProfile: ', error)
          throw error
        }
      }
    
      /*
       * Check if profileType exist
       * If not, the profileType is created
       * sucess return: profileType
       * failure return: null
       */
      public async initProfileType(): Promise<ProfileType | null> {
        const profileTypeEntityService = new ProfileTypeEntityService(this._client)
        try {
    
          const loadedProfileType = await profileTypeEntityService.getProfileType()
          log.info('[Initialization] ProfileType loaded')
    
          return loadedProfileType
        } catch (error) {
          log.error('Initialization error - initProfileType: ', error)
          throw error
        }
      }
    
      public async initProfileEcogesture(): Promise<ProfileEcogesture | null> {
        const profileEcogestureService = new ProfileEcogestureService(this._client)
        try {
          const loadedProfileEcogesture = await profileEcogestureService.getProfileEcogesture()
          log.info('[Initialization] ProfileEcogesture loaded')
          return loadedProfileEcogesture
        } catch (error) {
          log.error('Initialization error - initProfileEcogesture: ', error)
          throw error
        }
      }
    
    
      public async initEcogesture(hash: string): Promise<string> {
        const hashEcogestureType = hashFile(ecogestureData)
        const ecogestureService = new EcogestureService(this._client)
        // Populate data if none ecogesture exists
    
        const loadedEcogestures = await ecogestureService.getAllEcogestures(
          undefined,
          true
        )
    
        if (
          !loadedEcogestures ||
          (loadedEcogestures && loadedEcogestures.length === 0)
        ) {
          // Populate the doctype with data
          try {
            for (const ecogesture of ecogestureData) {
              await this._client.create(ECOGESTURE_DOCTYPE, ecogesture)
            }
            // Check of created document based on count
            const checkCount = await ecogestureService.getAllEcogestures()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== ecogestureData.length)
            ) {
              throw new Error(
                'initEcogesture: Created ecogesture type entities does not match'
              )
            }
    
            log.info('[Initialization] Ecogesture list created')
    
            return hashEcogestureType
          } catch (error) {
            log.error('Initialization error - initEcogesture: ', error)
            throw error
          }
        }
        // Update if the hash is not the same as the one from profile
        if (hash !== hashEcogestureType) {
          // Update the doctype
          try {
    
            loadedEcogestures
    
            // Deletion of all documents
            await ecogestureService.deleteAllEcogestures()
            // Population with the data
    
            for (const [index, ecogesture] of ecogestureData.entries()) {
              const updateEcogesture = loadedEcogestures[index]
                ? {
                    ...ecogesture,
                    objective: loadedEcogestures[index].objective ? true : false,
                    doing: loadedEcogestures[index].doing ? true : false,
    
                    viewedInSelection: loadedEcogestures[index].viewedInSelection
                      ? true
                      : false,
    
                  }
                : ecogesture
              await this._client.create(ECOGESTURE_DOCTYPE, updateEcogesture)
    
            }
            // Check of created document based on count
            const checkCount = await ecogestureService.getAllEcogestures()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== ecogestureData.length)
            ) {
              throw new Error(
                'initEcogesture: Created ecogesture type entities does not match'
              )
            }
            log.info('[Initialization] Ecogesture updated')
            return hashEcogestureType
          } catch (error) {
            log.error('Initialization error - initEcogesture: ', error)
            throw error
          }
        } else {
          // Doctype already up to date
    
          log.info('[Initialization]  Ecogesture already up-to-date')
    
          return hashEcogestureType
        }
      }
    
    
      public async initFluidPrices(hash: string): Promise<string> {
        const hashFluidPricesType = hashFile(fluidPrices)
        const fpService = new FluidPricesService(this._client)
        // Populate data if none ecogesture exists
        const loadedPrices = await fpService.getAllPrices()
        if (!loadedPrices || (loadedPrices && loadedPrices.length === 0)) {
          // Populate the doctype with data
    
          try {
            for (const price of fluidPrices) {
              await this._client.create(FLUIDPRICES_DOCTYPE, price)
            }
            // Check of created document based on count
            const checkCount = await fpService.getAllPrices()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== fluidPrices.length)
            ) {
              throw new Error(
                'initFluidPrices: Created fluidPrices type entities does not match'
              )
            }
            log.info('[Initialization] FluidPrices list created')
            return hashFluidPricesType
          } catch (error) {
            log.error('Initialization error - initFluidPrices: ', error)
            throw error
          }
        }
        // Update if the hash is not the same as the one from profile
        if (hash !== hashFluidPricesType) {
          // Update the doctype
          try {
            // Deletion of all documents
            await fpService.deleteAllFluidsPrices()
            // Population with the data
            for (const price of fluidPrices) {
              await this._client.create(FLUIDPRICES_DOCTYPE, price)
            }
            // Check of created document based on count
            const checkCount = await fpService.getAllPrices()
    
            if (
              !checkCount ||
              (checkCount && checkCount.length !== fluidPrices.length)
            ) {
              throw new Error(
                'initFluidPrices: Created fluidPrices type entities does not match'
              )
            }
            log.info('[Initialization] FluidPrices updated')
            return hashFluidPricesType
          } catch (error) {
            log.error('Initialization error - initFluidPrices: ', error)
            throw error
          }
        } else {
          // Doctype already up to date
          log.info('[Initialization]  FluidPrices already up-to-date')
          return hashFluidPricesType
        }
      }
    
    
      public async initChallengeEntity(hash: string): Promise<string> {
        const challengeHash = hashFile(challengeEntityData)
        const challengeService = new ChallengeService(this._client)
        // Populate data if none challengeEntity exists
        const loadedChallengeEntity = await challengeService.getAllChallengeEntities()
        if (
          !loadedChallengeEntity ||
          (loadedChallengeEntity && loadedChallengeEntity.length === 0)
        ) {
          // Populate the doctype with data
          try {
            for (const challengeEntity of challengeEntityData) {
              await this._client.create(CHALLENGE_DOCTYPE, challengeEntity)
            }
            // Check of created document
            const checkCount = await challengeService.getAllChallengeEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== challengeEntityData.length)
            ) {
              throw new Error(
                'initChallengeEntity: Created challenge entities does not match'
              )
            }
            log.info('[Initialization] Challenge entities created')
            return challengeHash
          } catch (error) {
            log.error('Initialization error - initChallengeEntity: ', error)
            throw error
          }
        }
        // Update if the hash is not the same as the one from profile
        if (hash !== challengeHash) {
          // Update the doctype
          try {
            // Deletion of all documents
            await challengeService.deleteAllChallengeEntities()
            // Population with the data
            for (const challengeEntity of challengeEntityData) {
              await this._client.create(CHALLENGE_DOCTYPE, challengeEntity)
            }
            // Check of created document
            const checkCount = await challengeService.getAllChallengeEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== challengeEntityData.length)
            ) {
              throw new Error(
                'initChallengeEntity: Created challenge entities does not match'
              )
            }
            log.info('[Initialization] Challenge entities updated')
            return challengeHash
          } catch (error) {
            log.error('Initialization error - initChallengeEntity: ', error)
            throw error
          }
        } else {
          // Doctype already up to date
          log.info('[Initialization] Challenge Entity loaded')
          return challengeHash
        }
      }
    
      public async initDuelEntity(hash: string): Promise<string> {
        const hashDuelEntity = hashFile(duelEntityData)
        const duelService = new DuelService(this._client)
        // Populate data if none DuelEntity exists
        const loadedDuelTypes = await duelService.getAllDuelEntities()
        if (!loadedDuelTypes || (loadedDuelTypes && loadedDuelTypes.length === 0)) {
          // Populate the doctype with data
          try {
            for (const duelEntity of duelEntityData) {
              await this._client.create(DUEL_DOCTYPE, duelEntity)
            }
            // Check of created document
            const checkCount = await duelService.getAllDuelEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== duelEntityData.length)
            ) {
              throw new Error(
                'initDuelEntity: Created duel entities does not match'
              )
            }
            log.info('[Initialization] UserDuel entities created')
            return hashDuelEntity
          } catch (error) {
            log.error('Initialization error - initDuelEntity: ', error)
            throw error
          }
        }
        // Update if the hash is not the same as the one from profile
        if (hash !== hashDuelEntity) {
          // Update the doctype
          try {
            // Deletion of all documents
            await duelService.deleteAllDuelEntities()
            // Population with the data
            for (const duelEntity of duelEntityData) {
              await this._client.create(DUEL_DOCTYPE, duelEntity)
            }
            // Check of created document
            const checkCount = await duelService.getAllDuelEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== duelEntityData.length)
            ) {
              throw new Error(
                'initDuelEntity: Created duel entities does not match'
              )
            }
            log.info('[Initialization] UserDuel entities updated')
            return hashDuelEntity
          } catch (error) {
            log.error('Initialization error - initDuelEntity: ', error)
            throw error
          }
        } else {
          // Doctype already up to date
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
          log.info('[Initialization] Duel Entity loaded')
    
          return hashDuelEntity
        }
      }
    
      public async initQuizEntity(hash: string): Promise<string> {
        const quizHash = hashFile(quizEntityData)
        const quizService = new QuizService(this._client)
        // Populate data if none quizEntity exists
        const loadedQuizEntity = await quizService.getAllQuizEntities()
        if (
          !loadedQuizEntity ||
          (loadedQuizEntity && loadedQuizEntity.length === 0)
        ) {
          // Populate the doctype with data
          try {
            for (const quizEntity of quizEntityData) {
              await this._client.create(QUIZ_DOCTYPE, quizEntity)
            }
            // Check of created document
            const checkCount = await quizService.getAllQuizEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== quizEntityData.length)
            ) {
              throw new Error(
                'initQuizEntity: Created quiz entities does not match'
              )
            }
    
            log.info('[Initialization] Quiz entities created')
            return quizHash
          } catch (error) {
            log.error('Initialization error - initQuizEntity: ', error)
            throw error
          }
        }
        // Update if the hash is not the same as the one from profile
        if (hash !== quizHash) {
          // Update the doctype
          try {
            // Deletion of all documents
            await quizService.deleteAllQuizEntities()
            // Population with the data
            for (const quizEntity of quizEntityData) {
              await this._client.create(QUIZ_DOCTYPE, quizEntity)
            }
            // Check of created document
            const checkCount = await quizService.getAllQuizEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== quizEntityData.length)
            ) {
              throw new Error(
                'initQuizEntity: Created quiz entities does not match'
              )
            }
            log.info('[Initialization] Quiz entities updated')
            return quizHash
          } catch (error) {
            log.error('Initialization error - initQuizEntity: ', error)
            throw error
          }
        } else {
          // Doctype already up to date
          log.info('[Initialization] Quiz Entity loaded')
          return quizHash
        }
      }
    
      public async initExplorationEntity(hash: string): Promise<string> {
        const explorationHash = hashFile(explorationEntityData)
        const explorationService = new ExplorationService(this._client)
        // Populate data if none explorationEntity exists
        const loadedExplorationEntity = await explorationService.getAllExplorationEntities()
        if (
          !loadedExplorationEntity ||
          (loadedExplorationEntity && loadedExplorationEntity.length === 0)
        ) {
          // Populate the doctype with data
          try {
            for (const explorationEntity of explorationEntityData) {
              await this._client.create(EXPLORATION_DOCTYPE, explorationEntity)
            }
            // Check of created document
            const checkCount = await explorationService.getAllExplorationEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== explorationEntityData.length)
            ) {
              throw new Error(
                'initExplorationEntity: Created exploration entities does not match'
              )
            }
            log.info('[Initialization] Exploration entities created')
            return explorationHash
          } catch (error) {
            log.error('Initialization error - initExplorationEntity: ', error)
            throw error
          }
        }
        // Update if the hash is not the same as the one from profile
        if (hash !== explorationHash) {
          // Update the doctype
          try {
            // Deletion of all documents
            await explorationService.deleteAllExplorationEntities()
            // Population with the data
            for (const explorationEntity of explorationEntityData) {
              await this._client.create(EXPLORATION_DOCTYPE, explorationEntity)
            }
            // Check of created document
            const checkCount = await explorationService.getAllExplorationEntities()
            if (
              !checkCount ||
              (checkCount && checkCount.length !== explorationEntityData.length)
            ) {
              throw new Error(
                'initExplorationEntity: Created exploration entities does not match'
              )
            }
            log.info('[Initialization] Exploration entities updated')
            return explorationHash
          } catch (error) {
            log.error('Initialization error - initExplorationEntity: ', error)
            throw error
          }
        } else {
          // Doctype already up to date
          log.info('[Initialization] Exploration Entity loaded')
          return explorationHash
        }
      }
    
      public async initAnalysis(
        profile: Profile
      ): Promise<{
        monthlyAnalysisDate: DateTime
        haveSeenLastAnalysis: boolean
      }> {
        try {
          const actualAnalysisDate = getActualAnalysisDate()
          if (
            profile.monthlyAnalysisDate &&
            actualAnalysisDate <= profile.monthlyAnalysisDate
          ) {
            return {
              monthlyAnalysisDate: profile.monthlyAnalysisDate,
              haveSeenLastAnalysis: profile.haveSeenLastAnalysis,
            }
          } else {
            log.info('[Initialization] Analysis information from profile updated')
            return {
              monthlyAnalysisDate: actualAnalysisDate,
              haveSeenLastAnalysis: profile.isFirstConnection ? true : false,
            }
          }
        } catch (error) {
          log.error('Initialization error - initAnalysis: ', error)
          throw error
        }
      }
    
      /*
       * Check if FluidTypes exist
       * sucess return: FluidType[]
       * failure throw error
       */
      public async initFluidTypes(): Promise<FluidType[]> {
        const kss = new KonnectorStatusService(this._client)
        try {
          const fluidtypes = await kss.getKonnectorAccountStatus()
          if (fluidtypes) {
            log.info('[Initialization] Fluid Types loaded')
            return fluidtypes
          } else {
            throw new Error('initFluidTypes: FluidTypes not found')
          }
        } catch (error) {
          log.error('Initialization error - initFluidTypes: ', error)
          throw error
        }
      }
    
      /*
       * For each fluid get the trigger status and the last data date
       * sucess return: FluidStatus[]
       * failure throw error
       */
      public async initFluidStatus(): Promise<FluidStatus[]> {
        const fs = new FluidService(this._client)
        try {
          const fluidStatus = await fs.getFluidStatus()
          if (fluidStatus) {
            log.info('[Initialization] Fluid Status loaded')
            return fluidStatus
          } else {
            throw new Error('initFluidStatus: fluidStatus not found')
          }
        } catch (error) {
          log.error('Initialization error - initFluidStatus: ', error)
          throw error
        }
      }
    
      /*
       * Build the userChallengeList
       * sucess return: UserChallenge[]
       * failure throw error
       */
      public async initUserChallenges(
        fluidStatus: FluidStatus[]
      ): Promise<UserChallenge[]> {
        const challengeService = new ChallengeService(this._client)
        try {
          const userChallengeList = await challengeService.buildUserChallengeList(
            fluidStatus
          )
          if (userChallengeList) {
            log.info('[Initialization] Challenges loaded')
            return userChallengeList
          } else {
            throw new Error('initUserChallenges: userChallengeList not found')
          }
        } catch (error) {
          log.error('Initialization error - initUserChallenges: ', error)
          throw error
        }
      }
    
      /*
       * Retrieve dataloads for ongoing duel
       * sucess return: UserChallenge, Dataload[]
       * failure throw error
       */
      public async initDuelProgress(
        userChallenge: UserChallenge
      ): Promise<{
        updatedUserChallenge: UserChallenge
        dataloads: Dataload[]
      }> {
        const challengeService = new ChallengeService(this._client)
        const consumptionService = new ConsumptionDataManager(this._client)
        try {
          const dataloads: Dataload[] = await challengeService.getUserChallengeDataload(
            userChallenge
          )
          const userConsumption: number = getRoundFloat(
            consumptionService.calculatePerformanceIndicatorValue(dataloads)
          )
          const _userChallenge: UserChallenge = {
            ...userChallenge,
            duel: {
              ...userChallenge.duel,
              userConsumption: userConsumption,
            },
          }
          const updatedUserChallenge: UserChallenge = await challengeService.updateUserChallenge(
            _userChallenge,
            UserChallengeUpdateFlag.DUEL_CONSUMPTION
          )
          return { updatedUserChallenge, dataloads }
        } catch (error) {
          log.error('Initialization error: ', error)
          throw error
        }
      }
    
      public async initConsent(): Promise<TermsStatus> {
        const termsStatus: TermsStatus = {
          accepted: false,
          versionType: 'init',
        }
    
        try {
          const termService = new TermsService(this._client)
          const isUpToDate = await termService.isConsentVersionUpToDate()
    
          const lastTerm = await termService.getLastTerm()
          if (lastTerm) {
            if (isUpToDate) {
              const isLastConsentValidated = await termService.isLastTermValidated()
              if (isLastConsentValidated) {
                termsStatus.accepted = true
                termsStatus.versionType = 'init'
                log.info(
                  '[Initialization] Last Consent successfully loaded and valid'
                )
              } else {
                termsStatus.versionType = 'init'
                termsStatus.accepted = false
                log.info('[Initialization] Consent not up-to-date')
              }
    
            } else {
    
              const versionType = await termService.getTermsVersionType()
              if (versionType === 'minor') {
                termsStatus.accepted = false
                termsStatus.versionType = 'minor'
                log.info('[Initialization] Minor Terms update detected')
              } else {
                termsStatus.accepted = false
                termsStatus.versionType = 'major'
                log.info('[Initialization] Major Terms update detected')
              }
    
            }
          } else {
    
            termsStatus.accepted = false
            termsStatus.versionType = 'init'
            log.info('[Initialization] Init first terms')
    
        } catch (error) {
          log.error('Initialization error - initConsent: ', error)
          throw error
        }
      }
    }