From 0ecb7e48308e1779bea0135a7257ebca24749fd4 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 2 Mar 2020 01:00:00 +0100 Subject: [PATCH] Add support for extra time for a specific day --- src/action/incrementcategoryextratime.ts | 18 ++++-- src/action/setcategoryextratime.ts | 18 ++++-- src/api/validator.ts | 6 ++ src/database/category.ts | 20 ++++++- .../migrations/20200309-add-extra-time-day.ts | 39 +++++++++++++ .../dispatch-parent-action/createcategory.ts | 1 + .../incrementcategoryextratime.ts | 56 ++++++++----------- .../setcategoryextratime.ts | 5 +- src/function/sync/get-server-data-status.ts | 3 + src/object/serverdatastatus.ts | 1 + 10 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 src/database/migration/migrations/20200309-add-extra-time-day.ts diff --git a/src/action/incrementcategoryextratime.ts b/src/action/incrementcategoryextratime.ts index 7d9742a..0e2e0db 100644 --- a/src/action/incrementcategoryextratime.ts +++ b/src/action/incrementcategoryextratime.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 Jonas Lochmann + * 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 @@ -21,8 +21,9 @@ import { ParentAction } from './basetypes' export class IncrementCategoryExtraTimeAction extends ParentAction { readonly categoryId: string readonly addedExtraTime: number + readonly day: number - constructor ({ categoryId, addedExtraTime }: {categoryId: string, addedExtraTime: number}) { + constructor ({ categoryId, addedExtraTime, day }: {categoryId: string, addedExtraTime: number, day: number}) { super() assertIdWithinFamily(categoryId) @@ -31,18 +32,24 @@ export class IncrementCategoryExtraTimeAction extends ParentAction { throw new Error('must add some extra time with IncrementCategoryExtraTimeAction') } + if (day < -1 || (!Number.isSafeInteger(day))) { + throw Error('day must be valid') + } + this.categoryId = categoryId this.addedExtraTime = addedExtraTime + this.day = day } serialize = (): SerializedIncrementCategoryExtraTimeAction => ({ type: 'INCREMENT_CATEGORY_EXTRATIME', categoryId: this.categoryId, - addedExtraTime: this.addedExtraTime + addedExtraTime: this.addedExtraTime, + day: this.day }) - static parse = ({ categoryId, addedExtraTime }: SerializedIncrementCategoryExtraTimeAction) => ( - new IncrementCategoryExtraTimeAction({ categoryId, addedExtraTime }) + static parse = ({ categoryId, addedExtraTime, day }: SerializedIncrementCategoryExtraTimeAction) => ( + new IncrementCategoryExtraTimeAction({ categoryId, addedExtraTime, day: day ?? -1 }) ) } @@ -50,4 +57,5 @@ export interface SerializedIncrementCategoryExtraTimeAction { type: 'INCREMENT_CATEGORY_EXTRATIME' categoryId: string addedExtraTime: number + day?: number } diff --git a/src/action/setcategoryextratime.ts b/src/action/setcategoryextratime.ts index 801b8b6..f91f4a7 100644 --- a/src/action/setcategoryextratime.ts +++ b/src/action/setcategoryextratime.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 Jonas Lochmann + * 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 @@ -21,8 +21,9 @@ import { ParentAction } from './basetypes' export class SetCategoryExtraTimeAction extends ParentAction { readonly categoryId: string readonly newExtraTime: number + readonly day: number - constructor ({ categoryId, newExtraTime }: {categoryId: string, newExtraTime: number}) { + constructor ({ categoryId, newExtraTime, day }: {categoryId: string, newExtraTime: number, day: number}) { super() assertIdWithinFamily(categoryId) @@ -31,18 +32,24 @@ export class SetCategoryExtraTimeAction extends ParentAction { throw Error('newExtraTime must be >= 0') } + if (day < -1 || (!Number.isSafeInteger(day))) { + throw Error('day must be valid') + } + this.categoryId = categoryId this.newExtraTime = newExtraTime + this.day = day } serialize = (): SerializedSetCategoryExtraTimeAction => ({ type: 'SET_CATEGORY_EXTRA_TIME', categoryId: this.categoryId, - newExtraTime: this.newExtraTime + newExtraTime: this.newExtraTime, + day: this.day }) - static parse = ({ categoryId, newExtraTime }: SerializedSetCategoryExtraTimeAction) => ( - new SetCategoryExtraTimeAction({ categoryId, newExtraTime }) + static parse = ({ categoryId, newExtraTime, day }: SerializedSetCategoryExtraTimeAction) => ( + new SetCategoryExtraTimeAction({ categoryId, newExtraTime, day: day ?? -1 }) ) } @@ -50,4 +57,5 @@ export interface SerializedSetCategoryExtraTimeAction { type: 'SET_CATEGORY_EXTRA_TIME' categoryId: string newExtraTime: number + day?: number } diff --git a/src/api/validator.ts b/src/api/validator.ts index d15823b..bf91666 100644 --- a/src/api/validator.ts +++ b/src/api/validator.ts @@ -370,6 +370,9 @@ const definitions = { }, "addedExtraTime": { "type": "number" + }, + "day": { + "type": "number" } }, "additionalProperties": false, @@ -483,6 +486,9 @@ const definitions = { }, "newExtraTime": { "type": "number" + }, + "day": { + "type": "number" } }, "additionalProperties": false, diff --git a/src/database/category.ts b/src/database/category.ts index eef1c70..c40b98e 100644 --- a/src/database/category.ts +++ b/src/database/category.ts @@ -61,9 +61,13 @@ export interface CategoryAttributesVersion7 { sort: number } +export interface CategoryAttributesVersion8 { + extraTimeDay: number +} + export type CategoryAttributes = CategoryAttributesVersion1 & CategoryAttributesVersion2 & CategoryAttributesVersion3 & CategoryAttributesVersion4 & CategoryAttributesVersion5 & - CategoryAttributesVersion6 & CategoryAttributesVersion7 + CategoryAttributesVersion6 & CategoryAttributesVersion7 & CategoryAttributesVersion8 export type CategoryModel = Sequelize.Model & CategoryAttributes export type CategoryModelStatic = typeof Sequelize.Model & { @@ -172,6 +176,17 @@ export const attributesVersion7: SequelizeAttributes } } +export const attributesVersion8: SequelizeAttributes = { + extraTimeDay: { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: -1, + validate: { + min: -1 + } + } +} + export const attributes: SequelizeAttributes = { ...attributesVersion1, ...attributesVersion2, @@ -179,7 +194,8 @@ export const attributes: SequelizeAttributes = { ...attributesVersion4, ...attributesVersion5, ...attributesVersion6, - ...attributesVersion7 + ...attributesVersion7, + ...attributesVersion8 } export const createCategoryModel = (sequelize: Sequelize.Sequelize): CategoryModelStatic => sequelize.define('Category', attributes) as CategoryModelStatic diff --git a/src/database/migration/migrations/20200309-add-extra-time-day.ts b/src/database/migration/migrations/20200309-add-extra-time-day.ts new file mode 100644 index 0000000..929b0b7 --- /dev/null +++ b/src/database/migration/migrations/20200309-add-extra-time-day.ts @@ -0,0 +1,39 @@ +/* + * 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 { QueryInterface, Sequelize, Transaction } from 'sequelize' +import { attributesVersion8 as categoryAttributes } from '../../category' + +export async function up (queryInterface: QueryInterface, sequelize: Sequelize) { + await sequelize.transaction({ + type: Transaction.TYPES.EXCLUSIVE + }, async (transaction) => { + await queryInterface.addColumn('Categories', 'extraTimeDay', { + ...categoryAttributes.extraTimeDay + }, { + transaction + }) + }) +} + +export async function down (queryInterface: QueryInterface, sequelize: Sequelize) { + await sequelize.transaction({ + type: Transaction.TYPES.EXCLUSIVE + }, async (transaction) => { + await queryInterface.removeColumn('Categories', 'extraTimeDay', { transaction }) + }) +} diff --git a/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts b/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts index 484749b..b6f8aa9 100644 --- a/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts +++ b/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts @@ -58,6 +58,7 @@ export async function dispatchCreateCategory ({ action, cache }: { temporarilyBlocked: false, temporarilyBlockedEndTime: 0, extraTimeInMillis: 0, + extraTimeDay: -1, timeLimitRulesVersion: generateVersionId(), baseVersion: generateVersionId(), assignedAppsVersion: generateVersionId(), diff --git a/src/function/sync/apply-actions/dispatch-parent-action/incrementcategoryextratime.ts b/src/function/sync/apply-actions/dispatch-parent-action/incrementcategoryextratime.ts index e49efc0..72e52d5 100644 --- a/src/function/sync/apply-actions/dispatch-parent-action/incrementcategoryextratime.ts +++ b/src/function/sync/apply-actions/dispatch-parent-action/incrementcategoryextratime.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 Jonas Lochmann + * 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 @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -import * as Sequelize from 'sequelize' import { IncrementCategoryExtraTimeAction } from '../../../../action' +import { CategoryModel } from '../../../../database/category' import { Cache } from '../cache' export async function dispatchIncrementCategoryExtraTime ({ action, cache }: { @@ -27,30 +27,22 @@ export async function dispatchIncrementCategoryExtraTime ({ action, cache }: { throw new Error('action requires full version') } - const categoryEntryUnsafe = await cache.database.category.findOne({ - where: { - familyId: cache.familyId, - categoryId: action.categoryId - }, - transaction: cache.transaction, - attributes: [ - 'childId', - 'parentCategoryId' - ] - }) + async function handleCategory (category: CategoryModel) { + if (action.day === category.extraTimeDay || category.extraTimeDay === -1) { + category.extraTimeInMillis += action.addedExtraTime + } else { + category.extraTimeInMillis = action.addedExtraTime + } - if (!categoryEntryUnsafe) { - throw new Error(`tried to add extra time to ${action.categoryId} but it does not exist`) + category.extraTimeDay = action.day + + await category.save({ transaction: cache.transaction }) + + cache.categoriesWithModifiedBaseData.push(category.categoryId) + cache.areChangesImportant = true } - const categoryEntry = { - childId: categoryEntryUnsafe.childId, - parentCategoryId: categoryEntryUnsafe.parentCategoryId - } - - await cache.database.category.update({ - extraTimeInMillis: Sequelize.literal(`extraTimeInMillis + ${action.addedExtraTime}`) as any - }, { + const categoryEntry = await cache.database.category.findOne({ where: { familyId: cache.familyId, categoryId: action.categoryId @@ -58,23 +50,23 @@ export async function dispatchIncrementCategoryExtraTime ({ action, cache }: { transaction: cache.transaction }) - cache.categoriesWithModifiedBaseData.push(action.categoryId) - cache.areChangesImportant = true + if (!categoryEntry) { + throw new Error(`tried to add extra time to ${action.categoryId} but it does not exist`) + } + + await handleCategory(categoryEntry) if (categoryEntry.parentCategoryId !== '') { - const [affectedRows] = await cache.database.category.update({ - extraTimeInMillis: Sequelize.literal(`extraTimeInMillis + ${action.addedExtraTime}`) as any - }, { + const parentCategoryEntry = await cache.database.category.findOne({ where: { familyId: cache.familyId, - categoryId: categoryEntry.parentCategoryId, - childId: categoryEntry.childId + categoryId: categoryEntry.parentCategoryId }, transaction: cache.transaction }) - if (affectedRows !== 0) { - cache.categoriesWithModifiedBaseData.push(categoryEntry.parentCategoryId) + if (parentCategoryEntry) { + await handleCategory(parentCategoryEntry) } } } diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setcategoryextratime.ts b/src/function/sync/apply-actions/dispatch-parent-action/setcategoryextratime.ts index 545c9f6..e77f974 100644 --- a/src/function/sync/apply-actions/dispatch-parent-action/setcategoryextratime.ts +++ b/src/function/sync/apply-actions/dispatch-parent-action/setcategoryextratime.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 Jonas Lochmann + * 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 @@ -27,7 +27,8 @@ export async function dispatchSetCategoryExtraTime ({ action, cache }: { } const [affectedRows] = await cache.database.category.update({ - extraTimeInMillis: action.newExtraTime + extraTimeInMillis: action.newExtraTime, + extraTimeDay: action.day }, { where: { familyId: cache.familyId, diff --git a/src/function/sync/get-server-data-status.ts b/src/function/sync/get-server-data-status.ts index ebc7d2c..36e444c 100644 --- a/src/function/sync/get-server-data-status.ts +++ b/src/function/sync/get-server-data-status.ts @@ -335,6 +335,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI 'title', 'blockedMinutesInWeek', 'extraTimeInMillis', + 'extraTimeDay', 'temporarilyBlocked', 'baseVersion', 'parentCategoryId', @@ -352,6 +353,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI title: item.title, blockedMinutesInWeek: item.blockedMinutesInWeek, extraTimeInMillis: item.extraTimeInMillis, + extraTimeDay: item.extraTimeDay, temporarilyBlocked: item.temporarilyBlocked, baseVersion: item.baseVersion, parentCategoryId: item.parentCategoryId, @@ -369,6 +371,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI title: item.title, blockedTimes: item.blockedMinutesInWeek, extraTime: item.extraTimeInMillis, + extraTimeDay: item.extraTimeDay, tempBlocked: item.temporarilyBlocked, version: item.baseVersion, parentCategoryId: item.parentCategoryId, diff --git a/src/object/serverdatastatus.ts b/src/object/serverdatastatus.ts index 108b9c0..9047b9b 100644 --- a/src/object/serverdatastatus.ts +++ b/src/object/serverdatastatus.ts @@ -99,6 +99,7 @@ export interface ServerUpdatedCategoryBaseData { title: string blockedTimes: string // blockedMinutesInWeek extraTime: number + extraTimeDay: number tempBlocked: boolean tempBlockTime: number version: string