/* * server component for the TimeLimit App * Copyright (C) 2019 - 2020 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import { MinuteOfDay } from '../util/minuteofday' import { AppLogicAction } from './basetypes' import { InvalidActionParameterException } from './meta/exception' import { assertIdWithinFamily, assertListWithoutDuplicates, assertSafeInteger, throwOutOfRange } from './meta/util' const actionType = 'AddUsedTimeActionVersion2' export class AddUsedTimeActionVersion2 extends AppLogicAction { readonly dayOfEpoch: number readonly items: Array<{ readonly categoryId: string readonly timeToAdd: number readonly extraTimeToSubtract: number readonly additionalCountingSlots: Array readonly sessionDurationLimits: Array }> readonly trustedTimestamp: number constructor ({ dayOfEpoch, items, trustedTimestamp }: { dayOfEpoch: number items: Array<{ categoryId: string timeToAdd: number extraTimeToSubtract: number additionalCountingSlots: Array sessionDurationLimits: Array }> trustedTimestamp: number }) { super() assertSafeInteger({ actionType, field: 'dayOfEpoch', value: dayOfEpoch }) if (dayOfEpoch < 0) { throwOutOfRange({ actionType, field: 'dayOfEpoch', value: dayOfEpoch }) } assertSafeInteger({ actionType, field: 'trustedTimestamp', value: trustedTimestamp }) if (trustedTimestamp < 0) { throwOutOfRange({ actionType, field: 'trustedTimestamp', value: trustedTimestamp }) } if (items.length === 0) { throw new InvalidActionParameterException({ actionType, staticMessage: 'no items' }) } assertListWithoutDuplicates({ actionType, field: 'categoryIds', list: items.map((item) => item.categoryId) }) items.forEach((item) => { assertIdWithinFamily({ actionType, field: 'categoryId', value: item.categoryId }) assertSafeInteger({ actionType, field: 'timeToAdd', value: item.timeToAdd }) if (item.timeToAdd < 0) { throwOutOfRange({ actionType, field: 'timeToAdd', value: item.timeToAdd }) } assertSafeInteger({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract }) if (item.extraTimeToSubtract < 0) { throwOutOfRange({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract }) } assertListWithoutDuplicates({ actionType, field: 'additionalCountingSlots', list: item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize())) }) assertListWithoutDuplicates({ actionType, field: 'sessionDurationLimits', list: item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize())) }) }) this.dayOfEpoch = dayOfEpoch this.items = items this.trustedTimestamp = trustedTimestamp } static parse = ({ d, i, t }: SerializedAddUsedTimeActionVersion2) => ( new AddUsedTimeActionVersion2({ dayOfEpoch: d, items: i.map((item) => ({ categoryId: item.categoryId, timeToAdd: item.tta, extraTimeToSubtract: item.etts, sessionDurationLimits: (item.sdl ?? []).map((item) => AddUsedTimeActionItemSessionDurationLimitSlot.parse(item)), additionalCountingSlots: (item.as ?? []).map((item) => AddUsedTimeActionItemAdditionalCountingSlot.parse(item)) })), trustedTimestamp: t ?? 0 }) ) } class AddUsedTimeActionItemAdditionalCountingSlot { readonly start: number readonly end: number constructor ({ start, end }: { start: number, end: number }) { assertSafeInteger({ actionType, field: 'start', value: start }) assertSafeInteger({ actionType, field: 'end', value: end }) if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) { throw new InvalidActionParameterException({ actionType, staticMessage: 'start or end out of range' }) } if (start === MinuteOfDay.MIN && end === MinuteOfDay.MAX) { throw new InvalidActionParameterException({ actionType, staticMessage: 'couting slot can not fill the whole day' }) } this.start = start this.end = end } serialize = () => [ this.start, this.end ] static parse = ([ start, end ]: [number, number]) => new AddUsedTimeActionItemAdditionalCountingSlot({ start, end }) } class AddUsedTimeActionItemSessionDurationLimitSlot { readonly start: number readonly end: number readonly duration: number readonly pause: number constructor ({ start, end, duration, pause }: { start: number, end: number, duration: number, pause: number }) { assertSafeInteger({ actionType, field: 'start', value: start }) assertSafeInteger({ actionType, field: 'end', value: end }) assertSafeInteger({ actionType, field: 'duration', value: duration }) assertSafeInteger({ actionType, field: 'pause', value: pause }) if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) { throw new InvalidActionParameterException({ actionType, staticMessage: 'start or end out of range' }) } if (duration <= 0 || pause <= 0) { throw new InvalidActionParameterException({ actionType, staticMessage: 'duration and pause must not be zero or smaller' }) } this.start = start this.end = end this.duration = duration this.pause = pause } serialize = () => [ this.start, this.end, this.duration, this.pause ] static parse = ([ start, end, duration, pause ]: [number, number, number, number]) => new AddUsedTimeActionItemSessionDurationLimitSlot({ start, end, duration, pause }) } export interface SerializedAddUsedTimeActionVersion2 { type: 'ADD_USED_TIME_V2' d: number i: Array<{ categoryId: string tta: number etts: number // start, end as?: Array<[number, number]> // start, end, length, pause sdl?: Array<[number, number, number, number]> }> t?: number }