Add support for more limits

This commit is contained in:
Jonas Lochmann
2020-05-18 02:00:00 +02:00
parent d021497e52
commit ef1b19a01c
79 changed files with 2785 additions and 136 deletions
+99 -5
View File
@@ -16,6 +16,7 @@
*/
import { uniq } from 'lodash'
import { MinuteOfDay } from '../util/minuteofday'
import { assertIdWithinFamily } from '../util/token'
import { AppLogicAction } from './basetypes'
@@ -25,15 +26,21 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
readonly categoryId: string
readonly timeToAdd: number
readonly extraTimeToSubtract: number
readonly additionalCountingSlots: Array<AddUsedTimeActionItemAdditionalCountingSlot>
readonly sessionDurationLimits: Array<AddUsedTimeActionItemSessionDurationLimitSlot>
}>
readonly trustedTimestamp: number
constructor ({ dayOfEpoch, items }: {
constructor ({ dayOfEpoch, items, trustedTimestamp }: {
dayOfEpoch: number
items: Array<{
categoryId: string
timeToAdd: number
extraTimeToSubtract: number
additionalCountingSlots: Array<AddUsedTimeActionItemAdditionalCountingSlot>
sessionDurationLimits: Array<AddUsedTimeActionItemSessionDurationLimitSlot>
}>
trustedTimestamp: number
}) {
super()
@@ -41,6 +48,10 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
throw new Error('illegal dayOfEpoch')
}
if (trustedTimestamp < 0 || (!Number.isSafeInteger(trustedTimestamp))) {
throw new Error('illegal trustedTimestamp')
}
if (items.length === 0) {
throw new Error('missing items')
}
@@ -59,10 +70,25 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
if (item.extraTimeToSubtract < 0 || (!Number.isSafeInteger(item.extraTimeToSubtract))) {
throw new Error('illegal extra time to subtract')
}
if (
uniq(item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))).length !==
item.additionalCountingSlots.length
) {
throw new Error()
}
if (
uniq(item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))).length !==
item.sessionDurationLimits.length
) {
throw new Error()
}
})
this.dayOfEpoch = dayOfEpoch
this.items = items
this.trustedTimestamp = trustedTimestamp
}
serialize = (): SerializedAddUsedTimeActionVersion2 => ({
@@ -72,21 +98,84 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
categoryId: item.categoryId,
tta: item.timeToAdd,
etts: item.extraTimeToSubtract
}))
})),
t: this.trustedTimestamp
})
static parse = ({ d, i }: SerializedAddUsedTimeActionVersion2) => (
static parse = ({ d, i, t }: SerializedAddUsedTimeActionVersion2) => (
new AddUsedTimeActionVersion2({
dayOfEpoch: d,
items: i.map((item) => ({
categoryId: item.categoryId,
timeToAdd: item.tta,
extraTimeToSubtract: item.etts
}))
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 }) {
if ((!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end))) {
throw new Error()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error()
}
if (start === MinuteOfDay.MIN && end === MinuteOfDay.MAX) {
throw new Error()
}
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 }) {
if (
(!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end)) ||
(!Number.isSafeInteger(duration)) || (!Number.isSafeInteger(pause))
) {
throw new Error()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error()
}
if (duration <= 0 || pause <= 0) {
throw new Error()
}
this.start = start
this.end = end
this.duration = duration
this.pause = pause
}
serialize = () => [ this.start, this.end ]
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
@@ -94,5 +183,10 @@ export interface SerializedAddUsedTimeActionVersion2 {
categoryId: string
tta: number
etts: number
// start, end
as?: Array<[number, number]>
// start, end, length, pause
sdl?: Array<[number, number, number, number]>
}>
t?: number
}
+50 -5
View File
@@ -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,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { MinuteOfDay } from '../util/minuteofday'
import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
@@ -23,12 +24,23 @@ export class UpdateTimelimitRuleAction extends ParentAction {
readonly maximumTimeInMillis: number
readonly dayMask: number
readonly applyToExtraTimeUsage: boolean
readonly start: number
readonly end: number
readonly sessionDurationMilliseconds: number
readonly sessionPauseMilliseconds: number
constructor ({ ruleId, maximumTimeInMillis, dayMask, applyToExtraTimeUsage }: {
constructor ({
ruleId, maximumTimeInMillis, dayMask, applyToExtraTimeUsage,
start, end, sessionDurationMilliseconds, sessionPauseMilliseconds
}: {
ruleId: string
maximumTimeInMillis: number
dayMask: number
applyToExtraTimeUsage: boolean
start: number
end: number
sessionDurationMilliseconds: number
sessionPauseMilliseconds: number
}) {
super()
@@ -36,6 +48,10 @@ export class UpdateTimelimitRuleAction extends ParentAction {
this.maximumTimeInMillis = maximumTimeInMillis
this.dayMask = dayMask
this.applyToExtraTimeUsage = applyToExtraTimeUsage
this.start = start
this.end = end
this.sessionDurationMilliseconds = sessionDurationMilliseconds
this.sessionPauseMilliseconds = sessionPauseMilliseconds
assertIdWithinFamily(ruleId)
@@ -50,6 +66,23 @@ export class UpdateTimelimitRuleAction extends ParentAction {
)) {
throw new Error('invalid day mask')
}
if (
(!Number.isSafeInteger(start)) ||
(!Number.isSafeInteger(end)) ||
(!Number.isSafeInteger(sessionDurationMilliseconds)) ||
(!Number.isSafeInteger(sessionPauseMilliseconds))
) {
throw new Error()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error()
}
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
throw new Error()
}
}
serialize = (): SerializedUpdateTimelimitRuleAction => ({
@@ -57,15 +90,23 @@ export class UpdateTimelimitRuleAction extends ParentAction {
ruleId: this.ruleId,
time: this.maximumTimeInMillis,
days: this.dayMask,
extraTime: this.applyToExtraTimeUsage
extraTime: this.applyToExtraTimeUsage,
start: this.start,
end: this.end,
pause: this.sessionPauseMilliseconds,
dur: this.sessionDurationMilliseconds
})
static parse = ({ ruleId, time, days, extraTime }: SerializedUpdateTimelimitRuleAction) => (
static parse = ({ ruleId, time, days, extraTime, start, end, dur, pause }: SerializedUpdateTimelimitRuleAction) => (
new UpdateTimelimitRuleAction({
ruleId,
maximumTimeInMillis: time,
dayMask: days,
applyToExtraTimeUsage: extraTime
applyToExtraTimeUsage: extraTime,
start: start ?? MinuteOfDay.MIN,
end: end ?? MinuteOfDay.MAX,
sessionDurationMilliseconds: dur ?? 0,
sessionPauseMilliseconds: pause ?? 0
})
)
}
@@ -76,4 +117,8 @@ export interface SerializedUpdateTimelimitRuleAction {
time: number
days: number
extraTime: boolean
start?: number
end?: number
dur?: number
pause?: number
}
+162 -1
View File
@@ -24,6 +24,9 @@ const definitions = {
},
"users": {
"type": "string"
},
"clientLevel": {
"type": "number"
}
},
"additionalProperties": false,
@@ -252,6 +255,18 @@ const definitions = {
},
"extraTime": {
"type": "boolean"
},
"start": {
"type": "number"
},
"end": {
"type": "number"
},
"dur": {
"type": "number"
},
"pause": {
"type": "number"
}
},
"additionalProperties": false,
@@ -1087,6 +1102,18 @@ const definitions = {
},
"extraTime": {
"type": "boolean"
},
"start": {
"type": "number"
},
"end": {
"type": "number"
},
"dur": {
"type": "number"
},
"pause": {
"type": "number"
}
},
"additionalProperties": false,
@@ -1208,6 +1235,68 @@ const definitions = {
},
"etts": {
"type": "number"
},
"as": {
"type": "array",
"items": {
"type": "array",
"items": [
{
"type": "number"
},
{
"type": "number"
}
],
"minItems": 2,
"additionalItems": {
"anyOf": [
{
"type": "number"
},
{
"type": "number"
}
]
}
}
},
"sdl": {
"type": "array",
"items": {
"type": "array",
"items": [
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
}
],
"minItems": 4,
"additionalItems": {
"anyOf": [
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
}
]
}
}
}
},
"additionalProperties": false,
@@ -1217,6 +1306,9 @@ const definitions = {
"tta"
]
}
},
"t": {
"type": "number"
}
},
"additionalProperties": false,
@@ -1750,6 +1842,12 @@ const definitions = {
"$ref": "#/definitions/ServerUsedTimeItem"
}
},
"sessionDurations": {
"type": "array",
"items": {
"$ref": "#/definitions/ServerSessionDurationItem"
}
},
"version": {
"type": "string"
}
@@ -1757,6 +1855,7 @@ const definitions = {
"additionalProperties": false,
"required": [
"categoryId",
"sessionDurations",
"times",
"version"
]
@@ -1769,14 +1868,60 @@ const definitions = {
},
"time": {
"type": "number"
},
"start": {
"type": "number"
},
"end": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"day",
"end",
"start",
"time"
]
},
"ServerSessionDurationItem": {
"type": "object",
"properties": {
"md": {
"description": "the maximum duration of a session (maxSessionDuration)",
"type": "number"
},
"spd": {
"description": "the pause duration after a session (sessionPauseDuration)",
"type": "number"
},
"sm": {
"description": "the start minute of the day of the session/ the rule\nwhich created this session (startMinuteOfDay)",
"type": "number"
},
"em": {
"description": "the end minute of the day of the session/ the rule\nwhich created this session (endMinuteOfDay)",
"type": "number"
},
"l": {
"description": "the timestamp of the last usage of this session (lastUsage)",
"type": "number"
},
"d": {
"description": "the duration of the last/ current session (lastSessionDuration)",
"type": "number"
}
},
"additionalProperties": false,
"required": [
"d",
"em",
"l",
"md",
"sm",
"spd"
]
},
"ServerUpdatedTimeLimitRules": {
"type": "object",
"properties": {
@@ -1814,14 +1959,30 @@ const definitions = {
},
"maxTime": {
"type": "number"
},
"start": {
"type": "number"
},
"end": {
"type": "number"
},
"session": {
"type": "number"
},
"pause": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"dayMask",
"end",
"extraTime",
"id",
"maxTime"
"maxTime",
"pause",
"session",
"start"
]
},
"ServerUserList": {
+3
View File
@@ -29,6 +29,7 @@ import { createMailLoginTokenModel, MailLoginTokenModelStatic } from './maillogi
import { createUmzug } from './migration/umzug'
import { createOldDeviceModel, OldDeviceModelStatic } from './olddevice'
import { createPurchaseModel, PurchaseModelStatic } from './purchase'
import { createSessionDurationModel, SessionDurationModelStatic } from './sessionduration'
import { createTimelimitRuleModel, TimelimitRuleModelStatic } from './timelimitrule'
import { createUsedTimeModel, UsedTimeModelStatic } from './usedtime'
import { createUserModel, UserModelStatic } from './user'
@@ -46,6 +47,7 @@ export interface Database {
mailLoginToken: MailLoginTokenModelStatic
oldDevice: OldDeviceModelStatic
purchase: PurchaseModelStatic
sessionDuration: SessionDurationModelStatic
timelimitRule: TimelimitRuleModelStatic
usedTime: UsedTimeModelStatic
user: UserModelStatic
@@ -65,6 +67,7 @@ const createDatabase = (sequelize: Sequelize.Sequelize): Database => ({
mailLoginToken: createMailLoginTokenModel(sequelize),
oldDevice: createOldDeviceModel(sequelize),
purchase: createPurchaseModel(sequelize),
sessionDuration: createSessionDurationModel(sequelize),
timelimitRule: createTimelimitRuleModel(sequelize),
usedTime: createUsedTimeModel(sequelize),
user: createUserModel(sequelize),
@@ -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
@@ -24,7 +24,7 @@ import { attributes as categoryAppAttributes } from '../../categoryapp'
import { attributesVersion1 as deviceAttributes } from '../../device'
import { attributes as familyAttributes } from '../../family'
import { attributes as purchaseAttributes } from '../../purchase'
import { attributes as timelimitruleAttributes } from '../../timelimitrule'
import { attributesVersion1 as timelimitruleAttributes } from '../../timelimitrule'
import { attributesVersion1 as usedTimeAttribute } from '../../usedtime'
import { attributesVersion1 as userAttributes } from '../../user'
@@ -0,0 +1,88 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { QueryInterface, Sequelize, Transaction } from 'sequelize'
import { MinuteOfDay } from '../../../util/minuteofday'
import { attributesVersion1 as sessionDurationAttributes } from '../../sessionduration'
import { attributesVersion2 as timelimitRuleAttributes } from '../../timelimitrule'
import {
attributesVersion1 as usedTimeAttributesVersion1,
attributesVersion2 as usedTimeAttributesVersion2,
attributesVersion3 as usedTimeAttributesVersion3
} from '../../usedtime'
export async function up (queryInterface: QueryInterface, sequelize: Sequelize) {
await sequelize.transaction({
type: Transaction.TYPES.EXCLUSIVE
}, async (transaction) => {
// session durations
await queryInterface.createTable('SessionDurations', sessionDurationAttributes, { transaction })
// timelimit rule table
await queryInterface.addColumn('TimelimitRules', 'startMinuteOfDay', {
...timelimitRuleAttributes.startMinuteOfDay
}, { transaction })
await queryInterface.addColumn('TimelimitRules', 'endMinuteOfDay', {
...timelimitRuleAttributes.endMinuteOfDay
}, { transaction })
await queryInterface.addColumn('TimelimitRules', 'sessionDurationMilliseconds', {
...timelimitRuleAttributes.sessionDurationMilliseconds
}, { transaction })
await queryInterface.addColumn('TimelimitRules', 'sessionPauseMilliseconds', {
...timelimitRuleAttributes.sessionPauseMilliseconds
}, { transaction })
// used times
await queryInterface.renameTable('UsedTimes', 'UsedTimesOld', { transaction })
await queryInterface.createTable('UsedTimes', {
...usedTimeAttributesVersion1,
...usedTimeAttributesVersion2,
...usedTimeAttributesVersion3
}, { transaction })
await sequelize.query(`
INSERT INTO UsedTimes (familyId, categoryId, dayOfEpoch, usedTime, lastUpdate, startMinuteOfDay, endMinuteOfDay)
SELECT familyId, categoryId, dayOfEpoch, usedTime, lastUpdate,
${MinuteOfDay.MIN} AS startMinuteOfDay, ${MinuteOfDay.MAX} AS endMinuteOfDay
FROM UsedTimesOld
`, { transaction })
await queryInterface.dropTable('UsedTimesOld', { transaction })
})
}
export async function down (queryInterface: QueryInterface, sequelize: Sequelize) {
await sequelize.transaction({
type: Transaction.TYPES.EXCLUSIVE
}, async (transaction) => {
// session durations
await queryInterface.dropTable('SessionDurations', { transaction })
// timelimit rule table
await queryInterface.removeColumn('TimelimitRules', 'startMinuteOfDay', { transaction })
await queryInterface.removeColumn('TimelimitRules', 'endMinuteOfDay', { transaction })
await queryInterface.removeColumn('TimelimitRules', 'sessionDurationMilliseconds', { transaction })
await queryInterface.removeColumn('TimelimitRules', 'sessionPauseMilliseconds', { transaction })
// used times
throw new Error('not implemented')
})
}
+112
View File
@@ -0,0 +1,112 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import * as Sequelize from 'sequelize'
import { MinuteOfDay } from '../util/minuteofday'
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
import { SequelizeAttributes } from './types'
interface SessionDurationAttributesVersion1 {
familyId: string
categoryId: string
maxSessionDuration: number
sessionPauseDuration: number
startMinuteOfDay: number
endMinuteOfDay: number
lastUsage: string
lastSessionDuration: number
// used for deleting old items, set by the server
roundedLastUpdate: string
}
export type SessionDurationAttributes = SessionDurationAttributesVersion1
export type SessionDurationModel = Sequelize.Model & SessionDurationAttributes
export type SessionDurationModelStatic = typeof Sequelize.Model & {
new (values?: object, options?: Sequelize.BuildOptions): SessionDurationModel;
}
export const attributesVersion1: SequelizeAttributes<SessionDurationAttributesVersion1> = {
familyId: {
...familyIdColumn,
primaryKey: true
},
categoryId: {
...idWithinFamilyColumn,
primaryKey: true
},
maxSessionDuration: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
validate: {
min: 1
}
},
sessionPauseDuration: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
validate: {
min: 1
}
},
startMinuteOfDay: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
validate: {
min: MinuteOfDay.MIN,
max: MinuteOfDay.MAX
}
},
endMinuteOfDay: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
validate: {
min: MinuteOfDay.MIN,
max: MinuteOfDay.MAX,
customValidator (endMinuteOfDay: unknown) {
const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
throw new Error('wrong data types')
}
if (startMinuteOfDay > endMinuteOfDay) {
throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay')
}
}
}
},
lastUsage: { ...timestampColumn },
lastSessionDuration: {
type: Sequelize.INTEGER,
allowNull: false,
validate: {
min: 0
}
},
roundedLastUpdate: { ...timestampColumn }
}
export const attributes: SequelizeAttributes<SessionDurationAttributes> = {
...attributesVersion1
}
export const createSessionDurationModel = (sequelize: Sequelize.Sequelize): SessionDurationModelStatic => sequelize.define('SessionDuration', attributes) as SessionDurationModelStatic
+66 -3
View File
@@ -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
@@ -16,10 +16,11 @@
*/
import * as Sequelize from 'sequelize'
import { MinuteOfDay } from '../util/minuteofday'
import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns'
import { SequelizeAttributes } from './types'
export interface TimelimitRuleAttributes {
interface TimelimitRuleAttributesVersion1 {
familyId: string
ruleId: string
categoryId: string
@@ -28,12 +29,21 @@ export interface TimelimitRuleAttributes {
maximumTimeInMillis: number
}
interface TimelimitRuleAttributesVersion2 {
startMinuteOfDay: number
endMinuteOfDay: number
sessionDurationMilliseconds: number
sessionPauseMilliseconds: number
}
type TimelimitRuleAttributes = TimelimitRuleAttributesVersion1 & TimelimitRuleAttributesVersion2
export type TimelimitRuleModel = Sequelize.Model & TimelimitRuleAttributes
export type TimelimitRuleModelStatic = typeof Sequelize.Model & {
new (values?: object, options?: Sequelize.BuildOptions): TimelimitRuleModel;
}
export const attributes: SequelizeAttributes<TimelimitRuleAttributes> = {
export const attributesVersion1: SequelizeAttributes<TimelimitRuleAttributesVersion1> = {
familyId: {
...familyIdColumn,
primaryKey: true
@@ -61,4 +71,57 @@ export const attributes: SequelizeAttributes<TimelimitRuleAttributes> = {
}
}
export const attributesVersion2: SequelizeAttributes<TimelimitRuleAttributesVersion2> = {
startMinuteOfDay: {
type: Sequelize.INTEGER,
validate: {
min: MinuteOfDay.MIN,
max: MinuteOfDay.MAX
},
allowNull: false,
defaultValue: MinuteOfDay.MIN
},
endMinuteOfDay: {
type: Sequelize.INTEGER,
validate: {
min: MinuteOfDay.MIN,
max: MinuteOfDay.MAX,
customValidator (endMinuteOfDay: unknown) {
const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
throw new Error('wrong data types')
}
if (startMinuteOfDay > endMinuteOfDay) {
throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay')
}
}
},
allowNull: false,
defaultValue: MinuteOfDay.MAX
},
sessionDurationMilliseconds: {
type: Sequelize.INTEGER,
validate: {
min: 0
},
allowNull: false,
defaultValue: 0
},
sessionPauseMilliseconds: {
type: Sequelize.INTEGER,
validate: {
min: 0
},
allowNull: false,
defaultValue: 0
}
}
export const attributes: SequelizeAttributes<TimelimitRuleAttributes> = {
...attributesVersion1,
...attributesVersion2
}
export const createTimelimitRuleModel = (sequelize: Sequelize.Sequelize): TimelimitRuleModelStatic => sequelize.define('TimelimitRule', attributes) as TimelimitRuleModelStatic
+46 -4
View File
@@ -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
@@ -16,6 +16,7 @@
*/
import * as Sequelize from 'sequelize'
import { MinuteOfDay } from '../util/minuteofday'
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
import { SequelizeAttributes } from './types'
@@ -30,7 +31,13 @@ export interface UsedTimeAttributesVersion2 {
lastUpdate: string
}
export type UsedTimeAttributes = UsedTimeAttributesVersion1 & UsedTimeAttributesVersion2
export interface UsedTimeAttributesVersion3 {
startMinuteOfDay: number
endMinuteOfDay: number
}
export type UsedTimeAttributes = UsedTimeAttributesVersion1 &
UsedTimeAttributesVersion2 & UsedTimeAttributesVersion3
export type UsedTimeModel = Sequelize.Model & UsedTimeAttributes
export type UsedTimeModelStatic = typeof Sequelize.Model & {
@@ -70,9 +77,44 @@ export const attributesVersion2: SequelizeAttributes<UsedTimeAttributesVersion2>
}
}
export const attributes = {
export const attributesVersion3: SequelizeAttributes<UsedTimeAttributesVersion3> = {
startMinuteOfDay: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: MinuteOfDay.MIN,
primaryKey: true,
validate: {
min: MinuteOfDay.MIN,
max: MinuteOfDay.MAX
}
},
endMinuteOfDay: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: MinuteOfDay.MAX,
primaryKey: true,
validate: {
min: MinuteOfDay.MIN,
max: MinuteOfDay.MAX,
customValidator (endMinuteOfDay: unknown) {
const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
throw new Error('wrong data types')
}
if (startMinuteOfDay > endMinuteOfDay) {
throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay')
}
}
}
}
}
export const attributes: SequelizeAttributes<UsedTimeAttributesVersion3> = {
...attributesVersion1,
...attributesVersion2
...attributesVersion2,
...attributesVersion3
}
export const createUsedTimeModel = (sequelize: Sequelize.Sequelize): UsedTimeModelStatic => sequelize.define('UsedTime', attributes) as UsedTimeModelStatic
@@ -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
@@ -17,6 +17,7 @@
import * as Sequelize from 'sequelize'
import { AddUsedTimeAction } from '../../../../action'
import { MinuteOfDay } from '../../../../util/minuteofday'
import { Cache } from '../cache'
export const getRoundedTimestamp = () => {
@@ -68,7 +69,9 @@ export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
where: {
familyId: cache.familyId,
categoryId: categoryId,
dayOfEpoch: action.dayOfEpoch
dayOfEpoch: action.dayOfEpoch,
startMinuteOfDay: MinuteOfDay.MIN,
endMinuteOfDay: MinuteOfDay.MAX
},
transaction: cache.transaction
})
@@ -80,7 +83,9 @@ export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
categoryId: categoryId,
dayOfEpoch: action.dayOfEpoch,
usedTime: action.timeToAdd,
lastUpdate: roundedTimestamp
lastUpdate: roundedTimestamp,
startMinuteOfDay: MinuteOfDay.MIN,
endMinuteOfDay: MinuteOfDay.MAX
}, {
transaction: cache.transaction
})
@@ -17,15 +17,23 @@
import * as Sequelize from 'sequelize'
import { AddUsedTimeActionVersion2 } from '../../../../action'
import { MinuteOfDay } from '../../../../util/minuteofday'
import { Cache } from '../cache'
import { getRoundedTimestamp } from './addusedtime'
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
export const getRoundedTimestampForSessionDuration = () => {
const now = Date.now()
return now - (now % (1000 * 60 * 60 * 12 /* 12 hours */))
}
export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache }: {
deviceId: string
action: AddUsedTimeActionVersion2
cache: Cache
}) {
const roundedTimestamp = getRoundedTimestamp().toString(10)
const roundedTimestampForUsedTime = getRoundedTimestampForUsedTime().toString(10)
const roundedTimestampForSessionDuration = getRoundedTimestampForSessionDuration().toString(10)
for (let i = 0; i < action.items.length; i++) {
const item = action.items[i]
@@ -41,6 +49,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache }:
'extraTimeInMillis'
]
})
// verify that the category exists
if (!categoryEntryUnsafe) {
cache.requireFullSync()
@@ -53,16 +62,19 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache }:
extraTimeInMillis: categoryEntryUnsafe.extraTimeInMillis
}
if (item.timeToAdd !== 0) {
// tslint:disable-next-line:no-inner-declarations
async function handle (start: number, end: number) {
// try to update first
const [updatedRows] = await cache.database.usedTime.update({
usedTime: Sequelize.literal(`usedTime + ${item.timeToAdd}`) as any,
lastUpdate: roundedTimestamp
lastUpdate: roundedTimestampForUsedTime
}, {
where: {
familyId: cache.familyId,
categoryId: item.categoryId,
dayOfEpoch: action.dayOfEpoch
dayOfEpoch: action.dayOfEpoch,
startMinuteOfDay: start,
endMinuteOfDay: end
},
transaction: cache.transaction
})
@@ -74,15 +86,75 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache }:
categoryId: item.categoryId,
dayOfEpoch: action.dayOfEpoch,
usedTime: item.timeToAdd,
lastUpdate: roundedTimestamp
lastUpdate: roundedTimestampForUsedTime,
startMinuteOfDay: start,
endMinuteOfDay: end
}, {
transaction: cache.transaction
})
}
cache.categoriesWithModifiedUsedTimes.push(item.categoryId)
}
await handle(MinuteOfDay.MIN, MinuteOfDay.MAX)
for (let j = 0; j < item.additionalCountingSlots.length; j++) {
const slot = item.additionalCountingSlots[j]
await handle(slot.start, slot.end)
}
const hasTrustedTimestamp = action.trustedTimestamp !== 0
for (let j = 0; j < item.sessionDurationLimits.length; j++) {
const limit = item.sessionDurationLimits[j]
const oldItem = await cache.database.sessionDuration.findOne({
where: {
familyId: cache.familyId,
categoryId: item.categoryId,
maxSessionDuration: limit.duration,
sessionPauseDuration: limit.pause,
startMinuteOfDay: limit.start,
endMinuteOfDay: limit.end
},
transaction: cache.transaction
})
if (oldItem) {
if (hasTrustedTimestamp) {
oldItem.lastUsage = action.trustedTimestamp.toString(10)
}
if (
hasTrustedTimestamp &&
action.trustedTimestamp > parseInt(oldItem.lastUsage, 10) + oldItem.sessionPauseDuration
) {
oldItem.lastSessionDuration = item.timeToAdd
} else {
oldItem.lastSessionDuration = oldItem.lastSessionDuration + item.timeToAdd
}
oldItem.roundedLastUpdate = roundedTimestampForSessionDuration
await oldItem.save({ transaction: cache.transaction })
} else {
await cache.database.sessionDuration.create({
familyId: cache.familyId,
categoryId: item.categoryId,
maxSessionDuration: limit.duration,
sessionPauseDuration: limit.pause,
startMinuteOfDay: limit.start,
endMinuteOfDay: limit.end,
// end of primary key
lastUsage: action.trustedTimestamp,
lastSessionDuration: item.timeToAdd,
roundedLastUpdate: roundedTimestampForSessionDuration
}, { transaction: cache.transaction })
}
}
cache.categoriesWithModifiedUsedTimes.push(item.categoryId)
if (item.extraTimeToSubtract !== 0) {
await cache.database.category.update({
extraTimeInMillis: Math.max(0, categoryEntry.extraTimeInMillis - item.extraTimeToSubtract)
@@ -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
@@ -34,7 +34,11 @@ export async function dispatchCreateTimeLimitRule ({ action, cache }: {
categoryId: action.rule.categoryId,
applyToExtraTimeUsage: action.rule.applyToExtraTimeUsage,
maximumTimeInMillis: action.rule.maxTimeInMillis,
dayMaskAsBitmask: action.rule.dayMask
dayMaskAsBitmask: action.rule.dayMask,
startMinuteOfDay: action.rule.start,
endMinuteOfDay: action.rule.end,
sessionDurationMilliseconds: action.rule.sessionDurationMilliseconds,
sessionPauseMilliseconds: action.rule.sessionPauseMilliseconds
}, { transaction: cache.transaction })
cache.categoriesWithModifiedTimeLimitRules.push(action.rule.categoryId)
@@ -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
@@ -37,6 +37,10 @@ export async function dispatchUpdateTimelimitRule ({ action, cache }: {
ruleEntry.applyToExtraTimeUsage = action.applyToExtraTimeUsage
ruleEntry.dayMaskAsBitmask = action.dayMask
ruleEntry.maximumTimeInMillis = action.maximumTimeInMillis
ruleEntry.startMinuteOfDay = action.start
ruleEntry.endMinuteOfDay = action.end
ruleEntry.sessionDurationMilliseconds = action.sessionDurationMilliseconds
ruleEntry.sessionPauseMilliseconds = action.sessionPauseMilliseconds
await ruleEntry.save({ transaction: cache.transaction })
+67 -9
View File
@@ -25,6 +25,7 @@ import {
ServerUpdatedCategoryBaseData, ServerUpdatedCategoryUsedTimes,
ServerUpdatedTimeLimitRules
} from '../../object/serverdatastatus'
import { MinuteOfDay } from '../../util/minuteofday'
export const generateServerDataStatus = async ({ database, clientStatus, familyId, transaction }: {
database: Database,
@@ -429,7 +430,11 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
'categoryId',
'applyToExtraTimeUsage',
'maximumTimeInMillis',
'dayMaskAsBitmask'
'dayMaskAsBitmask',
'startMinuteOfDay',
'endMinuteOfDay',
'sessionDurationMilliseconds',
'sessionPauseMilliseconds'
],
transaction
})).map((item) => ({
@@ -437,7 +442,11 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
categoryId: item.categoryId,
applyToExtraTimeUsage: item.applyToExtraTimeUsage,
maximumTimeInMillis: item.maximumTimeInMillis,
dayMaskAsBitmask: item.dayMaskAsBitmask
dayMaskAsBitmask: item.dayMaskAsBitmask,
startMinuteOfDay: item.startMinuteOfDay,
endMinuteOfDay: item.endMinuteOfDay,
sessionDurationMilliseconds: item.sessionDurationMilliseconds,
sessionPauseMilliseconds: item.sessionPauseMilliseconds
}))
const getCategoryRulesVersion = (categoryId: string) => {
@@ -456,26 +465,65 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
id: item.ruleId,
extraTime: item.applyToExtraTimeUsage,
dayMask: item.dayMaskAsBitmask,
maxTime: item.maximumTimeInMillis
maxTime: item.maximumTimeInMillis,
start: item.startMinuteOfDay,
end: item.endMinuteOfDay,
session: item.sessionDurationMilliseconds,
pause: item.sessionPauseMilliseconds
})),
version: getCategoryRulesVersion(categoryId)
}))
}
if (categoryIdsToSyncUsedTimes.length > 0) {
const dataForSyncing = (await database.usedTime.findAll({
const usedTimesForSyncing = (await database.usedTime.findAll({
where: {
familyId,
categoryId: {
[Sequelize.Op.in]: categoryIdsToSyncUsedTimes
},
...(clientStatus.clientLevel === undefined || clientStatus.clientLevel < 2) ? {
startMinuteOfDay: MinuteOfDay.MIN,
endMinuteOfDay: MinuteOfDay.MAX
} : {}
},
attributes: [
'categoryId', 'dayOfEpoch', 'usedTime', 'startMinuteOfDay', 'endMinuteOfDay'
],
transaction
})).map((item) => ({
categoryId: item.categoryId,
dayOfEpoch: item.dayOfEpoch,
usedTime: item.usedTime,
startMinuteOfDay: item.startMinuteOfDay,
endMinuteOfDay: item.endMinuteOfDay
}))
const sessionDurationsForSyncing = (await database.sessionDuration.findAll({
where: {
familyId,
categoryId: {
[Sequelize.Op.in]: categoryIdsToSyncUsedTimes
}
},
attributes: ['categoryId', 'dayOfEpoch', 'usedTime'],
attributes: [
'categoryId',
'maxSessionDuration',
'sessionPauseDuration',
'startMinuteOfDay',
'endMinuteOfDay',
'lastUsage',
'lastSessionDuration'
],
transaction
})).map((item) => ({
categoryId: item.categoryId,
dayOfEpoch: item.dayOfEpoch,
usedTime: item.usedTime
maxSessionDuration: item.maxSessionDuration,
sessionPauseDuration: item.sessionPauseDuration,
startMinuteOfDay: item.startMinuteOfDay,
endMinuteOfDay: item.endMinuteOfDay,
lastUsage: item.lastUsage,
lastSessionDuration: item.lastSessionDuration
}))
const getCategoryUsedTimesVersion = (categoryId: string) => {
@@ -490,9 +538,19 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
result.usedTimes = categoryIdsToSyncUsedTimes.map((categoryId): ServerUpdatedCategoryUsedTimes => ({
categoryId,
times: dataForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
times: usedTimesForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
day: item.dayOfEpoch,
time: item.usedTime
time: item.usedTime,
start: item.startMinuteOfDay,
end: item.endMinuteOfDay
})),
sessionDurations: sessionDurationsForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
md: item.maxSessionDuration,
spd: item.sessionPauseDuration,
sm: item.startMinuteOfDay,
em: item.endMinuteOfDay,
l: parseInt(item.lastUsage, 10),
d: item.lastSessionDuration
})),
version: getCategoryUsedTimesVersion(categoryId)
}))
+50 -5
View File
@@ -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,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { MinuteOfDay } from '../util/minuteofday'
import { assertIdWithinFamily } from '../util/token'
export class TimelimitRule {
@@ -23,19 +24,34 @@ export class TimelimitRule {
readonly maxTimeInMillis: number
readonly dayMask: number // stored as bitmask
readonly applyToExtraTimeUsage: boolean
readonly start: number
readonly end: number
readonly sessionDurationMilliseconds: number
readonly sessionPauseMilliseconds: number
constructor ({ ruleId, categoryId, maxTimeInMillis, dayMask, applyToExtraTimeUsage }: {
constructor ({
ruleId, categoryId, maxTimeInMillis, dayMask, applyToExtraTimeUsage,
start, end, sessionDurationMilliseconds, sessionPauseMilliseconds
}: {
ruleId: string
categoryId: string
maxTimeInMillis: number
dayMask: number
applyToExtraTimeUsage: boolean
start: number
end: number
sessionDurationMilliseconds: number
sessionPauseMilliseconds: number
}) {
this.ruleId = ruleId
this.categoryId = categoryId
this.maxTimeInMillis = maxTimeInMillis
this.dayMask = dayMask
this.applyToExtraTimeUsage = applyToExtraTimeUsage
this.start = start
this.end = end
this.sessionDurationMilliseconds = sessionDurationMilliseconds
this.sessionPauseMilliseconds = sessionPauseMilliseconds
assertIdWithinFamily(ruleId)
assertIdWithinFamily(categoryId)
@@ -51,6 +67,23 @@ export class TimelimitRule {
)) {
throw new Error('invalid day mask')
}
if (
(!Number.isSafeInteger(start)) ||
(!Number.isSafeInteger(end)) ||
(!Number.isSafeInteger(sessionDurationMilliseconds)) ||
(!Number.isSafeInteger(sessionPauseMilliseconds))
) {
throw new Error()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
throw new Error()
}
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
throw new Error()
}
}
serialize = (): SerializedTimeLimitRule => ({
@@ -58,16 +91,24 @@ export class TimelimitRule {
categoryId: this.categoryId,
time: this.maxTimeInMillis,
days: this.dayMask,
extraTime: this.applyToExtraTimeUsage
extraTime: this.applyToExtraTimeUsage,
start: this.start,
end: this.end,
pause: this.sessionPauseMilliseconds,
dur: this.sessionDurationMilliseconds
})
static parse = ({ ruleId, categoryId, time, days, extraTime }: SerializedTimeLimitRule) => (
static parse = ({ ruleId, categoryId, time, days, extraTime, start, end, dur, pause }: SerializedTimeLimitRule) => (
new TimelimitRule({
ruleId,
categoryId,
maxTimeInMillis: time,
dayMask: days,
applyToExtraTimeUsage: extraTime
applyToExtraTimeUsage: extraTime,
start: start ?? MinuteOfDay.MIN,
end: end ?? MinuteOfDay.MAX,
sessionDurationMilliseconds: dur ?? 0,
sessionPauseMilliseconds: pause ?? 0
})
)
}
@@ -78,4 +119,8 @@ export interface SerializedTimeLimitRule {
time: number
days: number
extraTime: boolean
start?: number
end?: number
dur?: number
pause?: number
}
+2 -1
View File
@@ -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
@@ -20,6 +20,7 @@ export interface ClientDataStatus {
apps: {[key: string]: string} // installedAppsVersionsByDeviceId
categories: {[key: string]: CategoryDataStatus}
users: string // userListVersion
clientLevel?: number
}
export interface CategoryDataStatus {
+36
View File
@@ -121,12 +121,44 @@ export interface ServerUpdatedCategoryAssignedApps {
export interface ServerUpdatedCategoryUsedTimes {
categoryId: string
times: Array<ServerUsedTimeItem>
sessionDurations: Array<ServerSessionDurationItem>
version: string
}
export interface ServerUsedTimeItem {
day: number // day of epoch
time: number // in milliseconds
start: number
end: number
}
export interface ServerSessionDurationItem {
/**
* the maximum duration of a session (maxSessionDuration)
*/
md: number
/**
* the pause duration after a session (sessionPauseDuration)
*/
spd: number
/**
* the start minute of the day of the session/ the rule
* which created this session (startMinuteOfDay)
*/
sm: number
/**
* the end minute of the day of the session/ the rule
* which created this session (endMinuteOfDay)
*/
em: number
/**
* the timestamp of the last usage of this session (lastUsage)
*/
l: number
/**
* the duration of the last/ current session (lastSessionDuration)
*/
d: number
}
export interface ServerUpdatedTimeLimitRules {
@@ -140,6 +172,10 @@ export interface ServerTimeLimitRule {
extraTime: boolean // applyToExtraTimeUsage
dayMask: number // as binary bitmask
maxTime: number // maximumTimeInMillis
start: number // startMinuteOfDay
end: number // endMinuteOfDay
session: number // maximum session duration
pause: number // session pause duration
}
export interface ServerInstalledAppsData {
+22
View File
@@ -0,0 +1,22 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
export const MinuteOfDay = {
MIN: 0,
MAX: 24 * 60 - 1,
LENGTH: 24 * 60
}
+36 -3
View File
@@ -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
@@ -45,7 +45,7 @@ async function deleteOldUsedTimes ({ database }: {
await database.transaction(async (transaction) => {
// get matching categories
const categoriesToCleanUp = await database.usedTime.findAll({
const categoriesToCleanUpOne = await database.usedTime.findAll({
transaction,
where: {
lastUpdate: {
@@ -56,12 +56,33 @@ async function deleteOldUsedTimes ({ database }: {
'familyId',
'categoryId'
],
limit: 100
limit: 1000,
order: [['lastUpdate', 'ASC']]
}).map((item) => ({
familyId: item.familyId,
categoryId: item.categoryId
}))
const categoriesToCleanUpTwo = await database.sessionDuration.findAll({
transaction,
where: {
roundedLastUpdate: {
[Sequelize.Op.lt]: (now - 1000 * 60 * 60 * 24 * 3 /* 3 days */).toString()
}
},
attributes: [
'familyId',
'categoryId'
],
limit: 1000,
order: [['roundedLastUpdate', 'ASC']]
}).map((item) => ({
familyId: item.familyId,
categoryId: item.categoryId
}))
const categoriesToCleanUp = [ ...categoriesToCleanUpOne, ...categoriesToCleanUpTwo ]
const distinctCategoriesToCleanUp = uniqBy(categoriesToCleanUp, (item) => item.familyId + '_' + item.categoryId)
if (distinctCategoriesToCleanUp.length > 0) {
@@ -78,6 +99,18 @@ async function deleteOldUsedTimes ({ database }: {
}
})
await database.sessionDuration.destroy({
transaction,
where: {
[Sequelize.Op.or]: (
distinctCategoriesToCleanUp
),
roundedLastUpdate: {
[Sequelize.Op.lt]: (now - 1000 * 60 * 60 * 24 * 3 /* 3 days */).toString()
}
}
})
// invalidiate categories
await database.category.update({
usedTimesVersion: generateVersionId()