-
Hugo NOUTS authoredHugo NOUTS authored
challengeDataManagerService.ts 20.21 KiB
import { DateTime } from 'luxon'
import { Client } from 'cozy-client'
import {
IChallengeManager,
UserChallenge,
ChallengeType,
EcogestureType,
UserProfile,
ChallengeState,
TypeChallenge,
BadgeState,
} from './dataChallengeContracts'
import UserProfileDataManager from 'services/userProfileDataManagerService'
import ChallengeDataMapper, {
UserChallengeEntity,
ChallengeTypeEntity,
} from 'services/challengeDataMapperService'
import { FluidType } from 'enum/fluid.enum'
import {
CHALLENGETYPE_DOCTYPE,
ECOGESTURE_DOCTYPE,
USERCHALLENGE_DOCTYPE,
USERPROFILE_DOCTYPE,
} from 'doctypes'
import { TimeStep } from 'services/dataConsumptionContracts'
import ConsumptionDataManager from 'services/consumptionDataManagerService'
import PerformanceIndicatorAggregateCalculator from 'services/performanceIndicatorAggregateCalculatorService'
export default class ChallengeManager implements IChallengeManager {
private readonly _client: Client
private readonly _challengeMapper: ChallengeDataMapper
constructor(_client: Client) {
this._client = _client
this._challengeMapper = new ChallengeDataMapper()
}
public async getMaxEnergy(
challenge: UserChallenge,
client: Client,
fluidTypes: FluidType[]
) {
const cdm = new ConsumptionDataManager(client)
let durationTimeStep = ''
let duration = 0
if (challenge && challenge.challengeType) {
durationTimeStep = Object.keys(challenge.challengeType.duration)[0]
duration = (challenge.challengeType.duration as any)[durationTimeStep]
}
const delay = { [durationTimeStep]: -duration }
const startDate = challenge.startingDate.plus(delay)
const endDate = challenge.startingDate.plus({ days: -1 }).endOf('day')
const period = { startDate, endDate }
if (challenge && challenge.challengeType) {
const fetchedPerformanceIndicators = await cdm.getPerformanceIndicators(
period,
TimeStep.DAY,
fluidTypes
)
const maxEnergy = PerformanceIndicatorAggregateCalculator.aggregatePerformanceIndicators(
fetchedPerformanceIndicators
)
return maxEnergy.value
} else {
return 0
}
}
public async setMaxEnergy(
challenge: UserChallenge,
client: Client,
fluidTypes: FluidType[]
): Promise<number> {
const cdm = new ConsumptionDataManager(client)
let durationTimeStep = ''
let duration = 0
if (challenge && challenge.challengeType) {
durationTimeStep = Object.keys(challenge.challengeType.duration)[0]
duration = (challenge.challengeType.duration as any)[durationTimeStep]
}
const delay = { [durationTimeStep]: -duration }
const startDate = challenge.startingDate.plus(delay)
const endDate = challenge.startingDate.plus({ days: -1 }).endOf('day')
const period = { startDate, endDate }
if (challenge && challenge.challengeType) {
const fetchedPerformanceIndicators = await cdm.getPerformanceIndicators(
period,
TimeStep.DAY,
fluidTypes
)
const maxEnergy = PerformanceIndicatorAggregateCalculator.aggregatePerformanceIndicators(
fetchedPerformanceIndicators
)
if (maxEnergy && maxEnergy.value && maxEnergy.value > 0) {
try {
await this._client
.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({ _id: challenge.id })
.limitBy(1)
)
.then(async ({ data }) => {
const doc = data[0]
await this._client.save({
...doc,
maxEnergy: maxEnergy.value,
})
})
return maxEnergy.value
} catch (error) {
return -1
}
}
}
return -1
}
public async getSpentEnergy(
challenge: UserChallenge,
client: Client,
fluidTypes: FluidType[]
) {
const lagDays = this.getLagDays(fluidTypes)
if (DateTime.local() > challenge.startingDate.plus({ days: lagDays })) {
const cdm = new ConsumptionDataManager(client)
const startDate = challenge.startingDate
let endDate = DateTime.local()
.plus({ days: -lagDays })
.endOf('day')
if (await this.isChallengeOver(challenge, fluidTypes)) {
endDate = challenge.endingDate.plus({ days: -1 }).endOf('day')
}
const period = { startDate, endDate }
if (challenge && challenge.challengeType) {
const fetchedPerformanceIndicators = await cdm.getPerformanceIndicators(
period,
TimeStep.DAY,
fluidTypes
)
const spentEnergy = PerformanceIndicatorAggregateCalculator.aggregatePerformanceIndicators(
fetchedPerformanceIndicators
)
return spentEnergy.value
} else {
return -1
}
} else {
return -1
}
}
public async setSpentEnergy(
challenge: UserChallenge,
client: Client,
fluidTypes: FluidType[]
): Promise<number> {
const lagDays = this.getLagDays(fluidTypes)
if (DateTime.local() > challenge.startingDate.plus({ days: lagDays })) {
const cdm = new ConsumptionDataManager(client)
const startDate = challenge.startingDate
let endDate = DateTime.local()
.plus({ days: -lagDays })
.endOf('day')
if (await this.isChallengeOver(challenge, fluidTypes)) {
endDate = challenge.endingDate.plus({ days: -1 }).endOf('day')
}
const period = { startDate, endDate }
if (challenge && challenge.challengeType) {
const fetchedPerformanceIndicators = await cdm.getPerformanceIndicators(
period,
TimeStep.DAY,
fluidTypes
)
const spentEnergy = PerformanceIndicatorAggregateCalculator.aggregatePerformanceIndicators(
fetchedPerformanceIndicators
)
if (spentEnergy && spentEnergy.value && spentEnergy.value >= 0) {
try {
await this._client
.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({ _id: challenge.id })
.limitBy(1)
)
.then(async ({ data }) => {
const doc = data[0]
await this._client.save({
...doc,
currentEnergy: spentEnergy.value,
})
})
return spentEnergy.value
} catch (error) {
return 0
}
}
}
}
return 0
}
public async updateUserLevel(level?: number) {
await this._client
.query(this._client.find(USERPROFILE_DOCTYPE).limitBy(1))
.then(async ({ data }) => {
const doc = data[0]
let actualLevel = 0
if (level) {
actualLevel = level
} else {
actualLevel = doc.level
}
await this._client.save({
...doc,
level: actualLevel + 1,
})
})
}
public async updateChallengeState(
id: string | undefined,
newState: number
): Promise<UserChallenge | null> {
const updateUserChallenge = await this._client
.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({ _id: id })
.limitBy(1)
)
.then(async ({ data }) => {
const doc = data[0]
await this._client.save({ ...doc, state: newState })
})
return updateUserChallenge
}
public async isChallengeOver(
challenge: UserChallenge,
fluidTypes: FluidType[]
) {
if (challenge) {
const typeChallenge = challenge.challengeType
? challenge.challengeType.type
: 0
return this.isChallengeOverByDate(
challenge.endingDate,
fluidTypes,
typeChallenge
)
} else {
return false
}
}
public async isChallengeOverByDate(
endingDate: DateTime,
fluidTypes: FluidType[],
typeChallenge: TypeChallenge
) {
const lagDays = this.getLagDays(fluidTypes)
const endDate =
typeChallenge === TypeChallenge.CHALLENGE
? endingDate.plus({ days: lagDays }).startOf('day')
: endingDate
if (DateTime.local() > endDate) {
return true
} else {
return false
}
}
public getTheRightBadge(
spentEnergy: number | null,
maxEnergy: number | null
) {
if (spentEnergy && maxEnergy) {
if (spentEnergy < maxEnergy) {
return 1
} else {
return 0
}
}
}
public async setTheRightBadge(challenge: UserChallenge): Promise<boolean> {
let badge: BadgeState | null = null
if (challenge.challengeType) {
if (challenge.challengeType.type === 0) {
badge =
challenge.currentEnergy < challenge.maxEnergy
? BadgeState.SUCCESS
: BadgeState.FAILED
} else if (challenge.challengeType.type === 1) {
badge = BadgeState.SUCCESS
}
try {
await this._client
.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({ _id: challenge.id })
.limitBy(1)
)
.then(async ({ data }) => {
const doc = data[0]
await this._client.save({
...doc,
badge: badge,
})
})
return true
} catch (error) {
return false
}
}
return false
}
public async endChallenge(challenge: UserChallenge, fluidTypes: FluidType[]) {
if (challenge && challenge.challengeType) {
if (await this.isChallengeOver(challenge, fluidTypes)) {
if (challenge.challengeType.type === TypeChallenge.ACHIEVEMENT) {
await this.updateChallengeState(challenge.id, ChallengeState.FINISHED)
await this.updateUserLevel(challenge.challengeType.level)
} else {
if (
this.getTheRightBadge(challenge.currentEnergy, challenge.maxEnergy)
) {
await this.updateUserLevel(challenge.challengeType.level)
}
await this.updateChallengeState(challenge.id, ChallengeState.FINISHED)
}
}
}
}
/*
* Return the date of the first day for which
* we can calculate the data in function of configured fluidTypes
*/
public getViewingDate = (
challenge: UserChallenge,
fluidTypes: FluidType[]
): DateTime => {
const startingDate = challenge.startingDate
if (fluidTypes.length > 0) {
const lagDays = this.getLagDays(fluidTypes)
return startingDate.plus({ days: lagDays })
} else {
return startingDate
}
}
/*
* Return the diff of day which represent
* the possible calculation of data based on configured fluidTypes
*/
public getLagDays = (fluidTypes: FluidType[]): number => {
if (fluidTypes.includes(FluidType.WATER)) {
return 3
} else if (fluidTypes.includes(FluidType.GAS)) {
return 2
} else {
return 1
}
}
public async getAllChallengeTypeEntities(): Promise<
ChallengeTypeEntity[] | null
> {
let challengeTypeEntities: ChallengeTypeEntity[] | null = null
const challengeTypesresult = await this._client.query(
this._client.find(CHALLENGETYPE_DOCTYPE)
)
challengeTypeEntities = challengeTypesresult.data
? challengeTypesresult.data
: null
if (!challengeTypeEntities) return null
return challengeTypeEntities
}
public async deleteAllChallengeTypeEntities(): Promise<boolean> {
const challengeType = await this.getAllChallengeTypeEntities()
if (!challengeType) return true
try {
for (let index = 0; index < challengeType.length; index++) {
await this._client.destroy(challengeType[index])
}
return true
} catch (error) {
return false
}
}
public async getAvailableChallenges(
fluidTypes?: FluidType[]
): Promise<ChallengeType[] | null> {
const userProfileDataManager = new UserProfileDataManager(this._client)
const userProfile: UserProfile | null = await userProfileDataManager.getUserProfile()
if (fluidTypes && fluidTypes.length === 0) {
fluidTypes = [0, 1, 2]
}
if (!userProfile) return null
const predicate = this.getAvailableChallengesPredicate(
fluidTypes,
userProfile && userProfile.level
)
const challengeTypesresult = await this._client.query(
this._client
.find(CHALLENGETYPE_DOCTYPE)
.where(predicate)
.include(['availableEcogestures'])
.sortBy([{ level: 'desc' }])
)
const challengeTypeEntities:
| ChallengeTypeEntity[]
| null = challengeTypesresult.data ? challengeTypesresult.data : null
const challengeTypeEntityRelationships:
| any
| null = challengeTypesresult.included
? challengeTypesresult.included
: null
if (!challengeTypeEntities || challengeTypeEntities.length === 0)
return null
const unlockedEcogestures = await this.getUnlockedEcogestures()
const challengeTypes = this._challengeMapper.mapToChallengeTypes(
challengeTypeEntities,
challengeTypeEntityRelationships,
unlockedEcogestures,
fluidTypes
)
return challengeTypes
}
public async startChallenge(
challenge: ChallengeType,
fluidTypes: FluidType[],
selectedEcogestes: EcogestureType[]
): Promise<UserChallenge | null> {
const ongoingChallenge = await this.getCurrentChallenge()
if (!ongoingChallenge) {
const startDate = DateTime.utc()
.plus({ days: 1 })
.startOf('day')
const userChallenge = new UserChallenge(
startDate,
startDate.plus(challenge.duration),
ChallengeState.ONGOING,
selectedEcogestes,
challenge,
-1,
-1,
-1
)
const resultUserChallenge = await this._client.create(
USERCHALLENGE_DOCTYPE,
this._challengeMapper.mapFromUserChallenge(userChallenge)
)
const createdUserChallengeEntity: UserChallengeEntity | null = resultUserChallenge.data
? resultUserChallenge.data
: null
if (!createdUserChallengeEntity) return null
const createdUserChallenge = this._challengeMapper.mapToUserChallenge(
createdUserChallengeEntity
)
return createdUserChallenge
} else {
return null
}
}
public async getAllUserChallengeEntities(): Promise<
UserChallengeEntity[] | null
> {
let userChallengeEntities: UserChallengeEntity[] | null = null
const userChallengesresult = await this._client.query(
this._client.find(USERCHALLENGE_DOCTYPE)
)
userChallengeEntities = userChallengesresult.data
? userChallengesresult.data
: null
if (!userChallengeEntities) return null
return userChallengeEntities
}
public async getAllUserChallenges(
withEcogestures = true
): Promise<UserChallenge[] | null> {
const relationShipsToInclude = ['challengeType']
if (withEcogestures) relationShipsToInclude.push('selectedEcogestures')
const resultUserChallenge = await this._client.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.include(relationShipsToInclude)
.where({
state: {
$gte: ChallengeState.ONGOING,
$lt: ChallengeState.ABANDONED,
},
})
.sortBy([{ endingDate: 'desc' }])
)
if (resultUserChallenge && !resultUserChallenge.data[0]) {
return []
}
const userChallengeEntitites:
| UserChallengeEntity[]
| null = resultUserChallenge.data ? resultUserChallenge.data : null
const userChallengeEntityRelationships:
| any
| null = resultUserChallenge.included
? resultUserChallenge.included
: null
if (!userChallengeEntitites || userChallengeEntitites.length === 0)
return null
const userChallenges: UserChallenge[] = []
userChallengeEntitites.forEach(userChallengeEntitity => {
userChallenges.push(
this._challengeMapper.mapToUserChallenge(
userChallengeEntitity,
userChallengeEntityRelationships
)
)
})
return userChallenges
}
public async getCurrentChallenge(
withEcogestures = true
): Promise<UserChallenge | null> {
const relationShipsToInclude = ['challengeType']
if (withEcogestures) relationShipsToInclude.push('selectedEcogestures')
const resultUserChallenge = await this._client.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({
state: {
$eq: ChallengeState.ONGOING,
},
})
.include(relationShipsToInclude)
.limitBy(1)
)
const userChallengeEntitites:
| UserChallengeEntity[]
| null = resultUserChallenge.data ? resultUserChallenge.data : null
const userChallengeEntityRelationships:
| any
| null = resultUserChallenge.included
? resultUserChallenge.included
: null
if (!userChallengeEntitites || userChallengeEntitites.length === 0)
return null
const userChallenge = this._challengeMapper.mapToUserChallenge(
userChallengeEntitites[0],
userChallengeEntityRelationships
)
return userChallenge
}
public async cancelChallenge(
id: string | undefined
): Promise<UserChallenge | null> {
const updateUserChallenge = await this._client
.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({ _id: id })
.limitBy(1)
)
.then(async ({ data }) => {
const doc = data[0]
await this._client.save({ ...doc, state: ChallengeState.ABANDONED })
})
return updateUserChallenge
}
public async getUnlockedEcogestures(): Promise<EcogestureType[] | null> {
const relationShipsToInclude = ['selectedEcogestures']
const ecogestures = await this._client.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({
state: {
$eq: ChallengeState.FINISHED,
},
})
.include(relationShipsToInclude)
)
if (ecogestures.data.length === 0) return null
const unlocked = ecogestures.data.map(
x => x.relationships.selectedEcogestures.data
)
return unlocked.flat().map(eg => eg._id)
}
public async getAllEcogestures(): Promise<EcogestureType[] | null> {
const ecogestures = await this._client.query(
this._client.find(ECOGESTURE_DOCTYPE)
)
if (!ecogestures) return null
return ecogestures.data
}
public getAvailableChallengesPredicate(
fluidTypes?: FluidType[],
level?: number
) {
let predicate = {}
let fluidTypePredicate = {}
// the predicate changes if the fluidType Array has more than 1 element
if (fluidTypes) {
if (fluidTypes.length >= 2) {
const orClauses: any = []
fluidTypes.forEach(fluidType =>
orClauses.push({
$elemMatch: {
$eq: fluidType,
},
})
)
fluidTypePredicate = { $or: [...orClauses] }
} else if (fluidTypes.length == 1) {
fluidTypePredicate = {
$elemMatch: {
$eq: fluidTypes[0],
},
}
}
}
let levelPredicate = {}
if (level) {
levelPredicate = {
$gte: level,
}
} else {
levelPredicate = {
$gt: 0,
}
}
if (fluidTypePredicate)
predicate = { ...predicate, fluidTypes: fluidTypePredicate }
if (levelPredicate) predicate = { ...predicate, level: levelPredicate }
return predicate
}
public async checkAchievement(id: string): Promise<UserChallenge | null> {
const challenge = await this.getCurrentChallenge()
if (
challenge &&
challenge.challengeType &&
challenge.challengeType.type === TypeChallenge.ACHIEVEMENT &&
challenge.challengeType.id === id
) {
// Achievement is on going, we should update the endingDate
try {
const endDate = DateTime.local().startOf('day')
await this._client
.query(
this._client
.find(USERCHALLENGE_DOCTYPE)
.where({ _id: challenge.id })
.limitBy(1)
)
.then(async ({ data }) => {
const doc = data[0]
await this._client.save({
...doc,
endingDate: endDate,
})
})
challenge.endingDate = endDate
return challenge
} catch (error) {
console.log(error)
return null
}
}
return null
}
}