diff --git a/src/action/addcategoryapps.ts b/src/action/addcategoryapps.ts
index 5c212b8..28c04ba 100644
--- a/src/action/addcategoryapps.ts
+++ b/src/action/addcategoryapps.ts
@@ -15,9 +15,10 @@
* along with this program. If not, see .
*/
-import { assertNonEmptyListWithoutDuplicates } from '../util/list'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
+
+const actionType = 'AddCategoryApps'
export class AddCategoryAppsAction extends ParentAction {
readonly categoryId: string
@@ -26,8 +27,8 @@ export class AddCategoryAppsAction extends ParentAction {
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array}) {
super()
- assertIdWithinFamily(categoryId)
- assertNonEmptyListWithoutDuplicates(packageNames)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
+ assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
this.categoryId = categoryId
this.packageNames = packageNames
diff --git a/src/action/addcategorynetworkid.ts b/src/action/addcategorynetworkid.ts
index 9d6c87e..6592b51 100644
--- a/src/action/addcategorynetworkid.ts
+++ b/src/action/addcategorynetworkid.ts
@@ -16,9 +16,11 @@
*/
import { anonymizedNetworkIdLength } from '../database/categorynetworkid'
-import { assertIsHexString } from '../util/hexstring'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertHexString, assertIdWithinFamily } from './meta/util'
+
+const actionType = 'AddCategoryNetworkIdAction'
export class AddCategoryNetworkIdAction extends ParentAction {
readonly categoryId: string
@@ -32,10 +34,16 @@ export class AddCategoryNetworkIdAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
- assertIdWithinFamily(itemId)
- assertIsHexString(hashedNetworkId)
- if (hashedNetworkId.length !== anonymizedNetworkIdLength) throw new Error('wrong network id length')
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
+ assertIdWithinFamily({ actionType, field: 'itemId', value: itemId })
+ assertHexString({ actionType, field: 'hashedNetworkId', value: hashedNetworkId })
+
+ if (hashedNetworkId.length !== anonymizedNetworkIdLength) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'wrong network id length'
+ })
+ }
this.categoryId = categoryId
this.itemId = itemId
diff --git a/src/action/addinstalledapps.ts b/src/action/addinstalledapps.ts
index 48f731b..666113f 100644
--- a/src/action/addinstalledapps.ts
+++ b/src/action/addinstalledapps.ts
@@ -16,8 +16,10 @@
*/
import { InstalledApp, SerializedInstalledApp } from '../model/installedapp'
-import { assertNonEmptyListWithoutDuplicates } from '../util/list'
import { AppLogicAction } from './basetypes'
+import { assertNonEmptyListWithoutDuplicates } from './meta/util'
+
+const actionType = 'AddInstalledAppsAction'
export class AddInstalledAppsAction extends AppLogicAction {
readonly apps: Array
@@ -25,7 +27,7 @@ export class AddInstalledAppsAction extends AppLogicAction {
constructor ({ apps }: {apps: Array}) {
super()
- assertNonEmptyListWithoutDuplicates(apps.map((app) => app.packageName))
+ assertNonEmptyListWithoutDuplicates({ actionType, field: 'apps', list: apps.map((app) => app.packageName) })
this.apps = apps
}
diff --git a/src/action/addusedtime.ts b/src/action/addusedtime.ts
index 650db00..59da4b2 100644
--- a/src/action/addusedtime.ts
+++ b/src/action/addusedtime.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { AppLogicAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'AddUsedTimeAction'
export class AddUsedTimeAction extends AppLogicAction {
readonly categoryId: string
@@ -32,18 +35,30 @@ export class AddUsedTimeAction extends AppLogicAction {
}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) {
- throw new Error('illegal dayOfEpoch')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid dayOfEpoch',
+ dynamicMessage: 'invalid dayOfEpoch: ' + dayOfEpoch
+ })
}
if (timeToAdd < 0 || (!Number.isSafeInteger(timeToAdd))) {
- throw new Error('illegal timeToAdd')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'illegal timeToAdd',
+ dynamicMessage: 'illegal timeToAdd: ' + timeToAdd
+ })
}
if (extraTimeToSubtract < 0 || (!Number.isSafeInteger(extraTimeToSubtract))) {
- throw new Error('illegal extra time to subtract')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'illegal extra time to subtract',
+ dynamicMessage: 'illegal extra time to subtract: ' + extraTimeToSubtract
+ })
}
this.categoryId = categoryId
diff --git a/src/action/addusedtime2.ts b/src/action/addusedtime2.ts
index f7a4387..1bd9701 100644
--- a/src/action/addusedtime2.ts
+++ b/src/action/addusedtime2.ts
@@ -15,10 +15,12 @@
* along with this program. If not, see .
*/
-import { uniq } from 'lodash'
import { MinuteOfDay } from '../util/minuteofday'
-import { assertIdWithinFamily } from '../util/token'
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
@@ -44,46 +46,54 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
}) {
super()
- if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) {
- throw new Error('illegal dayOfEpoch')
+ assertSafeInteger({ actionType, field: 'dayOfEpoch', value: dayOfEpoch })
+
+ if (dayOfEpoch < 0) {
+ throwOutOfRange({ actionType, field: 'dayOfEpoch', value: dayOfEpoch })
}
- if (trustedTimestamp < 0 || (!Number.isSafeInteger(trustedTimestamp))) {
- throw new Error('illegal trustedTimestamp')
+ assertSafeInteger({ actionType, field: 'trustedTimestamp', value: trustedTimestamp })
+
+ if (trustedTimestamp < 0) {
+ throwOutOfRange({ actionType, field: 'trustedTimestamp', value: trustedTimestamp })
}
if (items.length === 0) {
- throw new Error('missing items')
+ throw new InvalidActionParameterException({ actionType, staticMessage: 'no items' })
}
- if (items.length !== uniq(items.map((item) => item.categoryId)).length) {
- throw new Error('duplicate category ids')
- }
+ assertListWithoutDuplicates({
+ actionType,
+ field: 'categoryIds',
+ list: items.map((item) => item.categoryId)
+ })
items.forEach((item) => {
- assertIdWithinFamily(item.categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: item.categoryId })
- if (item.timeToAdd < 0 || (!Number.isSafeInteger(item.timeToAdd))) {
- throw new Error('illegal timeToAdd')
+ assertSafeInteger({ actionType, field: 'timeToAdd', value: item.timeToAdd })
+
+ if (item.timeToAdd < 0) {
+ throwOutOfRange({ actionType, field: 'timeToAdd', value: item.timeToAdd })
}
- if (item.extraTimeToSubtract < 0 || (!Number.isSafeInteger(item.extraTimeToSubtract))) {
- throw new Error('illegal extra time to subtract')
+ assertSafeInteger({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract })
+
+ if (item.extraTimeToSubtract < 0) {
+ throwOutOfRange({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract })
}
- if (
- uniq(item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))).length !==
- item.additionalCountingSlots.length
- ) {
- throw new Error()
- }
+ assertListWithoutDuplicates({
+ actionType,
+ field: 'additionalCountingSlots',
+ list: item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))
+ })
- if (
- uniq(item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))).length !==
- item.sessionDurationLimits.length
- ) {
- throw new Error()
- }
+ assertListWithoutDuplicates({
+ actionType,
+ field: 'sessionDurationLimits',
+ list: item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))
+ })
})
this.dayOfEpoch = dayOfEpoch
@@ -111,16 +121,15 @@ class AddUsedTimeActionItemAdditionalCountingSlot {
readonly end: number
constructor ({ start, end }: { start: number, end: number }) {
- if ((!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end))) {
- throw new Error()
- }
+ assertSafeInteger({ actionType, field: 'start', value: start })
+ assertSafeInteger({ actionType, field: 'end', value: end })
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
- throw new Error()
+ throw new InvalidActionParameterException({ actionType, staticMessage: 'start or end out of range' })
}
if (start === MinuteOfDay.MIN && end === MinuteOfDay.MAX) {
- throw new Error()
+ throw new InvalidActionParameterException({ actionType, staticMessage: 'couting slot can not fill the whole day' })
}
this.start = start
@@ -139,19 +148,17 @@ class AddUsedTimeActionItemSessionDurationLimitSlot {
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()
- }
+ 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 Error()
+ throw new InvalidActionParameterException({ actionType, staticMessage: 'start or end out of range' })
}
if (duration <= 0 || pause <= 0) {
- throw new Error()
+ throw new InvalidActionParameterException({ actionType, staticMessage: 'duration and pause must not be zero or smaller' })
}
this.start = start
diff --git a/src/action/adduser.ts b/src/action/adduser.ts
index 28deca6..2339b1b 100644
--- a/src/action/adduser.ts
+++ b/src/action/adduser.ts
@@ -15,9 +15,12 @@
* along with this program. If not, see .
*/
-import { assertParentPasswordValid, ParentPassword } from '../api/schema'
-import { assertIdWithinFamily } from '../util/token'
+import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'AddUserAction'
export class AddUserAction extends ParentAction {
readonly userId: string
@@ -35,7 +38,7 @@ export class AddUserAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(userId)
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId
this.name = name
@@ -45,12 +48,24 @@ export class AddUserAction extends ParentAction {
if (userType === 'parent') {
if (!password) {
- throw new Error('parent users must have got an password')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'parent users must have got an password'
+ })
}
}
if (password) {
- assertParentPasswordValid(password)
+ try {
+ assertParentPasswordValid(password)
+ } catch (ex) {
+ if (ex instanceof ParentPasswordValidationException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid password data'
+ })
+ } else throw ex
+ }
}
}
diff --git a/src/action/changeparentpassword.ts b/src/action/changeparentpassword.ts
index 4ef63ea..875ab6d 100644
--- a/src/action/changeparentpassword.ts
+++ b/src/action/changeparentpassword.ts
@@ -16,9 +16,11 @@
*/
import { createDecipheriv, createHash } from 'crypto'
-import { assertIsHexString } from '../util/hexstring'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertHexString, assertIdWithinFamily } from './meta/util'
+
+const actionType = 'ChangeParentPasswordAction'
export class ChangeParentPasswordAction extends ParentAction {
readonly parentUserId: string
@@ -36,7 +38,7 @@ export class ChangeParentPasswordAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(parentUserId)
+ assertIdWithinFamily({ actionType, field: 'parentUserId', value: parentUserId })
if (
(!parentUserId) ||
@@ -45,15 +47,25 @@ export class ChangeParentPasswordAction extends ParentAction {
(!newPasswordSecondHashEncrypted) ||
(!integrity)
) {
- throw new Error('missing required parameter for change parent password')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'missing required parameter for change parent password'
+ })
}
if (integrity.length !== 128) {
- throw new Error('wrong length of integrity data')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'wrong length of integrity data'
+ })
}
- assertIsHexString(newPasswordSecondHashEncrypted)
- assertIsHexString(integrity)
+ assertHexString({ actionType, field: 'newPasswordSecondHashEncrypted', value: newPasswordSecondHashEncrypted })
+ assertHexString({ actionType, field: 'integrity', value: integrity })
+
+ if (newPasswordSecondHashEncrypted.length <= 70) {
+ throw new InvalidActionParameterException({ actionType, staticMessage: 'wrong length of the new password' })
+ }
this.parentUserId = parentUserId
this.newPasswordFirstHash = newPasswordFirstHash
@@ -82,15 +94,11 @@ export class ChangeParentPasswordAction extends ParentAction {
const expected = createHash('sha512').update(integrityData).digest('hex')
if (expected !== this.integrity) {
- throw new Error('invalid integrity for change parent password action')
+ throw new InvalidChangeParentPasswordIntegrityException()
}
}
- decryptSecondHash ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) {
- if (this.newPasswordSecondHashEncrypted.length <= 70) {
- throw new Error('wrong length of the new password')
- }
-
+ decryptSecondHash ({ oldPasswordSecondHash }: { oldPasswordSecondHash: string }) {
const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32)
const salt = this.newPasswordSecondHashEncrypted.substring(32, 64)
const encryptedData = this.newPasswordSecondHashEncrypted.substring(64)
@@ -115,3 +123,7 @@ export interface SerializedChangeParentPasswordAction {
secondHashEncrypted: string
integrity: string
}
+
+export class InvalidChangeParentPasswordIntegrityException extends Error {
+ constructor () { super('invalid integrity for change parent password action') }
+}
diff --git a/src/action/childchangepassword.ts b/src/action/childchangepassword.ts
index e5156c7..cb36999 100644
--- a/src/action/childchangepassword.ts
+++ b/src/action/childchangepassword.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertParentPasswordValid, ParentPassword } from '../api/schema'
+import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
import { ChildAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+
+const actionType = 'ChildChangePasswordAction'
export class ChildChangePasswordAction extends ChildAction {
readonly password: ParentPassword
@@ -26,7 +29,16 @@ export class ChildChangePasswordAction extends ChildAction {
}) {
super()
- assertParentPasswordValid(password)
+ try {
+ assertParentPasswordValid(password)
+ } catch (ex) {
+ if (ex instanceof ParentPasswordValidationException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid password'
+ })
+ } else throw ex
+ }
this.password = password
}
diff --git a/src/action/childsignin.ts b/src/action/childsignin.ts
index 4b1538a..5fe208f 100644
--- a/src/action/childsignin.ts
+++ b/src/action/childsignin.ts
@@ -22,7 +22,7 @@ export class ChildSignInAction extends ChildAction {
super()
}
- static parse = (action: SerializedChildSignInAction) => (
+ static parse = (_: SerializedChildSignInAction) => (
new ChildSignInAction()
)
}
diff --git a/src/action/createcategory.ts b/src/action/createcategory.ts
index aa57e79..6db72ce 100644
--- a/src/action/createcategory.ts
+++ b/src/action/createcategory.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'CreateCategoryAction'
export class CreateCategoryAction extends ParentAction {
readonly categoryId: string
@@ -26,8 +28,8 @@ export class CreateCategoryAction extends ParentAction {
constructor ({ categoryId, childId, title }: {categoryId: string, childId: string, title: string}) {
super()
- assertIdWithinFamily(categoryId)
- assertIdWithinFamily(childId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
+ assertIdWithinFamily({ actionType, field: 'childId', value: childId })
this.categoryId = categoryId
this.childId = childId
diff --git a/src/action/createtimelimitrule.ts b/src/action/createtimelimitrule.ts
index 0a6514d..9b91a94 100644
--- a/src/action/createtimelimitrule.ts
+++ b/src/action/createtimelimitrule.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule'
+import { ParseTimeLimitRuleException, SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+
+const actionType = 'CreateTimeLimitRuleAction'
export class CreateTimeLimitRuleAction extends ParentAction {
rule: TimelimitRule
@@ -27,11 +30,21 @@ export class CreateTimeLimitRuleAction extends ParentAction {
this.rule = rule
}
- static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => (
- new CreateTimeLimitRuleAction({
- rule: TimelimitRule.parse(rule)
- })
- )
+ static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => {
+ try {
+ return new CreateTimeLimitRuleAction({
+ rule: TimelimitRule.parse(rule)
+ })
+ } catch (ex) {
+ if (ex instanceof ParseTimeLimitRuleException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid time limit rule',
+ dynamicMessage: 'invalid time limit rule: ' + ex.toString()
+ })
+ } else throw ex
+ }
+ }
}
export interface SerializedCreateTimelimtRuleAction {
diff --git a/src/action/deletecategory.ts b/src/action/deletecategory.ts
index 32ac423..a32a27f 100644
--- a/src/action/deletecategory.ts
+++ b/src/action/deletecategory.ts
@@ -15,16 +15,18 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'DeleteCategoryAction'
export class DeleteCategoryAction extends ParentAction {
readonly categoryId: string
- constructor ({ categoryId }: {categoryId: string}) {
+ constructor ({ categoryId }: { categoryId: string }) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId
}
diff --git a/src/action/deletetimelimitrule.ts b/src/action/deletetimelimitrule.ts
index 8bb03c3..8638c3a 100644
--- a/src/action/deletetimelimitrule.ts
+++ b/src/action/deletetimelimitrule.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'DeleteTimeLimitRuleAction'
export class DeleteTimeLimitRuleAction extends ParentAction {
readonly ruleId: string
@@ -24,7 +26,7 @@ export class DeleteTimeLimitRuleAction extends ParentAction {
constructor ({ ruleId }: {ruleId: string}) {
super()
- assertIdWithinFamily(ruleId)
+ assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
this.ruleId = ruleId
}
diff --git a/src/action/ignoremanipulation.ts b/src/action/ignoremanipulation.ts
index bd84330..ce7d79b 100644
--- a/src/action/ignoremanipulation.ts
+++ b/src/action/ignoremanipulation.ts
@@ -16,8 +16,10 @@
*/
import { DeviceHadManipulationFlags } from '../database/device'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'IgnoreManipulationAction'
export class IgnoreManipulationAction extends ParentAction {
readonly deviceId: string
@@ -51,13 +53,14 @@ export class IgnoreManipulationAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
+
+ assertSafeInteger({ actionType, field: 'ignoreHadManipulationFlags', value: ignoreHadManipulationFlags })
if (
- (!Number.isSafeInteger(ignoreHadManipulationFlags)) ||
ignoreHadManipulationFlags < 0 || ignoreHadManipulationFlags > DeviceHadManipulationFlags.ALL
) {
- throw new Error('invalid ignoreHadManipulationFlags')
+ throwOutOfRange({ actionType, field: 'ignoreHadManipulationFlags', value: ignoreHadManipulationFlags })
}
this.deviceId = deviceId
diff --git a/src/action/incrementcategoryextratime.ts b/src/action/incrementcategoryextratime.ts
index 91ade10..0c24dd2 100644
--- a/src/action/incrementcategoryextratime.ts
+++ b/src/action/incrementcategoryextratime.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'IncrementCategoryExtraTimeAction'
export class IncrementCategoryExtraTimeAction extends ParentAction {
readonly categoryId: string
@@ -26,14 +28,18 @@ export class IncrementCategoryExtraTimeAction extends ParentAction {
constructor ({ categoryId, addedExtraTime, day }: {categoryId: string, addedExtraTime: number, day: number}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
- if (addedExtraTime <= 0 || (!Number.isSafeInteger(addedExtraTime))) {
- throw new Error('must add some extra time with IncrementCategoryExtraTimeAction')
+ assertSafeInteger({ actionType, field: 'addedExtraTime', value: addedExtraTime })
+
+ if (addedExtraTime < 0) {
+ throwOutOfRange({ actionType, field: 'addedExtraTime', value: addedExtraTime })
}
- if (day < -1 || (!Number.isSafeInteger(day))) {
- throw Error('day must be valid')
+ assertSafeInteger({ actionType, field: 'day', value: day })
+
+ if (day < -1) {
+ throwOutOfRange({ actionType, field: 'day', value: day })
}
this.categoryId = categoryId
diff --git a/src/action/meta/exception.ts b/src/action/meta/exception.ts
new file mode 100644
index 0000000..d56f651
--- /dev/null
+++ b/src/action/meta/exception.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { StaticMessageException } from '../../exception'
+
+export class InvalidActionParameterException extends StaticMessageException {
+ constructor ({ actionType, staticMessage, dynamicMessage }: {
+ actionType: string
+ staticMessage: string
+ dynamicMessage?: string
+ }) {
+ super({
+ staticMessage: 'invalid action paramters:' + actionType + ':' + staticMessage,
+ dynamicMessage: dynamicMessage ? 'invalid action paramters:' + actionType + ':' + dynamicMessage : undefined
+ })
+ }
+}
+
+export class UnknownActionTypeException extends InvalidActionParameterException {
+ constructor ({ group }: { group: string }) {
+ super({
+ actionType: group,
+ staticMessage: 'unknown action type'
+ })
+ }
+}
diff --git a/src/action/meta/util.ts b/src/action/meta/util.ts
new file mode 100644
index 0000000..ec5fb6e
--- /dev/null
+++ b/src/action/meta/util.ts
@@ -0,0 +1,104 @@
+/*
+ * 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 { checkIfHexString } from '../../util/hexstring'
+import { hasDuplicates } from '../../util/list'
+import { isIdWithinFamily } from '../../util/token'
+import { InvalidActionParameterException } from './exception'
+
+export const assertIdWithinFamily = ({ value, actionType, field }: {
+ value: string
+ actionType: string
+ field: string
+}) => {
+ if (!isIdWithinFamily(value)) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid id within family for ' + field,
+ dynamicMessage: 'invalid id within family for ' + field + ': ' + value
+ })
+ }
+}
+
+export const assertHexString = ({ value, actionType, field }: {
+ value: string
+ actionType: string
+ field: string
+}) => {
+ if (!checkIfHexString(value)) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid hex string for ' + field,
+ dynamicMessage: 'invalid hex string for ' + field + ': ' + value
+ })
+ }
+}
+
+export const assertSafeInteger = ({ value, actionType, field }: {
+ value: number
+ actionType: string
+ field: string
+}) => {
+ if (!Number.isSafeInteger(value)) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'require number for ' + field,
+ dynamicMessage: 'require number for ' + field + ': ' + value
+ })
+ }
+}
+
+export const throwOutOfRange = ({ value, actionType, field }: {
+ value: number
+ actionType: string
+ field: string
+}) => {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: field + ' out of range',
+ dynamicMessage: field + ' out of range: ' + value
+ })
+}
+
+export function assertNonEmptyListWithoutDuplicates ({ list, actionType, field }: {
+ list: Array
+ actionType: string
+ field: string
+}) {
+ assertListWithoutDuplicates({ list, actionType, field })
+
+ if (hasDuplicates(list)) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'empty list for ' + field
+ })
+ }
+}
+
+export function assertListWithoutDuplicates ({ list, actionType, field }: {
+ list: Array
+ actionType: string
+ field: string
+}) {
+ if (hasDuplicates(list)) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'list has duplicates for ' + field,
+ dynamicMessage: 'list has duplicates for ' + field + ': ' + list.join(';')
+ })
+ }
+}
diff --git a/src/action/removecategoryapps.ts b/src/action/removecategoryapps.ts
index ea6e2a5..b4f5835 100644
--- a/src/action/removecategoryapps.ts
+++ b/src/action/removecategoryapps.ts
@@ -15,9 +15,10 @@
* along with this program. If not, see .
*/
-import { assertNonEmptyListWithoutDuplicates } from '../util/list'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
+
+const actionType = 'RemoveCategoryAppsAction'
export class RemoveCategoryAppsAction extends ParentAction {
readonly categoryId: string
@@ -26,8 +27,8 @@ export class RemoveCategoryAppsAction extends ParentAction {
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array}) {
super()
- assertIdWithinFamily(categoryId)
- assertNonEmptyListWithoutDuplicates(packageNames)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
+ assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
this.categoryId = categoryId
this.packageNames = packageNames
diff --git a/src/action/removeinstalledapps.ts b/src/action/removeinstalledapps.ts
index 84c924e..0e45b5c 100644
--- a/src/action/removeinstalledapps.ts
+++ b/src/action/removeinstalledapps.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertNonEmptyListWithoutDuplicates } from '../util/list'
import { AppLogicAction } from './basetypes'
+import { assertNonEmptyListWithoutDuplicates } from './meta/util'
+
+const actionType = 'RemoveInstalledAppsAction'
export class RemoveInstalledAppsAction extends AppLogicAction {
readonly packageNames: Array
@@ -24,7 +26,7 @@ export class RemoveInstalledAppsAction extends AppLogicAction {
constructor ({ packageNames }: {packageNames: Array}) {
super()
- assertNonEmptyListWithoutDuplicates(packageNames)
+ assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
this.packageNames = packageNames
}
diff --git a/src/action/removeuser.ts b/src/action/removeuser.ts
index b64871d..449fb52 100644
--- a/src/action/removeuser.ts
+++ b/src/action/removeuser.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'RemoveUserAction'
export class RemoveUserAction extends ParentAction {
readonly userId: string
@@ -31,7 +33,7 @@ export class RemoveUserAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(userId)
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId
this.authentication = authentication
diff --git a/src/action/renamechild.ts b/src/action/renamechild.ts
index 88ec04b..5b1be60 100644
--- a/src/action/renamechild.ts
+++ b/src/action/renamechild.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'RenameChildAction'
export class RenameChildAction extends ParentAction {
readonly childId: string
@@ -28,10 +31,13 @@ export class RenameChildAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(childId)
+ assertIdWithinFamily({ actionType, field: 'childId', value: childId })
if (newName === '') {
- throw new Error('new name must not be empty')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'new name must not be empty'
+ })
}
this.childId = childId
diff --git a/src/action/resetcategorynetworkids.ts b/src/action/resetcategorynetworkids.ts
index 6c88c2d..cc6c8de 100644
--- a/src/action/resetcategorynetworkids.ts
+++ b/src/action/resetcategorynetworkids.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'ResetCategoryNetworkIdsAction'
export class ResetCategoryNetworkIdsAction extends ParentAction {
readonly categoryId: string
@@ -26,7 +28,7 @@ export class ResetCategoryNetworkIdsAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId
}
diff --git a/src/action/resetparentblockedtimes.ts b/src/action/resetparentblockedtimes.ts
index 754975f..690095c 100644
--- a/src/action/resetparentblockedtimes.ts
+++ b/src/action/resetparentblockedtimes.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'ResetParentBlockedTimesAction'
export class ResetParentBlockedTimesAction extends ParentAction {
readonly parentId: string
@@ -26,7 +28,7 @@ export class ResetParentBlockedTimesAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(parentId)
+ assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
this.parentId = parentId
}
diff --git a/src/action/serialization/applogicaction.ts b/src/action/serialization/applogicaction.ts
index a3c4070..b464af1 100644
--- a/src/action/serialization/applogicaction.ts
+++ b/src/action/serialization/applogicaction.ts
@@ -20,6 +20,7 @@ import { AddUsedTimeAction, SerializedAddUsedTimeAction } from '../addusedtime'
import { AddUsedTimeActionVersion2, SerializedAddUsedTimeActionVersion2 } from '../addusedtime2'
import { AppLogicAction } from '../basetypes'
import { ForceSyncAction, SerializedForceSyncAction } from '../forcesync'
+import { UnknownActionTypeException } from '../meta/exception'
import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps'
import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice'
import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin'
@@ -57,6 +58,6 @@ export const parseAppLogicAction = (serialized: SerializedAppLogicAction): AppLo
} else if (serialized.type === 'UPDATE_DEVICE_STATUS') {
return UpdateDeviceStatusAction.parse(serialized)
} else {
- throw new Error('illegal state: unsupported type at parseAppLogicAction')
+ throw new UnknownActionTypeException({ group: 'app logic' })
}
}
diff --git a/src/action/serialization/childaction.ts b/src/action/serialization/childaction.ts
index 880568c..231fb3c 100644
--- a/src/action/serialization/childaction.ts
+++ b/src/action/serialization/childaction.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
@@ -17,6 +17,7 @@
import { ChildChangePasswordAction, SerializedChildChangePasswordAction } from '../childchangepassword'
import { ChildSignInAction, SerializedChildSignInAction } from '../childsignin'
+import { UnknownActionTypeException } from '../meta/exception'
export type SerializedChildAction = SerializedChildChangePasswordAction | SerializedChildSignInAction
@@ -26,6 +27,6 @@ export const parseChildAction = (serialized: SerializedChildAction) => {
} else if (serialized.type === 'CHILD_SIGN_IN') {
return ChildSignInAction.parse(serialized)
} else {
- throw new Error('illegal state: unsupported type at parseChildAction')
+ throw new UnknownActionTypeException({ group: 'child' })
}
}
diff --git a/src/action/serialization/parentaction.ts b/src/action/serialization/parentaction.ts
index 349ca2b..2fb7d90 100644
--- a/src/action/serialization/parentaction.ts
+++ b/src/action/serialization/parentaction.ts
@@ -26,6 +26,7 @@ import { DeleteCategoryAction, SerializedDeleteCategoryAction } from '../deletec
import { DeleteTimeLimitRuleAction, SerializedDeleteTimeLimitRuleAction } from '../deletetimelimitrule'
import { IgnoreManipulationAction, SerializedIgnoreManipulationAction } from '../ignoremanipulation'
import { IncrementCategoryExtraTimeAction, SerializedIncrementCategoryExtraTimeAction } from '../incrementcategoryextratime'
+import { UnknownActionTypeException } from '../meta/exception'
import { RemoveCategoryAppsAction, SerializedRemoveCategoryAppsAction } from '../removecategoryapps'
import { RemoveUserAction, SerializedRemoveUserAction } from '../removeuser'
import { RenameChildAction, SerializedRenameChildAction } from '../renamechild'
@@ -193,6 +194,6 @@ export const parseParentAction = (action: SerializedParentAction): ParentAction
} else if (action.type === 'UPDATE_USER_LIMIT_LOGIN_CATEGORY') {
return UpdateUserLimitLoginCategory.parse(action)
} else {
- throw new Error('illegal state: invalid type for action at parseParentAction')
+ throw new UnknownActionTypeException({ group: 'parent' })
}
}
diff --git a/src/action/setcategoryextratime.ts b/src/action/setcategoryextratime.ts
index b3f9ba9..f1e1df7 100644
--- a/src/action/setcategoryextratime.ts
+++ b/src/action/setcategoryextratime.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'SetCategoryExtraTimeAction'
export class SetCategoryExtraTimeAction extends ParentAction {
readonly categoryId: string
@@ -26,14 +28,18 @@ export class SetCategoryExtraTimeAction extends ParentAction {
constructor ({ categoryId, newExtraTime, day }: {categoryId: string, newExtraTime: number, day: number}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
- if (newExtraTime < 0 || (!Number.isSafeInteger(newExtraTime))) {
- throw Error('newExtraTime must be >= 0')
+ assertSafeInteger({ actionType, field: 'newExtraTime', value: newExtraTime })
+
+ if (newExtraTime < 0) {
+ throwOutOfRange({ actionType, field: 'newExtraTime', value: newExtraTime })
}
- if (day < -1 || (!Number.isSafeInteger(day))) {
- throw Error('day must be valid')
+ assertSafeInteger({ actionType, field: 'day', value: day })
+
+ if (day < -1) {
+ throwOutOfRange({ actionType, field: 'day', value: day })
}
this.categoryId = categoryId
diff --git a/src/action/setcategoryforunassignedapps.ts b/src/action/setcategoryforunassignedapps.ts
index 81623cd..b33ff87 100644
--- a/src/action/setcategoryforunassignedapps.ts
+++ b/src/action/setcategoryforunassignedapps.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetCategoryForUnassignedAppsAction'
export class SetCategoryForUnassignedAppsAction extends ParentAction {
readonly childId: string
@@ -28,10 +30,10 @@ export class SetCategoryForUnassignedAppsAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(childId)
+ assertIdWithinFamily({ actionType, field: 'childId', value: childId })
if (categoryId !== '') {
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
}
this.childId = childId
diff --git a/src/action/setchildpassword.ts b/src/action/setchildpassword.ts
index 4196e2f..34fcb8b 100644
--- a/src/action/setchildpassword.ts
+++ b/src/action/setchildpassword.ts
@@ -15,9 +15,12 @@
* along with this program. If not, see .
*/
-import { assertParentPasswordValid, ParentPassword } from '../api/schema'
-import { assertIdWithinFamily } from '../util/token'
+import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetChildPasswordAction'
export class SetChildPasswordAction extends ParentAction {
readonly childUserId: string
@@ -29,8 +32,18 @@ export class SetChildPasswordAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(childUserId)
- assertParentPasswordValid(newPassword)
+ assertIdWithinFamily({ actionType, field: 'childUserId', value: childUserId })
+
+ try {
+ assertParentPasswordValid(newPassword)
+ } catch (ex) {
+ if (ex instanceof ParentPasswordValidationException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid parent password'
+ })
+ } else throw ex
+ }
this.childUserId = childUserId
this.newPassword = newPassword
diff --git a/src/action/setconsiderrebootmanipulation.ts b/src/action/setconsiderrebootmanipulation.ts
index df3480b..5a6b678 100644
--- a/src/action/setconsiderrebootmanipulation.ts
+++ b/src/action/setconsiderrebootmanipulation.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetConsiderRebootManipulationAction'
export class SetConsiderRebootManipulationAction extends ParentAction {
readonly deviceId: string
@@ -25,7 +27,7 @@ export class SetConsiderRebootManipulationAction extends ParentAction {
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId
this.enable = enable
diff --git a/src/action/setdevicedefaultuser.ts b/src/action/setdevicedefaultuser.ts
index 333e214..da9dd98 100644
--- a/src/action/setdevicedefaultuser.ts
+++ b/src/action/setdevicedefaultuser.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetDeviceDefaultUserAction'
export class SetDeviceDefaultUserAction extends ParentAction {
readonly deviceId: string
@@ -28,10 +30,10 @@ export class SetDeviceDefaultUserAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
if (defaultUserId !== '') {
- assertIdWithinFamily(defaultUserId)
+ assertIdWithinFamily({ actionType, field: 'defaultUserId', value: defaultUserId })
}
this.deviceId = deviceId
diff --git a/src/action/setdevicedefaultusertimeout.ts b/src/action/setdevicedefaultusertimeout.ts
index fbb043f..af02d1d 100644
--- a/src/action/setdevicedefaultusertimeout.ts
+++ b/src/action/setdevicedefaultusertimeout.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
+
+const actionType = 'SetDeviceDefaultUserTimeoutAction'
export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
readonly deviceId: string
@@ -28,10 +31,15 @@ export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
+ assertSafeInteger({ actionType, field: 'timeout', value: timeout })
- if ((!Number.isInteger(timeout)) || (timeout < 0)) {
- throw new Error('timeout must be a non-negative integer')
+ if (timeout < 0) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'timeout must be a non-negative integer',
+ dynamicMessage: 'timeout must be a non-negative integer, was ' + timeout
+ })
}
this.deviceId = deviceId
diff --git a/src/action/setdeviceuser.ts b/src/action/setdeviceuser.ts
index 1a34c0f..3bd7fd7 100644
--- a/src/action/setdeviceuser.ts
+++ b/src/action/setdeviceuser.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetDeviceUserAction'
export class SetDeviceUserAction extends ParentAction {
readonly deviceId: string
@@ -28,10 +30,10 @@ export class SetDeviceUserAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
if (userId !== '') {
- assertIdWithinFamily(userId)
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
}
this.deviceId = deviceId
diff --git a/src/action/setkeepsignedin.ts b/src/action/setkeepsignedin.ts
index bf433ad..0395f6e 100644
--- a/src/action/setkeepsignedin.ts
+++ b/src/action/setkeepsignedin.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetKeepSignedInAction'
export class SetKeepSignedInAction extends ParentAction {
readonly deviceId: string
@@ -28,7 +30,7 @@ export class SetKeepSignedInAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId
this.keepSignedIn = keepSignedIn
diff --git a/src/action/setparentcategory.ts b/src/action/setparentcategory.ts
index 52ad0dc..012a72f 100644
--- a/src/action/setparentcategory.ts
+++ b/src/action/setparentcategory.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetParentCategoryAction'
export class SetParentCategoryAction extends ParentAction {
readonly categoryId: string
@@ -28,10 +30,10 @@ export class SetParentCategoryAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (parentCategory !== '') {
- assertIdWithinFamily(parentCategory)
+ assertIdWithinFamily({ actionType, field: 'parentCategory', value: parentCategory })
}
this.categoryId = categoryId
diff --git a/src/action/setrelaxprimarydevice.ts b/src/action/setrelaxprimarydevice.ts
index 72f2a27..b23818e 100644
--- a/src/action/setrelaxprimarydevice.ts
+++ b/src/action/setrelaxprimarydevice.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetRelaxPrimaryDeviceAction'
export class SetRelaxPrimaryDeviceAction extends ParentAction {
readonly userId: string
@@ -28,7 +30,7 @@ export class SetRelaxPrimaryDeviceAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(userId)
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId
this.relax = relax
diff --git a/src/action/setsenddeviceconnected.ts b/src/action/setsenddeviceconnected.ts
index c8dffa7..ead7da4 100644
--- a/src/action/setsenddeviceconnected.ts
+++ b/src/action/setsenddeviceconnected.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetSendDeviceConnected'
export class SetSendDeviceConnected extends ParentAction {
readonly deviceId: string
@@ -28,7 +30,7 @@ export class SetSendDeviceConnected extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId
this.enable = enable
diff --git a/src/action/setuserdisablelimitsuntil.ts b/src/action/setuserdisablelimitsuntil.ts
index cd09035..daefb53 100644
--- a/src/action/setuserdisablelimitsuntil.ts
+++ b/src/action/setuserdisablelimitsuntil.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
+
+const actionType = 'SetUserDisableLimitsUntilAction'
export class SetUserDisableLimitsUntilAction extends ParentAction {
readonly childId: string
@@ -28,10 +31,15 @@ export class SetUserDisableLimitsUntilAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(childId)
+ assertIdWithinFamily({ actionType, field: 'childId', value: childId })
+ assertSafeInteger({ actionType, field: 'timestamp', value: timestamp })
- if (timestamp < 0 || (!Number.isSafeInteger(timestamp))) {
- throw new Error('timestamp for set user disabe limits until must be >= 0')
+ if (timestamp < 0) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'timestamp for set user disabe limits until must be >= 0',
+ dynamicMessage: 'timestamp for set user disabe limits until must be >= 0, but was ' + timestamp
+ })
}
this.childId = childId
diff --git a/src/action/setusertimezone.ts b/src/action/setusertimezone.ts
index 33690e5..c718c06 100644
--- a/src/action/setusertimezone.ts
+++ b/src/action/setusertimezone.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'SetUserTimezoneAction'
export class SetUserTimezoneAction extends ParentAction {
readonly userId: string
@@ -28,7 +30,7 @@ export class SetUserTimezoneAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(userId)
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
this.userId = userId
this.timezone = timezone
diff --git a/src/action/signoutatdevice.ts b/src/action/signoutatdevice.ts
index 05b0820..773cdbd 100644
--- a/src/action/signoutatdevice.ts
+++ b/src/action/signoutatdevice.ts
@@ -24,7 +24,7 @@ export class SignOutAtDeviceAction extends AppLogicAction {
super()
}
- static parse = (action: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance
+ static parse = (_: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance
}
export interface SerializedSignOutAtDeviceAction {
diff --git a/src/action/updateappactivities.ts b/src/action/updateappactivities.ts
index 8c761a2..45d5f7a 100644
--- a/src/action/updateappactivities.ts
+++ b/src/action/updateappactivities.ts
@@ -15,9 +15,14 @@
* along with this program. If not, see .
*/
-import { AppActivityItem, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem } from '../model/appactivity'
-import { assertListWithoutDuplicates } from '../util/list'
+import {
+ AppActivityItem, IncompleteAppActivityItemException, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem
+} from '../model/appactivity'
import { AppLogicAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertListWithoutDuplicates } from './meta/util'
+
+const actionType = 'UpdateAppActivitiesAction'
export class UpdateAppActivitiesAction extends AppLogicAction {
readonly removed: Array
@@ -29,23 +34,44 @@ export class UpdateAppActivitiesAction extends AppLogicAction {
}) {
super()
- assertListWithoutDuplicates(removed.map((item) => item.packageName + ':' + item.activityName))
- assertListWithoutDuplicates(updatedOrAdded.map((item) => item.packageName + ':' + item.activityName))
+ assertListWithoutDuplicates({
+ actionType,
+ field: 'removed',
+ list: removed.map((item) => item.packageName + ':' + item.activityName)
+ })
+
+ assertListWithoutDuplicates({
+ actionType,
+ field: 'updatedOrAdded',
+ list: updatedOrAdded.map((item) => item.packageName + ':' + item.activityName)
+ })
if (removed.length === 0 && updatedOrAdded.length === 0) {
- throw new Error('UpdateAppActivitiesAction is empty')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'UpdateAppActivitiesAction is empty'
+ })
}
this.removed = removed
this.updatedOrAdded = updatedOrAdded
}
- static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => (
- new UpdateAppActivitiesAction({
- removed: removed.map((item) => RemovedAppActivityItem.parse(item)),
- updatedOrAdded: updatedOrAdded.map((item) => AppActivityItem.parse(item))
- })
- )
+ static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => {
+ try {
+ return new UpdateAppActivitiesAction({
+ removed: removed.map((item) => RemovedAppActivityItem.parse(item)),
+ updatedOrAdded: updatedOrAdded.map((item) => AppActivityItem.parse(item))
+ })
+ } catch (ex) {
+ if (ex instanceof IncompleteAppActivityItemException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid app activity item'
+ })
+ } else throw ex
+ }
+ }
}
export interface SerializedUpdateAppActivitiesAction {
diff --git a/src/action/updatecategorybatterylimit.ts b/src/action/updatecategorybatterylimit.ts
index a4362ec..b58cde4 100644
--- a/src/action/updatecategorybatterylimit.ts
+++ b/src/action/updatecategorybatterylimit.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'UpdateCategoryBatteryLimitAction'
export class UpdateCategoryBatteryLimitAction extends ParentAction {
readonly categoryId: string
@@ -30,17 +32,21 @@ export class UpdateCategoryBatteryLimitAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (chargeLimit !== undefined) {
- if ((!Number.isSafeInteger(chargeLimit)) || chargeLimit < 0 || chargeLimit > 100) {
- throw new Error('charge limit out of range')
+ assertSafeInteger({ actionType, field: 'chargeLimit', value: chargeLimit })
+
+ if (chargeLimit < 0 || chargeLimit > 100) {
+ throwOutOfRange({ actionType, field: 'chargeLimit', value: chargeLimit })
}
}
if (mobileLimit !== undefined) {
- if ((!Number.isSafeInteger(mobileLimit)) || mobileLimit < 0 || mobileLimit > 100) {
- throw new Error('mobile limit out of range')
+ assertSafeInteger({ actionType, field: 'mobileLimit', value: mobileLimit })
+
+ if (mobileLimit < 0 || mobileLimit > 100) {
+ throwOutOfRange({ actionType, field: 'mobileLimit', value: mobileLimit })
}
}
diff --git a/src/action/updatecategoryblockallnotifications.ts b/src/action/updatecategoryblockallnotifications.ts
index eec3a0a..40924e5 100644
--- a/src/action/updatecategoryblockallnotifications.ts
+++ b/src/action/updatecategoryblockallnotifications.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,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateCategoryBlockAllNotificationsAction'
export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
readonly categoryId: string
@@ -25,7 +27,7 @@ export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
constructor ({ categoryId, blocked }: {categoryId: string, blocked: boolean}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId
this.blocked = blocked
diff --git a/src/action/updatecategoryblockedtimes.ts b/src/action/updatecategoryblockedtimes.ts
index 8c79144..f60ec97 100644
--- a/src/action/updatecategoryblockedtimes.ts
+++ b/src/action/updatecategoryblockedtimes.ts
@@ -15,12 +15,15 @@
* along with this program. If not, see .
*/
-import { validateBitmask } from '../util/bitmask'
-import { assertIdWithinFamily } from '../util/token'
+import { BitmapValidationException, validateBitmask } from '../util/bitmask'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
export const blockedTimesBitmaskLength = 60 * 24 * 7 /* number of minutes per week */
+const actionType = 'UpdateCategoryBlockedTimesAction'
+
export class UpdateCategoryBlockedTimesAction extends ParentAction {
readonly categoryId: string
readonly blockedTimes: string
@@ -31,8 +34,18 @@ export class UpdateCategoryBlockedTimesAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
- validateBitmask(blockedTimes, blockedTimesBitmaskLength)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
+
+ try {
+ validateBitmask(blockedTimes, blockedTimesBitmaskLength)
+ } catch (ex) {
+ if (ex instanceof BitmapValidationException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid bitmask'
+ })
+ } else throw ex
+ }
this.categoryId = categoryId
this.blockedTimes = blockedTimes
diff --git a/src/action/updatecategorysorting.ts b/src/action/updatecategorysorting.ts
index 1605ae8..c1c1590 100644
--- a/src/action/updatecategorysorting.ts
+++ b/src/action/updatecategorysorting.ts
@@ -15,9 +15,10 @@
* along with this program. If not, see .
*/
-import { uniq } from 'lodash'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
+
+const actionType = 'UpdateCategorySortingAction'
export class UpdateCategorySortingAction extends ParentAction {
readonly categoryIds: Array
@@ -27,15 +28,13 @@ export class UpdateCategorySortingAction extends ParentAction {
}) {
super()
- if (categoryIds.length === 0) {
- throw new Error('empty category sorting list')
- }
+ assertNonEmptyListWithoutDuplicates({ actionType, field: 'categoryIds', list: categoryIds })
- if (uniq(categoryIds).length !== categoryIds.length) {
- throw new Error('category sorting list has duplicates')
- }
-
- categoryIds.forEach((categoryId) => assertIdWithinFamily(categoryId))
+ categoryIds.forEach((categoryId) => assertIdWithinFamily({
+ actionType,
+ field: 'categoryIds',
+ value: categoryId
+ }))
this.categoryIds = categoryIds
}
diff --git a/src/action/updatecategorytemporarilyblocked.ts b/src/action/updatecategorytemporarilyblocked.ts
index 4bd1a53..2ba00e4 100644
--- a/src/action/updatecategorytemporarilyblocked.ts
+++ b/src/action/updatecategorytemporarilyblocked.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
+
+const actionType = 'UpdateCategoryTemporarilyBlockedAction'
export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
readonly categoryId: string
@@ -30,15 +33,16 @@ export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
if (endTime !== undefined) {
- if (!Number.isSafeInteger(endTime)) {
- throw new Error()
- }
+ assertSafeInteger({ actionType, field: 'endTime', value: endTime })
if (!blocked) {
- throw new Error()
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'can not set a end time when disabling blocking'
+ })
}
}
diff --git a/src/action/updatecategorytimewarnings.ts b/src/action/updatecategorytimewarnings.ts
index 5b4171d..d69583e 100644
--- a/src/action/updatecategorytimewarnings.ts
+++ b/src/action/updatecategorytimewarnings.ts
@@ -16,8 +16,10 @@
*/
import { allowedTimeWarningFlags } from '../database/category'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'UpdateCategoryTimeWarningsAction'
export class UpdateCategoryTimeWarningsAction extends ParentAction {
readonly categoryId: string
@@ -31,10 +33,11 @@ export class UpdateCategoryTimeWarningsAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
+ assertSafeInteger({ actionType, field: 'flags', value: flags })
if ((flags & allowedTimeWarningFlags) !== flags) {
- throw new Error('illegal flags')
+ throwOutOfRange({ actionType, field: 'flags', value: flags })
}
this.categoryId = categoryId
diff --git a/src/action/updatecategorytitle.ts b/src/action/updatecategorytitle.ts
index 3c46454..5411968 100644
--- a/src/action/updatecategorytitle.ts
+++ b/src/action/updatecategorytitle.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateCategoryTitleAction'
export class UpdateCategoryTitleAction extends ParentAction {
readonly categoryId: string
@@ -25,7 +27,7 @@ export class UpdateCategoryTitleAction extends ParentAction {
constructor ({ categoryId, newTitle }: {categoryId: string, newTitle: string}) {
super()
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
this.categoryId = categoryId
this.newTitle = newTitle
diff --git a/src/action/updatedevicename.ts b/src/action/updatedevicename.ts
index d097714..5c72c9d 100644
--- a/src/action/updatedevicename.ts
+++ b/src/action/updatedevicename.ts
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateDeviceNameAction'
export class UpdateDeviceNameAction extends ParentAction {
readonly deviceId: string
@@ -31,10 +34,13 @@ export class UpdateDeviceNameAction extends ParentAction {
this.deviceId = deviceId
this.name = name
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
if (name.trim().length === 0) {
- throw new Error('new device name must not be blank')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'new device name must not be blank'
+ })
}
}
diff --git a/src/action/updatedevicestatus.ts b/src/action/updatedevicestatus.ts
index a5d9a77..33a0115 100644
--- a/src/action/updatedevicestatus.ts
+++ b/src/action/updatedevicestatus.ts
@@ -19,6 +19,9 @@ import { NewPermissionStatus } from '../model/newpermissionstatus'
import { ProtectionLevel } from '../model/protectionlevel'
import { RuntimePermissionStatus } from '../model/runtimepermissionstatus'
import { AppLogicAction } from './basetypes'
+import { assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'UpdateDeviceStatusAction'
export class UpdateDeviceStatusAction extends AppLogicAction {
readonly newProtetionLevel?: ProtectionLevel
@@ -52,8 +55,10 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
super()
if (newAppVersion !== undefined) {
- if (!Number.isSafeInteger(newAppVersion) || (newAppVersion < 0)) {
- throw new Error('invalid new ap version')
+ assertSafeInteger({ actionType, field: 'newAppVersion', value: newAppVersion })
+
+ if (newAppVersion < 0) {
+ throwOutOfRange({ actionType, field: 'newAppVersion', value: newAppVersion })
}
}
diff --git a/src/action/updateenableactivitylevelblocking.ts b/src/action/updateenableactivitylevelblocking.ts
index 93f4626..ae2fa4a 100644
--- a/src/action/updateenableactivitylevelblocking.ts
+++ b/src/action/updateenableactivitylevelblocking.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateEnableActivityLevelBlockingAction'
export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
readonly deviceId: string
@@ -25,7 +27,7 @@ export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId
this.enable = enable
diff --git a/src/action/updatenetworktimeverification.ts b/src/action/updatenetworktimeverification.ts
index c50808e..b74f7d4 100644
--- a/src/action/updatenetworktimeverification.ts
+++ b/src/action/updatenetworktimeverification.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateNetworkTimeVerificationAction'
export class UpdateNetworkTimeVerificationAction extends ParentAction {
readonly deviceId: string
@@ -28,7 +30,7 @@ export class UpdateNetworkTimeVerificationAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(deviceId)
+ assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
this.deviceId = deviceId
this.mode = mode
diff --git a/src/action/updateparentblockedtimes.ts b/src/action/updateparentblockedtimes.ts
index 4a346cb..ff35725 100644
--- a/src/action/updateparentblockedtimes.ts
+++ b/src/action/updateparentblockedtimes.ts
@@ -15,9 +15,12 @@
* along with this program. If not, see .
*/
-import { validateAndParseBitmask } from '../util/bitmask'
-import { assertIdWithinFamily } from '../util/token'
+import { BitmapValidationException, validateAndParseBitmask } from '../util/bitmask'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateParentBlockedTimesAction'
export class UpdateParentBlockedTimesAction extends ParentAction {
readonly parentId: string
@@ -29,9 +32,9 @@ export class UpdateParentBlockedTimesAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(parentId)
+ assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
- {
+ try {
const parsedBlockedTimes = validateAndParseBitmask(blockedTimes, 60 * 24 * 7 /* number of minutes per week */)
for (let day = 0; day < 7; day++) {
@@ -44,9 +47,19 @@ export class UpdateParentBlockedTimesAction extends ParentAction {
}
if (blockedMinutes > 60 * 18 /* 18 hours */) {
- throw new Error('too much blocked minutes per day')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'too much blocked minutes per day'
+ })
}
}
+ } catch (ex) {
+ if (ex instanceof BitmapValidationException) {
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'invalid bitmask'
+ })
+ } else throw ex
}
this.parentId = parentId
diff --git a/src/action/updateparentnotificationflags.ts b/src/action/updateparentnotificationflags.ts
index 8289d3d..329ae05 100644
--- a/src/action/updateparentnotificationflags.ts
+++ b/src/action/updateparentnotificationflags.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'UpdateParentNotificationFlagsAction'
export class UpdateParentNotificationFlagsAction extends ParentAction {
readonly parentId: string
@@ -30,14 +32,12 @@ export class UpdateParentNotificationFlagsAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(parentId)
+ assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
- if (!Number.isSafeInteger(flags)) {
- throw new Error('flags must be an integer')
- }
+ assertSafeInteger({ actionType, field: 'flags', value: flags })
if (flags < 0 || flags > 1) {
- throw new Error('flags are out of the valid range')
+ throwOutOfRange({ actionType, field: 'flags', value: flags })
}
this.parentId = parentId
diff --git a/src/action/updatetimelimitrule.ts b/src/action/updatetimelimitrule.ts
index 1ebbff9..af8e1ed 100644
--- a/src/action/updatetimelimitrule.ts
+++ b/src/action/updatetimelimitrule.ts
@@ -16,8 +16,11 @@
*/
import { MinuteOfDay } from '../util/minuteofday'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
+
+const actionType = 'UpdateTimelimitRuleAction'
export class UpdateTimelimitRuleAction extends ParentAction {
readonly ruleId: string
@@ -53,35 +56,37 @@ export class UpdateTimelimitRuleAction extends ParentAction {
this.sessionDurationMilliseconds = sessionDurationMilliseconds
this.sessionPauseMilliseconds = sessionPauseMilliseconds
- assertIdWithinFamily(ruleId)
+ assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
- if (maximumTimeInMillis < 0 || (!Number.isSafeInteger(maximumTimeInMillis))) {
- throw new Error('maximumTimeInMillis must be >= 0')
+ assertSafeInteger({ actionType, field: 'maximumTimeInMillis', value: maximumTimeInMillis })
+
+ if (maximumTimeInMillis < 0) {
+ throwOutOfRange({ actionType, field: 'maximumTimeInMillis', value: maximumTimeInMillis })
}
- if (!(
- Number.isSafeInteger(dayMask) ||
- dayMask < 0 ||
- dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)
- )) {
- throw new Error('invalid day mask')
+ assertSafeInteger({ actionType, field: 'dayMask', value: dayMask })
+
+ if (dayMask < 0 || dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)) {
+ throwOutOfRange({ actionType, field: 'dayMask', value: dayMask })
}
- if (
- (!Number.isSafeInteger(start)) ||
- (!Number.isSafeInteger(end)) ||
- (!Number.isSafeInteger(sessionDurationMilliseconds)) ||
- (!Number.isSafeInteger(sessionPauseMilliseconds))
- ) {
- throw new Error()
- }
+ assertSafeInteger({ actionType, field: 'start', value: start })
+ assertSafeInteger({ actionType, field: 'end', value: end })
+ assertSafeInteger({ actionType, field: 'sessionDurationMilliseconds', value: sessionDurationMilliseconds })
+ assertSafeInteger({ actionType, field: 'sessionPauseMilliseconds', value: sessionPauseMilliseconds })
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
- throw new Error()
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'time slot out of range'
+ })
}
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
- throw new Error()
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'session duration lesser than zero'
+ })
}
}
diff --git a/src/action/updateuserflags.ts b/src/action/updateuserflags.ts
index 8c5f3d1..ad4d8da 100644
--- a/src/action/updateuserflags.ts
+++ b/src/action/updateuserflags.ts
@@ -16,8 +16,11 @@
*/
import { UserFlags } from '../model/userflags'
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { InvalidActionParameterException } from './meta/exception'
+import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
+
+const actionType = 'UpdateUserFlagsAction'
export class UpdateUserFlagsAction extends ParentAction {
readonly userId: string
@@ -31,14 +34,16 @@ export class UpdateUserFlagsAction extends ParentAction {
}) {
super()
- assertIdWithinFamily(userId)
-
- if ((!Number.isSafeInteger(modifiedBits)) || (!Number.isSafeInteger(newValues))) {
- throw new Error('flags must be an integer')
- }
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
+ assertSafeInteger({ actionType, field: 'modifiedBits', value: modifiedBits })
+ assertSafeInteger({ actionType, field: 'newValues', value: newValues })
if ((modifiedBits | UserFlags.ALL_FLAGS) !== UserFlags.ALL_FLAGS || (modifiedBits | newValues) !== modifiedBits) {
- throw new Error('flags are out of the valid range')
+ throw new InvalidActionParameterException({
+ actionType,
+ staticMessage: 'flags are out of the valid range',
+ dynamicMessage: 'flags are out of the valid range: ' + modifiedBits + ', ' + newValues
+ })
}
this.userId = userId
diff --git a/src/action/updateuserlimitlogincategory.ts b/src/action/updateuserlimitlogincategory.ts
index 994f828..9978ae2 100644
--- a/src/action/updateuserlimitlogincategory.ts
+++ b/src/action/updateuserlimitlogincategory.ts
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
-import { assertIdWithinFamily } from '../util/token'
import { ParentAction } from './basetypes'
+import { assertIdWithinFamily } from './meta/util'
+
+const actionType = 'UpdateUserLimitLoginCategory'
export class UpdateUserLimitLoginCategory extends ParentAction {
readonly userId: string
@@ -28,10 +30,10 @@ export class UpdateUserLimitLoginCategory extends ParentAction {
}) {
super()
- assertIdWithinFamily(userId)
+ assertIdWithinFamily({ actionType, field: 'userId', value: userId })
if (categoryId !== undefined) {
- assertIdWithinFamily(categoryId)
+ assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
}
this.userId = userId
diff --git a/src/api/schema.ts b/src/api/schema.ts
index 7103c4e..27ac31b 100644
--- a/src/api/schema.ts
+++ b/src/api/schema.ts
@@ -52,14 +52,16 @@ export interface ParentPassword {
export const assertParentPasswordValid = (password: ParentPassword) => {
if (password.hash === '' || password.secondHash === '' || password.secondSalt === '') {
- throw new Error('missing fields at parent password')
+ throw new ParentPasswordValidationException('missing fields at parent password')
}
if (!(optionalPasswordRegex.test(password.hash) && optionalPasswordRegex.test(password.secondHash) && optionalSaltRegex.test(password.secondSalt))) {
- throw new Error('invalid parent password')
+ throw new ParentPasswordValidationException('invalid parent password')
}
}
+export class ParentPasswordValidationException extends Error {}
+
export interface CreateFamilyByMailTokenRequest {
mailAuthToken: string
parentPassword: ParentPassword
diff --git a/src/api/sync.ts b/src/api/sync.ts
index f5c7d38..88c8755 100644
--- a/src/api/sync.ts
+++ b/src/api/sync.ts
@@ -85,7 +85,7 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
throw new BadRequest()
}
- await database.transaction(async (transaction) => {
+ const serverStatus = await database.transaction(async (transaction) => {
const deviceEntryUnsafe = await database.device.findOne({
where: {
deviceAuthToken: body.deviceAuthToken
@@ -112,23 +112,23 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
})
}
- const serverStatus = await generateServerDataStatus({
+ return generateServerDataStatus({
database,
familyId,
clientStatus: body.status,
transaction
})
-
- if (serverStatus.devices) { eventHandler.countEvent('pullStatusRequest devices') }
- if (serverStatus.apps) { eventHandler.countEvent('pullStatusRequest apps') }
- if (serverStatus.categoryBase) { eventHandler.countEvent('pullStatusRequest categoryBase') }
- if (serverStatus.categoryApp) { eventHandler.countEvent('pullStatusRequest categoryApp') }
- if (serverStatus.usedTimes) { eventHandler.countEvent('pullStatusRequest usedTimes') }
- if (serverStatus.rules) { eventHandler.countEvent('pullStatusRequest rules') }
- if (serverStatus.users) { eventHandler.countEvent('pullStatusRequest users') }
-
- res.json(serverStatus)
})
+
+ if (serverStatus.devices) { eventHandler.countEvent('pullStatusRequest devices') }
+ if (serverStatus.apps) { eventHandler.countEvent('pullStatusRequest apps') }
+ if (serverStatus.categoryBase) { eventHandler.countEvent('pullStatusRequest categoryBase') }
+ if (serverStatus.categoryApp) { eventHandler.countEvent('pullStatusRequest categoryApp') }
+ if (serverStatus.usedTimes) { eventHandler.countEvent('pullStatusRequest usedTimes') }
+ if (serverStatus.rules) { eventHandler.countEvent('pullStatusRequest rules') }
+ if (serverStatus.users) { eventHandler.countEvent('pullStatusRequest users') }
+
+ res.json(serverStatus)
} catch (ex) {
next(ex)
}
diff --git a/src/config.ts b/src/config.ts
index 316a913..837581a 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -29,7 +29,7 @@ function parseYesNo (value: string) {
} else if (value === 'no') {
return false
} else {
- throw new Error('invalid value "' + value + '", expected "yes" or "no"')
+ throw new ParseYesNoException('invalid value "' + value + '", expected "yes" or "no"')
}
}
@@ -39,3 +39,5 @@ export const config: Config = {
pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000,
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false
}
+
+class ParseYesNoException extends Error {}
diff --git a/src/database/sessionduration.ts b/src/database/sessionduration.ts
index 7d99eff..a3ec0c5 100644
--- a/src/database/sessionduration.ts
+++ b/src/database/sessionduration.ts
@@ -16,6 +16,7 @@
*/
import * as Sequelize from 'sequelize'
+import { ValidationException } from '../exception'
import { MinuteOfDay } from '../util/minuteofday'
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
import { SequelizeAttributes } from './types'
@@ -85,11 +86,11 @@ export const attributesVersion1: SequelizeAttributes endMinuteOfDay) {
- throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay')
+ throw new ValidationException({ staticMessage: 'startMinuteOfDay must not be bigger than endMinuteOfDay for a session duration' })
}
}
}
diff --git a/src/database/timelimitrule.ts b/src/database/timelimitrule.ts
index 573f99a..fe16614 100644
--- a/src/database/timelimitrule.ts
+++ b/src/database/timelimitrule.ts
@@ -16,6 +16,7 @@
*/
import * as Sequelize from 'sequelize'
+import { ValidationException } from '../exception'
import { MinuteOfDay } from '../util/minuteofday'
import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns'
import { SequelizeAttributes } from './types'
@@ -90,11 +91,11 @@ export const attributesVersion2: SequelizeAttributes endMinuteOfDay) {
- throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay')
+ throw new ValidationException({ staticMessage: 'startMinuteOfDay must not be bigger than endMinuteOfDay for a time limit rule' })
}
}
},
diff --git a/src/database/usedtime.ts b/src/database/usedtime.ts
index 63f2dde..4f96169 100644
--- a/src/database/usedtime.ts
+++ b/src/database/usedtime.ts
@@ -16,6 +16,7 @@
*/
import * as Sequelize from 'sequelize'
+import { ValidationException } from '../exception'
import { MinuteOfDay } from '../util/minuteofday'
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
import { SequelizeAttributes } from './types'
@@ -100,11 +101,11 @@ export const attributesVersion3: SequelizeAttributes
const startMinuteOfDay = this.startMinuteOfDay
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
- throw new Error('wrong data types')
+ throw new ValidationException({ staticMessage: 'wrong data types for start and end minute at the used time' })
}
if (startMinuteOfDay > endMinuteOfDay) {
- throw new Error('startMinuteOfDay must not be bigger than endMinuteOfDay')
+ throw new ValidationException({ staticMessage: 'startMinuteOfDay must not be bigger than endMinuteOfDay for a used time' })
}
}
}
diff --git a/src/exception/index.ts b/src/exception/index.ts
new file mode 100644
index 0000000..10537fd
--- /dev/null
+++ b/src/exception/index.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 .
+ */
+
+export { StaticMessageException, IllegalStateException } from './static-message-exception'
+export { ValidationException } from './validation'
diff --git a/src/exception/static-message-exception.ts b/src/exception/static-message-exception.ts
new file mode 100644
index 0000000..f3c8f4e
--- /dev/null
+++ b/src/exception/static-message-exception.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 .
+ */
+
+export class StaticMessageException extends Error {
+ readonly staticMessage: string
+ readonly dynamicMessage?: string
+
+ constructor ({ staticMessage, dynamicMessage }: { staticMessage: string, dynamicMessage?: string }) {
+ super(dynamicMessage || staticMessage)
+
+ this.staticMessage = staticMessage
+ this.dynamicMessage = dynamicMessage
+ }
+}
+
+export class IllegalStateException extends StaticMessageException {}
diff --git a/src/exception/validation.ts b/src/exception/validation.ts
new file mode 100644
index 0000000..e37af22
--- /dev/null
+++ b/src/exception/validation.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { StaticMessageException } from './static-message-exception'
+
+export class ValidationException extends StaticMessageException {}
diff --git a/src/function/child/set-primary-device.ts b/src/function/child/set-primary-device.ts
index 0d63390..6da1e21 100644
--- a/src/function/child/set-primary-device.ts
+++ b/src/function/child/set-primary-device.ts
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-import { Conflict, Unauthorized } from 'http-errors'
+import { Conflict, InternalServerError, Unauthorized } from 'http-errors'
import * as Sequelize from 'sequelize'
import { config } from '../../config'
import { Database } from '../../database'
@@ -147,7 +147,7 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
throw new Conflict()
}
} else {
- throw new Error('illegal state')
+ throw new InternalServerError('illegal state')
}
// invalidiate user list
diff --git a/src/function/device/report-device-removed.ts b/src/function/device/report-device-removed.ts
index c500363..3f40c66 100644
--- a/src/function/device/report-device-removed.ts
+++ b/src/function/device/report-device-removed.ts
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+import { Unauthorized } from 'http-errors'
import { Database } from '../../database'
import { generateAuthToken, generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket'
@@ -83,7 +84,7 @@ export async function reportDeviceRemoved ({ database, deviceAuthToken, websocke
})
if (!oldDeviceEntry) {
- throw new Error('device not found')
+ throw new Unauthorized('device not found')
}
}
})
diff --git a/src/function/parent/get-status-by-mail-address.ts b/src/function/parent/get-status-by-mail-address.ts
index bfa91c1..80efb86 100644
--- a/src/function/parent/get-status-by-mail-address.ts
+++ b/src/function/parent/get-status-by-mail-address.ts
@@ -16,13 +16,14 @@
*/
import { Database, Transaction } from '../../database'
+import { StaticMessageException } from '../../exception'
import { requireMailByAuthToken } from '../authentication'
const getStatusByMailAddress = async ({
mail, database, transaction
}: { mail: string, database: Database, transaction: Transaction }) => {
if (!mail) {
- throw new Error('no mail address')
+ throw new StaticMessageException({ staticMessage: 'getStatusByMailAddress: no mail address provided' })
}
const entry = await database.user.findOne({
diff --git a/src/function/sync/apply-actions/baseinfo.ts b/src/function/sync/apply-actions/baseinfo.ts
new file mode 100644
index 0000000..334192b
--- /dev/null
+++ b/src/function/sync/apply-actions/baseinfo.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { Unauthorized } from 'http-errors'
+import { Database, Transaction } from '../../../database'
+import { SourceFamilyNotFoundException } from './exception/illegal-state'
+
+export interface ApplyActionBaseInfo {
+ familyId: string
+ deviceId: string
+ nextSequenceNumber: number
+ hasFullVersion: boolean
+}
+
+export async function getApplyActionBaseInfo ({ database, transaction, deviceAuthToken }: {
+ database: Database
+ transaction: Transaction
+ deviceAuthToken: string
+}): Promise {
+ const deviceEntryUnsafe = await database.device.findOne({
+ where: { deviceAuthToken },
+ attributes: ['familyId', 'deviceId', 'nextSequenceNumber'],
+ transaction
+ })
+
+ if (!deviceEntryUnsafe) {
+ throw new Unauthorized()
+ }
+
+ const deviceEntry = {
+ familyId: deviceEntryUnsafe.familyId,
+ deviceId: deviceEntryUnsafe.deviceId,
+ nextSequenceNumber: deviceEntryUnsafe.nextSequenceNumber
+ }
+
+ const familyEntryUnsafe = await database.family.findOne({
+ where: {
+ familyId: deviceEntry.familyId
+ },
+ transaction,
+ attributes: ['hasFullVersion']
+ })
+
+ if (!familyEntryUnsafe) {
+ throw new SourceFamilyNotFoundException()
+ }
+
+ const familyEntry = {
+ hasFullVersion: familyEntryUnsafe.hasFullVersion
+ }
+
+ return {
+ familyId: deviceEntry.familyId,
+ deviceId: deviceEntry.deviceId,
+ nextSequenceNumber: deviceEntry.nextSequenceNumber,
+ hasFullVersion: familyEntry.hasFullVersion
+ }
+}
diff --git a/src/function/sync/apply-actions/cache.ts b/src/function/sync/apply-actions/cache.ts
index 58199d7..1399242 100644
--- a/src/function/sync/apply-actions/cache.ts
+++ b/src/function/sync/apply-actions/cache.ts
@@ -21,6 +21,8 @@ import { config } from '../../../config'
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
import { Database } from '../../../database'
import { generateVersionId } from '../../../util/token'
+import { SourceUserNotFoundException } from './exception/illegal-state'
+import { InvalidChildActionIntegrityValue } from './exception/integrity'
export class Cache {
readonly familyId: string
@@ -84,7 +86,7 @@ export class Cache {
})
if (!userEntryUnsafe) {
- throw new Error('user not found')
+ throw new SourceUserNotFoundException()
}
return userEntryUnsafe.secondPasswordHash
@@ -102,11 +104,11 @@ export class Cache {
})
if (!userEntryUnsafe) {
- throw new Error('user not found')
+ throw new SourceUserNotFoundException()
}
if (!userEntryUnsafe.secondPasswordHash) {
- throw new Error('user does not have a password')
+ throw new InvalidChildActionIntegrityValue()
}
return userEntryUnsafe.secondPasswordHash
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts
index a7ceb91..be1a28e 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts
@@ -19,6 +19,7 @@ import * as Sequelize from 'sequelize'
import { AddUsedTimeAction } from '../../../../action'
import { MinuteOfDay } from '../../../../util/minuteofday'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
export const getRoundedTimestamp = () => {
const now = Date.now()
@@ -29,7 +30,7 @@ export const getRoundedTimestamp = () => {
const dayLengthInMinutes = MinuteOfDay.LENGTH
const dayLengthInMs = dayLengthInMinutes * 1000 * 60
-export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
+export async function dispatchAddUsedTime ({ action, cache }: {
deviceId: string
action: AddUsedTimeAction
cache: Cache
@@ -50,7 +51,7 @@ export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
})
// verify that the category exists
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id')
+ throw new MissingCategoryException()
}
const categoryEntry = {
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts
index 7c6f245..8fff885 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts
@@ -20,6 +20,7 @@ import { AddUsedTimeActionVersion2 } from '../../../../action'
import { EventHandler } from '../../../../monitoring/eventhandler'
import { MinuteOfDay } from '../../../../util/minuteofday'
import { Cache } from '../cache'
+import { SourceDeviceNotFoundException } from '../exception/illegal-state'
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
export const getRoundedTimestampForSessionDuration = () => {
@@ -44,7 +45,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
})
if (!deviceEntryUnsafe) {
- throw new Error('source device not found')
+ throw new SourceDeviceNotFoundException()
}
const deviceEntry = {
@@ -56,9 +57,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
let addUsedTimeForADifferentUserThanTheCurrentUserOfTheDevice = false
- for (let i = 0; i < action.items.length; i++) {
- const item = action.items[i]
-
+ for (const item of action.items) {
const categoryEntryUnsafe = await cache.database.category.findOne({
where: {
familyId: cache.familyId,
@@ -73,9 +72,10 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
// verify that the category exists
if (!categoryEntryUnsafe) {
+ eventHandler.countEvent('add used time category to add time for not found')
cache.requireFullSync()
- return
+ continue
}
const categoryEntry = {
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts
index cae0827..eaed2d6 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts
@@ -29,6 +29,7 @@ import {
} from '../../../../action'
import { EventHandler } from '../../../../monitoring/eventhandler'
import { Cache } from '../cache'
+import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
import { dispatchAddInstalledApps } from './addinstalledapps'
import { dispatchAddUsedTime } from './addusedtime'
import { dispatchAddUsedTimeVersion2 } from './addusedtime2'
@@ -64,6 +65,6 @@ export const dispatchAppLogicAction = async ({ action, deviceId, cache, eventHan
} else if (action instanceof TriedDisablingDeviceAdminAction) {
await dispatchTriedDisablingDeviceAdmin({ deviceId, action, cache })
} else {
- throw new Error('unsupported action type')
+ throw new ActionObjectTypeNotHandledException()
}
}
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/signoutatdevice.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/signoutatdevice.ts
index 154da97..a15684c 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/signoutatdevice.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/signoutatdevice.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
@@ -18,14 +18,16 @@
import { SetDeviceUserAction, SignOutAtDeviceAction } from '../../../../action'
import { Cache } from '../cache'
import { dispatchSetDeviceUser } from '../dispatch-parent-action/setdeviceuser'
+import { IllegalStateException, SourceDeviceNotFoundException } from '../exception/illegal-state'
+import { PremiumVersionMissingException } from '../exception/premium'
-export async function dispatchSignOutAtDevice ({ deviceId, action, cache }: {
+export async function dispatchSignOutAtDevice ({ deviceId, cache }: {
deviceId: string
action: SignOutAtDeviceAction
cache: Cache
}) {
if (!cache.hasFullVersion) {
- throw new Error('action requires full version')
+ throw new PremiumVersionMissingException()
}
const deviceEntry = await cache.database.device.findOne({
@@ -37,11 +39,13 @@ export async function dispatchSignOutAtDevice ({ deviceId, action, cache }: {
})
if (!deviceEntry) {
- throw new Error('illegal state: missing device which dispatched the action')
+ throw new SourceDeviceNotFoundException()
}
if (deviceEntry.defaultUserId === '') {
- throw new Error('no default user available')
+ throw new IllegalStateException({
+ staticMessage: 'tried to switch to the default user where it does not exist'
+ })
}
if (deviceEntry.currentUserId !== deviceEntry.defaultUserId) {
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/trieddisablingdeviceadmin.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/trieddisablingdeviceadmin.ts
index e2283f9..a376abd 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/trieddisablingdeviceadmin.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/trieddisablingdeviceadmin.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
@@ -19,8 +19,9 @@ import { TriedDisablingDeviceAdminAction } from '../../../../action'
import { hasDeviceManipulation } from '../../../../database/device'
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
import { Cache } from '../cache'
+import { SourceDeviceNotFoundException } from '../exception/illegal-state'
-export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, action, cache }: {
+export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, cache }: {
deviceId: string
action: TriedDisablingDeviceAdminAction
cache: Cache
@@ -34,7 +35,7 @@ export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, action, cac
})
if (deviceEntry === null) {
- throw new Error('illegal state: missing device which dispatched the action')
+ throw new SourceDeviceNotFoundException()
}
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts
index 73f2e4d..c232478 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.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
@@ -29,9 +29,7 @@ export async function dispatchUpdateAppActivities ({ deviceId, action, cache }:
if (action.updatedOrAdded.length > 0) {
const chuncks = chunk(action.updatedOrAdded, 500)
- for (let i = 0; i < chuncks.length; i++) {
- const items = chuncks[i]
-
+ for (const items of chuncks) {
await cache.database.appActivity.destroy({
where: {
familyId: cache.familyId,
@@ -62,9 +60,7 @@ export async function dispatchUpdateAppActivities ({ deviceId, action, cache }:
if (action.removed.length > 0) {
const chunks = chunk(action.removed, 500)
- for (let i = 0; i < chunks.length; i++) {
- const items = chunks[i]
-
+ for (const items of chunks) {
await cache.database.appActivity.destroy({
where: {
familyId: cache.familyId,
diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/updatedevicestatus.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/updatedevicestatus.ts
index 520ea0c..39d8290 100644
--- a/src/function/sync/apply-actions/dispatch-app-logic-action/updatedevicestatus.ts
+++ b/src/function/sync/apply-actions/dispatch-app-logic-action/updatedevicestatus.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
@@ -23,6 +23,7 @@ import { runtimePermissionStatusValues } from '../../../../model/runtimepermissi
import { enumMax } from '../../../../util/enum'
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
import { Cache } from '../cache'
+import { SourceDeviceNotFoundException } from '../exception/illegal-state'
export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
deviceId: string
@@ -38,7 +39,7 @@ export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
})
if (!deviceEntry) {
- throw new Error('device not found')
+ throw new SourceDeviceNotFoundException()
}
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
diff --git a/src/function/sync/apply-actions/dispatch-child-action/childchangepassword.ts b/src/function/sync/apply-actions/dispatch-child-action/childchangepassword.ts
index 3b1c662..ceecdfe 100644
--- a/src/function/sync/apply-actions/dispatch-child-action/childchangepassword.ts
+++ b/src/function/sync/apply-actions/dispatch-child-action/childchangepassword.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
@@ -18,6 +18,7 @@
import * as Sequelize from 'sequelize'
import { ChildChangePasswordAction } from '../../../../action'
import { Cache } from '../cache'
+import { SourceUserNotFoundException } from '../exception/illegal-state'
export const dispatchChildChangePassword = async ({ action, childUserId, cache }: {
action: ChildChangePasswordAction
@@ -35,7 +36,7 @@ export const dispatchChildChangePassword = async ({ action, childUserId, cache }
})
if (!childEntry) {
- throw new Error('child entry not found')
+ throw new SourceUserNotFoundException()
}
childEntry.passwordHash = action.password.hash
diff --git a/src/function/sync/apply-actions/dispatch-child-action/childsignin.ts b/src/function/sync/apply-actions/dispatch-child-action/childsignin.ts
index f84d80b..9699fb2 100644
--- a/src/function/sync/apply-actions/dispatch-child-action/childsignin.ts
+++ b/src/function/sync/apply-actions/dispatch-child-action/childsignin.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
@@ -18,15 +18,17 @@
import { ChildSignInAction, SetDeviceUserAction } from '../../../../action'
import { Cache } from '../cache'
import { dispatchSetDeviceUser } from '../dispatch-parent-action/setdeviceuser'
+import { SourceUserNotFoundException } from '../exception/illegal-state'
+import { PremiumVersionMissingException } from '../exception/premium'
-export const dispatchChildSignIn = async ({ action, deviceId, childUserId, cache }: {
+export const dispatchChildSignIn = async ({ deviceId, childUserId, cache }: {
action: ChildSignInAction
deviceId: string
childUserId: string
cache: Cache
}) => {
if (!cache.hasFullVersion) {
- throw new Error('action requires full version')
+ throw new PremiumVersionMissingException()
}
await dispatchSetDeviceUser({
@@ -50,7 +52,7 @@ export const dispatchChildSignIn = async ({ action, deviceId, childUserId, cache
})
if (!userEntryUnsafe) {
- throw new Error('illegal state')
+ throw new SourceUserNotFoundException()
}
if (userEntryUnsafe.currentDevice === deviceId) {
diff --git a/src/function/sync/apply-actions/dispatch-child-action/index.ts b/src/function/sync/apply-actions/dispatch-child-action/index.ts
index d0cbc76..fa0bde1 100644
--- a/src/function/sync/apply-actions/dispatch-child-action/index.ts
+++ b/src/function/sync/apply-actions/dispatch-child-action/index.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,6 +21,7 @@ import {
ChildSignInAction
} from '../../../../action'
import { Cache } from '../cache'
+import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
import { dispatchChildChangePassword } from './childchangepassword'
import { dispatchChildSignIn } from './childsignin'
@@ -35,6 +36,6 @@ export const dispatchChildAction = async ({ action, deviceId, childUserId, cache
} else if (action instanceof ChildSignInAction) {
await dispatchChildSignIn({ action, childUserId, deviceId, cache })
} else {
- throw new Error('unsupported action type')
+ throw new ActionObjectTypeNotHandledException()
}
}
diff --git a/src/function/sync/apply-actions/dispatch-helper/app-logic-action.ts b/src/function/sync/apply-actions/dispatch-helper/app-logic-action.ts
new file mode 100644
index 0000000..1d81747
--- /dev/null
+++ b/src/function/sync/apply-actions/dispatch-helper/app-logic-action.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { parseAppLogicAction } from '../../../../action/serialization'
+import { ClientPushChangesRequestAction } from '../../../../api/schema'
+import { isSerializedAppLogicAction } from '../../../../api/validator'
+import { EventHandler } from '../../../../monitoring/eventhandler'
+import { Cache } from '../cache'
+import { dispatchAppLogicAction as dispatchAppLogicActionInternal } from '../dispatch-app-logic-action'
+import { dispatch } from './helper'
+
+export async function dispatchAppLogicAction ({ action, eventHandler, deviceId, cache }: {
+ action: ClientPushChangesRequestAction
+ deviceId: string
+ cache: Cache
+ eventHandler: EventHandler
+}) {
+ return dispatch({
+ action,
+ eventHandler,
+ type: 'app logic',
+ validator: isSerializedAppLogicAction,
+ parser: parseAppLogicAction,
+ applier: async (action) => {
+ await dispatchAppLogicActionInternal({ action, cache, eventHandler, deviceId })
+ }
+ })
+}
diff --git a/src/function/sync/apply-actions/dispatch-helper/child-action.ts b/src/function/sync/apply-actions/dispatch-helper/child-action.ts
new file mode 100644
index 0000000..0e0538d
--- /dev/null
+++ b/src/function/sync/apply-actions/dispatch-helper/child-action.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { parseChildAction } from '../../../../action/serialization'
+import { ClientPushChangesRequestAction } from '../../../../api/schema'
+import { isSerializedChildAction } from '../../../../api/validator'
+import { EventHandler } from '../../../../monitoring/eventhandler'
+import { Cache } from '../cache'
+import { dispatchChildAction as dispatchChildActionInternal } from '../dispatch-child-action'
+import { dispatch } from './helper'
+
+export async function dispatchChildAction ({ action, eventHandler, deviceId, cache, childUserId }: {
+ action: ClientPushChangesRequestAction
+ deviceId: string
+ cache: Cache
+ eventHandler: EventHandler
+ childUserId: string
+}) {
+ return dispatch({
+ action,
+ eventHandler,
+ type: 'child',
+ validator: isSerializedChildAction,
+ parser: parseChildAction,
+ applier: async (action) => {
+ await dispatchChildActionInternal({ action, cache, deviceId, childUserId })
+ }
+ })
+}
diff --git a/src/function/sync/apply-actions/dispatch-helper/helper.ts b/src/function/sync/apply-actions/dispatch-helper/helper.ts
new file mode 100644
index 0000000..7fb000f
--- /dev/null
+++ b/src/function/sync/apply-actions/dispatch-helper/helper.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { ClientPushChangesRequestAction } from '../../../../api/schema'
+import { EventHandler } from '../../../../monitoring/eventhandler'
+import { ApplyActionException } from '../exception/index'
+import { EncodedActionSchemaMismatchException } from '../exception/invalidaction'
+import { parseEncodedAction } from '../parse-encoded-action'
+
+export async function dispatch ({ type, action, validator, parser, applier, eventHandler }: {
+ type: 'app logic' | 'parent' | 'child'
+ action: ClientPushChangesRequestAction
+ validator: (input: any) => input is T1
+ parser: (input: T1) => T2
+ applier: (input: T2) => Promise
+ eventHandler: EventHandler
+}) {
+ const parsedSerializedAction = parseEncodedAction(action)
+
+ if (!validator(parsedSerializedAction)) {
+ throw new EncodedActionSchemaMismatchException({ type, action: parsedSerializedAction })
+ }
+
+ const actionType = parsedSerializedAction.type
+
+ try {
+ const parsedAction = parser(parsedSerializedAction)
+
+ await applier(parsedAction)
+
+ eventHandler.countEvent('dispatched action:' + actionType)
+ } catch (ex) {
+ if (ex instanceof ApplyActionException) {
+ throw new ApplyActionException({
+ staticMessage: 'error during dispatching ' + actionType + ': ' + ex.staticMessage,
+ dynamicMessage: ex.dynamicMessage ? 'error during dispatching ' + actionType + ': ' + ex.dynamicMessage : undefined
+ })
+ } else throw ex
+ }
+}
diff --git a/src/function/sync/apply-actions/dispatch-helper/index.ts b/src/function/sync/apply-actions/dispatch-helper/index.ts
new file mode 100644
index 0000000..e6128a3
--- /dev/null
+++ b/src/function/sync/apply-actions/dispatch-helper/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 .
+ */
+
+export { dispatchAppLogicAction } from './app-logic-action'
+export { dispatchChildAction } from './child-action'
+export { dispatchParentAction } from './parent-action'
diff --git a/src/function/sync/apply-actions/dispatch-helper/parent-action.ts b/src/function/sync/apply-actions/dispatch-helper/parent-action.ts
new file mode 100644
index 0000000..c0d8a6e
--- /dev/null
+++ b/src/function/sync/apply-actions/dispatch-helper/parent-action.ts
@@ -0,0 +1,106 @@
+/*
+ * 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 { parseParentAction } from '../../../../action/serialization'
+import { ClientPushChangesRequestAction } from '../../../../api/schema'
+import { isSerializedParentAction } from '../../../../api/validator'
+import { UserFlags } from '../../../../model/userflags'
+import { EventHandler } from '../../../../monitoring/eventhandler'
+import { Cache } from '../cache'
+import { dispatchParentAction as dispatchParentActionInternal } from '../dispatch-parent-action'
+import { SourceDeviceNotFoundException } from '../exception/illegal-state'
+import { SelfLimitNotPossibleException } from '../exception/self-limit'
+import { dispatch } from './helper'
+
+export async function dispatchParentAction ({ action, eventHandler, cache, isChildLimitAdding, deviceId }: {
+ action: ClientPushChangesRequestAction
+ cache: Cache
+ eventHandler: EventHandler
+ isChildLimitAdding: boolean
+ deviceId: string
+}) {
+ return dispatch({
+ action,
+ eventHandler,
+ type: 'parent',
+ validator: isSerializedParentAction,
+ parser: parseParentAction,
+ applier: async (parsedAction) => {
+ if (isChildLimitAdding) {
+ const deviceEntryUnsafe = await cache.database.device.findOne({
+ attributes: ['currentUserId'],
+ where: {
+ familyId: cache.familyId,
+ deviceId,
+ currentUserId: action.userId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!deviceEntryUnsafe) {
+ throw new SourceDeviceNotFoundException()
+ }
+
+ const deviceUserId = deviceEntryUnsafe.currentUserId
+
+ if (!deviceUserId) {
+ throw new SelfLimitNotPossibleException({
+ staticMessage: 'no device user id set but child add self limit action requested'
+ })
+ }
+
+ const deviceUserEntryUnsafe = await cache.database.user.findOne({
+ attributes: ['flags'],
+ where: {
+ familyId: cache.familyId,
+ userId: deviceUserId,
+ type: 'child'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!deviceUserEntryUnsafe) {
+ throw new SelfLimitNotPossibleException({
+ staticMessage: 'no child user found for child limit adding action'
+ })
+ }
+
+ if ((parseInt(deviceUserEntryUnsafe.flags, 10) & UserFlags.ALLOW_SELF_LIMIT_ADD) !== UserFlags.ALLOW_SELF_LIMIT_ADD) {
+ throw new SelfLimitNotPossibleException({
+ staticMessage: 'child add limit action found but not allowed'
+ })
+ }
+
+ await dispatchParentActionInternal({
+ action: parsedAction,
+ cache,
+ parentUserId: action.userId,
+ sourceDeviceId: deviceId,
+ fromChildSelfLimitAddChildUserId: deviceUserId
+ })
+ } else {
+ await dispatchParentActionInternal({
+ action: parsedAction,
+ cache,
+ parentUserId: action.userId,
+ sourceDeviceId: deviceId,
+ fromChildSelfLimitAddChildUserId: null
+ })
+ }
+ }
+ })
+}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts b/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts
index 7acfd68..1385b5d 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts
@@ -18,8 +18,11 @@
import * as Sequelize from 'sequelize'
import { AddCategoryAppsAction } from '../../../../action'
import { CategoryAppAttributes } from '../../../../database/categoryapp'
-import { getCategoryWithParentCategories } from '../../../../util/category'
+import { getCategoryWithParentCategories, GetParentCategoriesException } from '../../../../util/category'
import { Cache } from '../cache'
+import { SourceUserNotFoundException } from '../exception/illegal-state'
+import { MissingCategoryException } from '../exception/missing-item'
+import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: AddCategoryAppsAction
@@ -36,14 +39,14 @@ export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLim
})
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id')
+ throw new MissingCategoryException()
}
const { childId } = categoryEntryUnsafe
if (fromChildSelfLimitAddChildUserId !== null) {
if (childId !== fromChildSelfLimitAddChildUserId) {
- throw new Error('can not add apps to other users')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
}
@@ -77,64 +80,74 @@ export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLim
}).map((item) => item.categoryId)
if (fromChildSelfLimitAddChildUserId !== null) {
- const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
- const userEntryUnsafe = await cache.database.user.findOne({
- attributes: [ 'categoryForNotAssignedApps' ],
- where: {
- familyId: cache.familyId,
- userId: fromChildSelfLimitAddChildUserId
- },
- transaction: cache.transaction
- })
-
- if (!userEntryUnsafe) {
- throw new Error('illegal state')
- }
-
- const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
- const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
- const allowUnassignedElements = validatedDefaultCategoryId !== undefined &&
- parentCategoriesOfTargetCategory.indexOf(validatedDefaultCategoryId) !== -1
-
- const assertCanAddApp = async (packageName: string, isApp: boolean) => {
- const categoryAppEntryUnsafe = await cache.database.categoryApp.findOne({
- attributes: [ 'categoryId' ],
+ try {
+ const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
+ const userEntryUnsafe = await cache.database.user.findOne({
+ attributes: [ 'categoryForNotAssignedApps' ],
where: {
familyId: cache.familyId,
- categoryId: {
- [Sequelize.Op.in]: userCategoryIds
- },
- packageName: packageName
+ userId: fromChildSelfLimitAddChildUserId
},
transaction: cache.transaction
})
- const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
+ if (!userEntryUnsafe) {
+ throw new SourceUserNotFoundException()
+ }
- if (categoryAppEntry === null) {
- if ((isApp && allowUnassignedElements) || (!isApp)) {
- // allow
+ const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
+ const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
+ const allowUnassignedElements = validatedDefaultCategoryId !== undefined &&
+ parentCategoriesOfTargetCategory.indexOf(validatedDefaultCategoryId) !== -1
+
+ const assertCanAddApp = async (packageName: string, isApp: boolean) => {
+ const categoryAppEntryUnsafe = await cache.database.categoryApp.findOne({
+ attributes: [ 'categoryId' ],
+ where: {
+ familyId: cache.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: userCategoryIds
+ },
+ packageName: packageName
+ },
+ transaction: cache.transaction
+ })
+
+ const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
+
+ if (categoryAppEntry === null) {
+ if ((isApp && allowUnassignedElements) || (!isApp)) {
+ // allow
+ } else {
+ throw new SelfLimitationException({
+ staticMessage: 'can not assign apps without category as child'
+ })
+ }
} else {
- throw new Error('can not assign apps without category as child')
- }
- } else {
- if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
- // allow
- } else {
- throw new Error('can not add app which is not contained in the parent category')
+ if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
+ // allow
+ } else {
+ throw new SelfLimitationException({
+ staticMessage: 'can not add app which is not contained in the parent category as child'
+ })
+ }
}
}
- }
- for (let i = 0; i < action.packageNames.length; i++) {
- const packageName = action.packageNames[i]
+ for (let i = 0; i < action.packageNames.length; i++) {
+ const packageName = action.packageNames[i]
- if (packageName.indexOf(':') !== -1) {
- await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
- await assertCanAddApp(packageName, false)
- } else {
- await assertCanAddApp(packageName, true)
+ if (packageName.indexOf(':') !== -1) {
+ await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
+ await assertCanAddApp(packageName, false)
+ } else {
+ await assertCanAddApp(packageName, true)
+ }
}
+ } catch (ex) {
+ if (ex instanceof GetParentCategoriesException) {
+ throw new MissingCategoryException()
+ } else throw ex
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/addcategorynetworkid.ts b/src/function/sync/apply-actions/dispatch-parent-action/addcategorynetworkid.ts
index f68af53..5f3ddd5 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/addcategorynetworkid.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/addcategorynetworkid.ts
@@ -18,6 +18,8 @@
import { AddCategoryNetworkIdAction } from '../../../../action'
import { maxNetworkIdsPerCategory } from '../../../../database/categorynetworkid'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { MissingCategoryException } from '../exception/missing-item'
export async function dispatchAddCategoryNetworkId ({ action, cache }: {
action: AddCategoryNetworkIdAction
@@ -33,7 +35,7 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
})
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id for new rule')
+ throw new MissingCategoryException()
}
const count = await cache.database.categoryNetworkId.count({
@@ -45,7 +47,9 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
})
if (count + 1 > maxNetworkIdsPerCategory) {
- throw new Error('category network limit reached')
+ throw new ApplyActionException({
+ staticMessage: 'can not add a category network id because the category network limit reached'
+ })
}
const hasOldItem = (await cache.database.categoryNetworkId.count({
@@ -58,7 +62,9 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
})) !== 0
if (hasOldItem) {
- throw new Error('id already used')
+ throw new ApplyActionException({
+ staticMessage: 'can not add a category network id because the id is already used'
+ })
}
await cache.database.categoryNetworkId.create({
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/changeparentpassword.ts b/src/function/sync/apply-actions/dispatch-parent-action/changeparentpassword.ts
index c329c9b..9450531 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/changeparentpassword.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/changeparentpassword.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
@@ -16,8 +16,10 @@
*/
import * as Sequelize from 'sequelize'
-import { ChangeParentPasswordAction } from '../../../../action'
+import { ChangeParentPasswordAction, InvalidChangeParentPasswordIntegrityException } from '../../../../action/changeparentpassword'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchChangeParentPassword ({ action, cache }: {
action: ChangeParentPasswordAction
@@ -34,10 +36,17 @@ export async function dispatchChangeParentPassword ({ action, cache }: {
})
if (!parentEntry) {
- throw new Error('parent entry not found')
+ throw new MissingUserException()
+ }
+
+ try {
+ action.assertIntegrityValid({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
+ } catch (ex) {
+ if (ex instanceof InvalidChangeParentPasswordIntegrityException) {
+ throw new ApplyActionException({ staticMessage: 'invalid new password integrity' })
+ } else throw ex
}
- action.assertIntegrityValid({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
const newSecondPasswordHash = action.decryptSecondHash({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
parentEntry.passwordHash = action.newPasswordFirstHash
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 46cd92f..a342c68 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts
@@ -18,6 +18,8 @@
import { CreateCategoryAction } from '../../../../action'
import { generateVersionId } from '../../../../util/token'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
+import { CanNotModifyOtherUsersBySelfLimitationException } from '../exception/self-limit'
export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: CreateCategoryAction
@@ -26,7 +28,7 @@ export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimi
}) {
if (fromChildSelfLimitAddChildUserId !== null) {
if (fromChildSelfLimitAddChildUserId !== action.childId) {
- throw new Error('can not create categories for other child users')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
}
@@ -41,7 +43,7 @@ export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimi
})
if (!childEntry) {
- throw new Error('missing child for new category')
+ throw new MissingUserException()
}
const oldMaxSort: number = await cache.database.category.max('sort', {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts b/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts
index bf64b99..35a0741 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts
@@ -17,6 +17,8 @@
import { CreateTimeLimitRuleAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
+import { CanNotModifyOtherUsersBySelfLimitationException } from '../exception/self-limit'
export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: CreateTimeLimitRuleAction
@@ -33,12 +35,12 @@ export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSel
})
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id for new rule')
+ throw new MissingCategoryException()
}
if (fromChildSelfLimitAddChildUserId !== null) {
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
- throw new Error('can not add rules for other users')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/deletecategory.ts b/src/function/sync/apply-actions/dispatch-parent-action/deletecategory.ts
index a55a460..b9a2a7b 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/deletecategory.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/deletecategory.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
@@ -17,15 +17,25 @@
import { DeleteCategoryAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
export async function dispatchDeleteCategory ({ action, cache }: {
action: DeleteCategoryAction
cache: Cache
}) {
- // no version number needs to be updated
const { familyId, transaction } = cache
const { categoryId } = action
+ const categoryEntry = await cache.database.category.findOne({
+ where: {
+ familyId,
+ categoryId
+ },
+ transaction
+ })
+
+ if (!categoryEntry) { throw new MissingCategoryException() }
+
await cache.database.timelimitRule.destroy({
where: {
familyId,
@@ -75,4 +85,6 @@ export async function dispatchDeleteCategory ({ action, cache }: {
if (affectedUserRows !== 0) {
cache.invalidiateUserList = true
}
+
+ // no version number needs to be updated
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/deletetimelimitrule.ts b/src/function/sync/apply-actions/dispatch-parent-action/deletetimelimitrule.ts
index eb7ea47..e1e6909 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/deletetimelimitrule.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/deletetimelimitrule.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
@@ -17,6 +17,7 @@
import { DeleteTimeLimitRuleAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingRuleException } from '../exception/missing-item'
export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
action: DeleteTimeLimitRuleAction
@@ -30,10 +31,12 @@ export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
transaction: cache.transaction
})
- if (ruleEntry) {
- await ruleEntry.destroy({ transaction: cache.transaction })
-
- cache.categoriesWithModifiedTimeLimitRules.push(ruleEntry.categoryId)
- cache.areChangesImportant = true
+ if (!ruleEntry) {
+ throw new MissingRuleException()
}
+
+ await ruleEntry.destroy({ transaction: cache.transaction })
+
+ cache.categoriesWithModifiedTimeLimitRules.push(ruleEntry.categoryId)
+ cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/ignoremanipulation.ts b/src/function/sync/apply-actions/dispatch-parent-action/ignoremanipulation.ts
index b16a646..81d6712 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/ignoremanipulation.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/ignoremanipulation.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
@@ -17,6 +17,7 @@
import { IgnoreManipulationAction } from '../../../../action'
import { Cache } from '../cache'
+import { SourceDeviceNotFoundException } from '../exception/illegal-state'
export async function dispatchIgnoreManipulation ({ action, cache }: {
action: IgnoreManipulationAction
@@ -31,7 +32,7 @@ export async function dispatchIgnoreManipulation ({ action, cache }: {
})
if (deviceEntry === null) {
- throw new Error('illegal state: missing device which dispatched the action')
+ throw new SourceDeviceNotFoundException()
}
if (action.ignoreDeviceAdminManipulation) {
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 72e52d5..5ff9710 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/incrementcategoryextratime.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/incrementcategoryextratime.ts
@@ -18,13 +18,15 @@
import { IncrementCategoryExtraTimeAction } from '../../../../action'
import { CategoryModel } from '../../../../database/category'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
+import { PremiumVersionMissingException } from '../exception/premium'
export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
action: IncrementCategoryExtraTimeAction
cache: Cache
}) {
if (!cache.hasFullVersion) {
- throw new Error('action requires full version')
+ throw new PremiumVersionMissingException()
}
async function handleCategory (category: CategoryModel) {
@@ -51,7 +53,7 @@ export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
})
if (!categoryEntry) {
- throw new Error(`tried to add extra time to ${action.categoryId} but it does not exist`)
+ throw new MissingCategoryException()
}
await handleCategory(categoryEntry)
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/index.ts b/src/function/sync/apply-actions/dispatch-parent-action/index.ts
index 058c685..953154d 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/index.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/index.ts
@@ -62,6 +62,8 @@ import {
UpdateUserLimitLoginCategory
} from '../../../../action'
import { Cache } from '../cache'
+import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
+import { ActionNotSupportedBySelfLimitationException } from '../exception/self-limit'
import { dispatchAddCategoryApps } from './addcategoryapps'
import { dispatchAddCategoryNetworkId } from './addcategorynetworkid'
import { dispatchAddUser } from './adduser'
@@ -129,7 +131,9 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
return dispatchUpdateCategoryBlockedTimes({ action, cache, fromChildSelfLimitAddChildUserId })
}
- if (fromChildSelfLimitAddChildUserId === null) {
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ throw new ActionNotSupportedBySelfLimitationException()
+ } else {
if (action instanceof AddCategoryNetworkIdAction) {
return dispatchAddCategoryNetworkId({ action, cache })
} else if (action instanceof AddUserAction) {
@@ -202,8 +206,8 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
return dispatchUpdateUserFlagsAction({ action, cache })
} else if (action instanceof UpdateUserLimitLoginCategory) {
return dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
+ } else {
+ throw new ActionObjectTypeNotHandledException()
}
- } else {
- throw new Error('unsupported action type')
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/removecategoryapps.ts b/src/function/sync/apply-actions/dispatch-parent-action/removecategoryapps.ts
index de56752..7f471af 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/removecategoryapps.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/removecategoryapps.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
@@ -18,6 +18,7 @@
import * as Sequelize from 'sequelize'
import { RemoveCategoryAppsAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingItemException } from '../exception/missing-item'
export async function dispatchRemoveCategoryApps ({ action, cache }: {
action: RemoveCategoryAppsAction
@@ -35,7 +36,9 @@ export async function dispatchRemoveCategoryApps ({ action, cache }: {
})
if (affectedRows !== action.packageNames.length) {
- throw new Error('could not delete as much entries as requested')
+ throw new MissingItemException({
+ staticMessage: 'could not remove all requested category app items'
+ })
}
cache.categoriesWithModifiedApps.push(action.categoryId)
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/removeuser.ts b/src/function/sync/apply-actions/dispatch-parent-action/removeuser.ts
index 6e1a8c2..dc450ea 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/removeuser.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/removeuser.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,6 +21,9 @@ import { difference } from 'lodash'
import * as Sequelize from 'sequelize'
import { RemoveUserAction } from '../../../../action'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { ApplyActionIntegrityException } from '../exception/integrity'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
action: RemoveUserAction
@@ -36,7 +39,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
})
if (!user) {
- throw new Error('invalid user id')
+ throw new MissingUserException()
}
if (user.type === 'parent') {
@@ -45,7 +48,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
}
if (parentUserId === action.userId) {
- throw new Error('users can not delete themself')
+ throw new ApplyActionException({ staticMessage: 'users can not delete themself' })
}
const expectedIntegrityValue = createHash('sha512').update(
@@ -53,7 +56,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
).digest('hex').substring(0, 16)
if (expectedIntegrityValue !== action.authentication) {
- throw new Error('invalid authentication value')
+ throw new ApplyActionIntegrityException({ staticMessage: 'invalid authentication value for removing a user' })
}
if (user.mail !== '') {
@@ -69,7 +72,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
})
if (usersWithLinkedMail <= 1) {
- throw new Error('this user is the last one with a linked mail address')
+ throw new ApplyActionException({ staticMessage: 'this user is the last one with a linked mail address' })
}
}
@@ -93,7 +96,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
const allOtherParentUserIds = allParentUserIds.filter((item) => item !== action.userId)
if (difference(allOtherParentUserIds, usersWithLimitLoginCategories).length === 0) {
- throw new Error('can not delete the last user without limit login category')
+ throw new ApplyActionException({ staticMessage: 'can not delete the last user without limit login category' })
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/renamechild.ts b/src/function/sync/apply-actions/dispatch-parent-action/renamechild.ts
index 1ce5b59..51f9560 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/renamechild.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/renamechild.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
@@ -17,12 +17,26 @@
import { RenameChildAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchRenameChild ({ action, cache }: {
action: RenameChildAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.user.update({
+ const oldItem = await cache.database.user.findOne({
+ where: {
+ familyId: cache.familyId,
+ userId: action.childId,
+ type: 'child'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldItem) {
+ throw new MissingUserException()
+ }
+
+ await cache.database.user.update({
name: action.newName
}, {
where: {
@@ -33,10 +47,6 @@ export async function dispatchRenameChild ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows !== 1) {
- throw new Error('can not update child name if child does not exist')
- }
-
cache.invalidiateUserList = true
cache.doesUserExist.cache.set(action.childId, false)
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/resetcategorynetworkids.ts b/src/function/sync/apply-actions/dispatch-parent-action/resetcategorynetworkids.ts
index 879ae92..ee6de73 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/resetcategorynetworkids.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/resetcategorynetworkids.ts
@@ -17,6 +17,7 @@
import { ResetCategoryNetworkIdsAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
action: ResetCategoryNetworkIdsAction
@@ -32,7 +33,7 @@ export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
})
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id for new rule')
+ throw new MissingCategoryException()
}
await cache.database.categoryNetworkId.destroy({
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/resetparentblockedtimes.ts b/src/function/sync/apply-actions/dispatch-parent-action/resetparentblockedtimes.ts
index 6957f7f..e1e2bda 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/resetparentblockedtimes.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/resetparentblockedtimes.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
@@ -17,12 +17,26 @@
import { ResetParentBlockedTimesAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchResetParentBlockedTimes ({ action, cache }: {
action: ResetParentBlockedTimesAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.user.update({
+ const oldItem = await cache.database.user.findOne({
+ where: {
+ familyId: cache.familyId,
+ userId: action.parentId,
+ type: 'parent'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldItem) {
+ throw new MissingUserException()
+ }
+
+ await cache.database.user.update({
blockedTimes: ''
}, {
where: {
@@ -33,10 +47,6 @@ export async function dispatchResetParentBlockedTimes ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows === 0) {
- throw new Error('invalid parent user id provided')
- }
-
cache.invalidiateUserList = true
cache.areChangesImportant = true
}
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 e77f974..3e125c0 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setcategoryextratime.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setcategoryextratime.ts
@@ -17,16 +17,30 @@
import { SetCategoryExtraTimeAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
+import { PremiumVersionMissingException } from '../exception/premium'
export async function dispatchSetCategoryExtraTime ({ action, cache }: {
action: SetCategoryExtraTimeAction
cache: Cache
}) {
if (!cache.hasFullVersion) {
- throw new Error('action requires full version')
+ throw new PremiumVersionMissingException()
}
- const [affectedRows] = await cache.database.category.update({
+ const oldItem = await cache.database.category.findOne({
+ where: {
+ familyId: cache.familyId,
+ categoryId: action.categoryId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldItem) {
+ throw new MissingCategoryException()
+ }
+
+ await cache.database.category.update({
extraTimeInMillis: action.newExtraTime,
extraTimeDay: action.day
}, {
@@ -37,8 +51,6 @@ export async function dispatchSetCategoryExtraTime ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows !== 0) {
- cache.categoriesWithModifiedBaseData.push(action.categoryId)
- cache.areChangesImportant = true
- }
+ cache.categoriesWithModifiedBaseData.push(action.categoryId)
+ cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setcategoryforunassignedapps.ts b/src/function/sync/apply-actions/dispatch-parent-action/setcategoryforunassignedapps.ts
index 5ed561a..d21d9a1 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setcategoryforunassignedapps.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setcategoryforunassignedapps.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
@@ -17,11 +17,26 @@
import { SetCategoryForUnassignedAppsAction } from '../../../../action'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
action: SetCategoryForUnassignedAppsAction
cache: Cache
}) {
+ const oldUserEntry = await cache.database.user.findOne({
+ where: {
+ familyId: cache.familyId,
+ userId: action.childId,
+ type: 'child'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldUserEntry) {
+ throw new MissingUserException()
+ }
+
if (action.categoryId === '') {
// nothing to check
} else {
@@ -35,7 +50,9 @@ export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
})
if (!categoryEntryUnsafe) {
- throw new Error('can not set a category which does not exist as category for unassigned apps')
+ throw new ApplyActionException({
+ staticMessage: 'can not set a category which does not exist as category for unassigned apps'
+ })
}
const categoryEntry = {
@@ -43,11 +60,13 @@ export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
}
if (categoryEntry.childId !== action.childId) {
- throw new Error('can not set a category of one child as category for unassigned apps for an other child')
+ throw new ApplyActionException({
+ staticMessage: 'can not set a category of one child as category for unassigned apps for an other child'
+ })
}
}
- const [affectedRows] = await cache.database.user.update({
+ await cache.database.user.update({
categoryForNotAssignedApps: action.categoryId
}, {
where: {
@@ -58,10 +77,6 @@ export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows !== 1) {
- throw new Error('could not find a child with matching id for setting the category for not assigned apps')
- }
-
cache.invalidiateUserList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setchildpassword.ts b/src/function/sync/apply-actions/dispatch-parent-action/setchildpassword.ts
index 95cb831..6e7e634 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setchildpassword.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setchildpassword.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
@@ -18,6 +18,7 @@
import * as Sequelize from 'sequelize'
import { SetChildPasswordAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchSetChildPassword ({ action, cache }: {
action: SetChildPasswordAction
@@ -34,7 +35,7 @@ export async function dispatchSetChildPassword ({ action, cache }: {
})
if (!childEntry) {
- throw new Error('parent entry not found')
+ throw new MissingUserException()
}
childEntry.passwordHash = action.newPassword.hash
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setconsiderrebootmanipulation.ts b/src/function/sync/apply-actions/dispatch-parent-action/setconsiderrebootmanipulation.ts
index 588eee7..398d0d1 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setconsiderrebootmanipulation.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setconsiderrebootmanipulation.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
@@ -17,12 +17,25 @@
import { SetConsiderRebootManipulationAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException } from '../exception/missing-item'
export async function dispatchSetConsiderRebootManipulation ({ action, cache }: {
action: SetConsiderRebootManipulationAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.device.update({
+ const oldDevice = await cache.database.device.findOne({
+ transaction: cache.transaction,
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ }
+ })
+
+ if (!oldDevice) {
+ throw new MissingDeviceException()
+ }
+
+ await cache.database.device.update({
considerRebootManipulation: action.enable
}, {
transaction: cache.transaction,
@@ -32,10 +45,6 @@ export async function dispatchSetConsiderRebootManipulation ({ action, cache }:
}
})
- if (affectedRows === 0) {
- throw new Error('did not find device to update consider reboot manipulation')
- }
-
cache.invalidiateDeviceList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultuser.ts b/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultuser.ts
index ac4c45b..dea14b8 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultuser.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultuser.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
@@ -17,6 +17,7 @@
import { SetDeviceDefaultUserAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException, MissingUserException } from '../exception/missing-item'
export async function dispatchSetDeviceDefaultUser ({ action, cache }: {
action: SetDeviceDefaultUserAction
@@ -26,11 +27,23 @@ export async function dispatchSetDeviceDefaultUser ({ action, cache }: {
const doesUserExist = await cache.doesUserExist(action.defaultUserId)
if (!doesUserExist) {
- throw new Error('can not set invalid user as default user')
+ throw new MissingUserException()
}
}
- const [affectedRows] = await cache.database.device.update({
+ const oldDeviceItem = await cache.database.device.findOne({
+ transaction: cache.transaction,
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ }
+ })
+
+ if (!oldDeviceItem) {
+ throw new MissingDeviceException()
+ }
+
+ await cache.database.device.update({
defaultUserId: action.defaultUserId
}, {
transaction: cache.transaction,
@@ -40,10 +53,6 @@ export async function dispatchSetDeviceDefaultUser ({ action, cache }: {
}
})
- if (affectedRows === 0) {
- throw new Error('did not find device to update default user')
- }
-
cache.invalidiateDeviceList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultusertimeout.ts b/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultusertimeout.ts
index 8b3e217..645a56d 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultusertimeout.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setdevicedefaultusertimeout.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
@@ -17,12 +17,25 @@
import { SetDeviceDefaultUserTimeoutAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException } from '../exception/missing-item'
export async function dispatchSetDeviceDefaultUserTimeout ({ action, cache }: {
action: SetDeviceDefaultUserTimeoutAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.device.update({
+ const oldDeviceItem = await cache.database.device.findOne({
+ transaction: cache.transaction,
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ }
+ })
+
+ if (!oldDeviceItem) {
+ throw new MissingDeviceException()
+ }
+
+ await cache.database.device.update({
defaultUserTimeout: action.timeout
}, {
transaction: cache.transaction,
@@ -32,10 +45,6 @@ export async function dispatchSetDeviceDefaultUserTimeout ({ action, cache }: {
}
})
- if (affectedRows === 0) {
- throw new Error('did not find device to update default user timeout')
- }
-
cache.invalidiateDeviceList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setdeviceuser.ts b/src/function/sync/apply-actions/dispatch-parent-action/setdeviceuser.ts
index e242c86..3edf454 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setdeviceuser.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setdeviceuser.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
@@ -17,6 +17,7 @@
import { SetDeviceUserAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException, MissingUserException } from '../exception/missing-item'
export async function dispatchSetDeviceUser ({ action, cache }: {
action: SetDeviceUserAction
@@ -26,11 +27,23 @@ export async function dispatchSetDeviceUser ({ action, cache }: {
const doesUserExist = await cache.doesUserExist(action.userId)
if (!doesUserExist) {
- throw new Error('invalid user id provided')
+ throw new MissingUserException()
}
}
- const [affectedRows] = await cache.database.device.update({
+ const oldDeviceItem = await cache.database.device.findOne({
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldDeviceItem) {
+ throw new MissingDeviceException()
+ }
+
+ await cache.database.device.update({
currentUserId: action.userId,
isUserKeptSignedIn: false
}, {
@@ -41,8 +54,6 @@ export async function dispatchSetDeviceUser ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows !== 0) {
- cache.invalidiateDeviceList = true
- cache.areChangesImportant = true
- }
+ cache.invalidiateDeviceList = true
+ cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setkeepsignedin.ts b/src/function/sync/apply-actions/dispatch-parent-action/setkeepsignedin.ts
index d001ead..4a91c6d 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setkeepsignedin.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setkeepsignedin.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
@@ -17,6 +17,9 @@
import { SetKeepSignedInAction } from '../../../../action'
import { Cache } from '../cache'
+import { SourceUserNotFoundException } from '../exception/illegal-state'
+import { ApplyActionException } from '../exception/index'
+import { MissingDeviceException } from '../exception/missing-item'
export async function dispatchSetKeepSignedIn ({ action, cache, parentUserId }: {
action: SetKeepSignedInAction
@@ -26,7 +29,7 @@ export async function dispatchSetKeepSignedIn ({ action, cache, parentUserId }:
const doesUserExist = await cache.doesUserExist(parentUserId)
if (!doesUserExist) {
- throw new Error('invalid user id provided')
+ throw new SourceUserNotFoundException()
}
const deviceEntry = await cache.database.device.findOne({
@@ -38,12 +41,12 @@ export async function dispatchSetKeepSignedIn ({ action, cache, parentUserId }:
})
if (!deviceEntry) {
- throw new Error('device does not exist')
+ throw new MissingDeviceException()
}
if (deviceEntry.currentUserId !== parentUserId) {
if (action.keepSignedIn) {
- throw new Error('only the user itself can disable asking for the password')
+ throw new ApplyActionException({ staticMessage: 'only the user itself can disable asking for the password' })
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts b/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts
index b1050bb..7423933 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts
@@ -16,8 +16,11 @@
*/
import { SetParentCategoryAction } from '../../../../action'
-import { getCategoryWithParentCategories } from '../../../../util/category'
+import { getCategoryWithParentCategories, GetParentCategoriesException } from '../../../../util/category'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { MissingCategoryException } from '../exception/missing-item'
+import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
export async function dispatchSetParentCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: SetParentCategoryAction
@@ -33,12 +36,12 @@ export async function dispatchSetParentCategory ({ action, cache, fromChildSelfL
})
if (!categoryEntry) {
- throw new Error('tried to set parent category of non existent category')
+ throw new MissingCategoryException()
}
if (fromChildSelfLimitAddChildUserId !== null) {
if (categoryEntry.childId !== fromChildSelfLimitAddChildUserId) {
- throw new Error('can not set parent category for other user')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
}
@@ -56,7 +59,7 @@ export async function dispatchSetParentCategory ({ action, cache, fromChildSelfL
}))
if (categoriesByUserId.find((item) => item.categoryId === action.parentCategory) === undefined) {
- throw new Error('selected parent category does not exist')
+ throw new MissingCategoryException()
}
const childCategoryIds = new Set()
@@ -80,16 +83,26 @@ export async function dispatchSetParentCategory ({ action, cache, fromChildSelfL
}
if (childCategoryIds.has(action.parentCategory) || action.parentCategory === action.categoryId) {
- throw new Error('can not set a category as parent which is a child of the category')
+ throw new ApplyActionException({
+ staticMessage: 'can not set a category as parent which is a child of the category'
+ })
}
if (fromChildSelfLimitAddChildUserId !== null) {
- const ownParentCategory = categoriesByUserId.find((item) => item.categoryId === categoryEntry.parentCategoryId)
- const enableDueToLimitAddingWhenChild = ownParentCategory === undefined ||
- getCategoryWithParentCategories(categoriesByUserId, action.parentCategory).indexOf(ownParentCategory.categoryId) !== -1
+ try {
+ const ownParentCategory = categoriesByUserId.find((item) => item.categoryId === categoryEntry.parentCategoryId)
+ const enableDueToLimitAddingWhenChild = ownParentCategory === undefined ||
+ getCategoryWithParentCategories(categoriesByUserId, action.parentCategory).indexOf(ownParentCategory.categoryId) !== -1
- if (!enableDueToLimitAddingWhenChild) {
- throw new Error('can not change parent categories in a way which reduces limits')
+ if (!enableDueToLimitAddingWhenChild) {
+ throw new SelfLimitationException({
+ staticMessage: 'can not change parent categories in a way which reduces limits'
+ })
+ }
+ } catch (ex) {
+ if (ex instanceof GetParentCategoriesException) {
+ throw new MissingCategoryException()
+ } else throw ex
}
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setrelaxprimarydevice.ts b/src/function/sync/apply-actions/dispatch-parent-action/setrelaxprimarydevice.ts
index d981a1e..a10a7c4 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setrelaxprimarydevice.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setrelaxprimarydevice.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
@@ -17,12 +17,26 @@
import { SetRelaxPrimaryDeviceAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchSetRelaxPrimaryDevice ({ action, cache }: {
action: SetRelaxPrimaryDeviceAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.user.update({
+ const oldUser = cache.database.user.findOne({
+ transaction: cache.transaction,
+ where: {
+ familyId: cache.familyId,
+ userId: action.userId,
+ type: 'child'
+ }
+ })
+
+ if (!oldUser) {
+ throw new MissingUserException()
+ }
+
+ await cache.database.user.update({
relaxPrimaryDeviceRule: action.relax
}, {
transaction: cache.transaction,
@@ -33,10 +47,6 @@ export async function dispatchSetRelaxPrimaryDevice ({ action, cache }: {
}
})
- if (affectedRows === 0) {
- throw new Error('did not find user to update relax primary device')
- }
-
cache.invalidiateUserList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setsenddeviceconnected.ts b/src/function/sync/apply-actions/dispatch-parent-action/setsenddeviceconnected.ts
index 3d3cce0..04a4cac 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setsenddeviceconnected.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setsenddeviceconnected.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
@@ -17,6 +17,7 @@
import { SetSendDeviceConnected } from '../../../../action'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
export async function dispatchSetSendDeviceConnected ({ action, cache, sourceDeviceId }: {
action: SetSendDeviceConnected
@@ -24,10 +25,12 @@ export async function dispatchSetSendDeviceConnected ({ action, cache, sourceDev
sourceDeviceId: string | null
}) {
if (sourceDeviceId === null || action.deviceId !== sourceDeviceId) {
- throw new Error('only can do that from the device itself')
+ throw new ApplyActionException({
+ staticMessage: 'only can do that from the device itself if the connection status should be sent'
+ })
}
- const [affectedRows] = await cache.database.device.update({
+ await cache.database.device.update({
showDeviceConnected: action.enable
}, {
transaction: cache.transaction,
@@ -37,10 +40,6 @@ export async function dispatchSetSendDeviceConnected ({ action, cache, sourceDev
}
})
- if (affectedRows === 0) {
- throw new Error('did not find device to update send if connected')
- }
-
cache.devicesWithModifiedShowDeviceConnected.set(action.deviceId, action.enable)
cache.invalidiateDeviceList = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setuserdisablelmitsuntil.ts b/src/function/sync/apply-actions/dispatch-parent-action/setuserdisablelmitsuntil.ts
index e7a6f8a..db51649 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setuserdisablelmitsuntil.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setuserdisablelmitsuntil.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
@@ -17,6 +17,8 @@
import { SetUserDisableLimitsUntilAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
+import { PremiumVersionMissingException } from '../exception/premium'
export async function dispatchUserSetDisableLimitsUntil ({ action, cache }: {
action: SetUserDisableLimitsUntilAction
@@ -24,11 +26,24 @@ export async function dispatchUserSetDisableLimitsUntil ({ action, cache }: {
}) {
if (action.timestamp !== 0) {
if (!cache.hasFullVersion) {
- throw new Error('action requires full version')
+ throw new PremiumVersionMissingException()
}
}
- const [affectedRows] = await cache.database.user.update({
+ const oldUser = await cache.database.user.findOne({
+ where: {
+ familyId: cache.familyId,
+ userId: action.childId,
+ type: 'child'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldUser) {
+ throw new MissingUserException()
+ }
+
+ await cache.database.user.update({
disableTimelimitsUntil: action.timestamp.toString(10)
}, {
where: {
@@ -39,10 +54,6 @@ export async function dispatchUserSetDisableLimitsUntil ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows === 0) {
- throw new Error('invalid user id provided')
- }
-
cache.invalidiateUserList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setusertimezone.ts b/src/function/sync/apply-actions/dispatch-parent-action/setusertimezone.ts
index 4487a11..c74da7c 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setusertimezone.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setusertimezone.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
@@ -17,12 +17,25 @@
import { SetUserTimezoneAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchSetUserTimezone ({ action, cache }: {
action: SetUserTimezoneAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.user.update({
+ const oldUser = await cache.database.user.findOne({
+ where: {
+ familyId: cache.familyId,
+ userId: action.userId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldUser) {
+ throw new MissingUserException()
+ }
+
+ await cache.database.user.update({
timeZone: action.timezone
}, {
transaction: cache.transaction,
@@ -32,10 +45,6 @@ export async function dispatchSetUserTimezone ({ action, cache }: {
}
})
- if (affectedRows === 0) {
- throw new Error('did not find user to update timezone')
- }
-
cache.invalidiateUserList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorybatterylimit.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorybatterylimit.ts
index 3f94679..6f7a7b8 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorybatterylimit.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorybatterylimit.ts
@@ -17,6 +17,7 @@
import { UpdateCategoryBatteryLimitAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
export async function dispatchUpdateCategoryBatteryLimit ({ action, cache }: {
action: UpdateCategoryBatteryLimitAction
@@ -31,7 +32,7 @@ export async function dispatchUpdateCategoryBatteryLimit ({ action, cache }: {
})
if (!categoryEntry) {
- throw new Error('invalid category id')
+ throw new MissingCategoryException()
}
if (action.chargeLimit !== undefined) {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts
index a67959c..8868b99 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts
@@ -17,6 +17,8 @@
import { UpdateCategoryBlockAllNotificationsAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
+import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: UpdateCategoryBlockAllNotificationsAction
@@ -33,16 +35,16 @@ export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cac
})
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id for updating notification blocking')
+ throw new MissingCategoryException()
}
if (fromChildSelfLimitAddChildUserId !== null) {
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
- throw new Error('can not add rules for other users')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
if (!action.blocked) {
- throw new Error('can not disable filter as child')
+ throw new SelfLimitationException({ staticMessage: 'can not disable notification filter as child' })
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockedtimes.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockedtimes.ts
index 4fda2bf..046c9e1 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockedtimes.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockedtimes.ts
@@ -18,6 +18,8 @@
import { blockedTimesBitmaskLength, UpdateCategoryBlockedTimesAction } from '../../../../action/updatecategoryblockedtimes'
import { validateAndParseBitmask } from '../../../../util/bitmask'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
+import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: UpdateCategoryBlockedTimesAction
@@ -34,7 +36,7 @@ export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromC
})
if (!categoryEntryUnsafe) {
- throw new Error('can not update blocked time areas for a category which does not exist')
+ throw new MissingCategoryException()
}
const categoryEntry = {
@@ -44,7 +46,7 @@ export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromC
if (fromChildSelfLimitAddChildUserId !== null) {
if (categoryEntry.childId !== fromChildSelfLimitAddChildUserId) {
- throw new Error('can not update blocked time areas for other child users')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
const oldBlocked = validateAndParseBitmask(categoryEntry.blockedMinutesInWeek, blockedTimesBitmaskLength)
@@ -52,7 +54,7 @@ export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromC
oldBlocked.forEach((value, index) => {
if (value && !newBlocked[index]) {
- throw new Error('new blocked time areas are smaller')
+ throw new SelfLimitationException({ staticMessage: 'new blocked time areas are smaller' })
}
})
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts
index ffe2f04..61f7573 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts
@@ -17,6 +17,9 @@
import { UpdateCategoryTemporarilyBlockedAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
+import { PremiumVersionMissingException } from '../exception/premium'
+import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: UpdateCategoryTemporarilyBlockedAction
@@ -25,7 +28,7 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache,
}) {
if (action.blocked === true) {
if (!cache.hasFullVersion) {
- throw new Error('action requires full version')
+ throw new PremiumVersionMissingException()
}
}
@@ -39,7 +42,7 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache,
})
if (!categoryEntryUnsafe) {
- throw new Error('invalid category id for updating temporarily blocking')
+ throw new MissingCategoryException()
}
const categoryEntry = {
@@ -50,16 +53,16 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache,
if (fromChildSelfLimitAddChildUserId !== null) {
if (fromChildSelfLimitAddChildUserId !== categoryEntry.childId) {
- throw new Error('can not update temporarily blocking as child for other users')
+ throw new CanNotModifyOtherUsersBySelfLimitationException()
}
if (action.endTime === undefined || !action.blocked) {
- throw new Error('the child may only enable a temporarily blocking')
+ throw new SelfLimitationException({ staticMessage: 'the child may only enable a temporarily blocking' })
}
if (categoryEntry.temporarilyBlocked) {
if (action.endTime < categoryEntry.temporarilyBlockedEndTime || categoryEntry.temporarilyBlockedEndTime === 0) {
- throw new Error('the child may not reduce the temporarily blocking')
+ throw new SelfLimitationException({ staticMessage: 'the child may not reduce the temporarily blocking' })
}
}
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytimewarnings.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytimewarnings.ts
index b611e2a..103cb5e 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytimewarnings.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytimewarnings.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
@@ -17,6 +17,7 @@
import { UpdateCategoryTimeWarningsAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
export async function dispatchUpdateCategoryTimeWarnings ({ action, cache }: {
action: UpdateCategoryTimeWarningsAction
@@ -31,7 +32,7 @@ export async function dispatchUpdateCategoryTimeWarnings ({ action, cache }: {
})
if (!categoryEntry) {
- throw new Error('invalid category id')
+ throw new MissingCategoryException()
}
if (action.enable) {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytitle.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytitle.ts
index c0726f6..058ee66 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytitle.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytitle.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
@@ -17,11 +17,24 @@
import { UpdateCategoryTitleAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingCategoryException } from '../exception/missing-item'
export async function dispatchUpdateCategoryTitle ({ action, cache }: {
action: UpdateCategoryTitleAction
cache: Cache
}) {
+ const oldCategory = await cache.database.category.findOne({
+ where: {
+ familyId: cache.familyId,
+ categoryId: action.categoryId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldCategory) {
+ throw new MissingCategoryException()
+ }
+
const [affectedRows] = await cache.database.category.update({
title: action.newTitle
}, {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatedevicename.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatedevicename.ts
index 2a9af64..c052861 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatedevicename.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatedevicename.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
@@ -17,11 +17,24 @@
import { UpdateDeviceNameAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException } from '../exception/missing-item'
export async function dispatchUpdateDeviceName ({ action, cache }: {
action: UpdateDeviceNameAction
cache: Cache
}) {
+ const oldDevice = await cache.database.device.findOne({
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldDevice) {
+ throw new MissingDeviceException()
+ }
+
const [affectedRows] = await cache.database.device.update({
name: action.name
}, {
@@ -32,9 +45,7 @@ export async function dispatchUpdateDeviceName ({ action, cache }: {
transaction: cache.transaction
})
- if (affectedRows === 0) {
- throw new Error('invalid device id')
- } else {
+ if (affectedRows !== 0) {
cache.invalidiateDeviceList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts b/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts
index dccc22b..4050c1b 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.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
@@ -17,12 +17,25 @@
import { UpdateEnableActivityLevelBlockingAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException } from '../exception/missing-item'
export async function dispatchUpdateEnableActivityLevelBlocking ({ action, cache }: {
action: UpdateEnableActivityLevelBlockingAction
cache: Cache
}) {
- const [affectedRows] = await cache.database.device.update({
+ const oldDevice = await cache.database.device.findOne({
+ transaction: cache.transaction,
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ }
+ })
+
+ if (!oldDevice) {
+ throw new MissingDeviceException()
+ }
+
+ await cache.database.device.update({
activityLevelBlocking: action.enable
}, {
transaction: cache.transaction,
@@ -32,10 +45,6 @@ export async function dispatchUpdateEnableActivityLevelBlocking ({ action, cache
}
})
- if (affectedRows === 0) {
- throw new Error('did not find device to update activity level blocking')
- }
-
cache.invalidiateDeviceList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatenetworktimeverification.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatenetworktimeverification.ts
index 7772c46..eb8475a 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatenetworktimeverification.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatenetworktimeverification.ts
@@ -17,11 +17,24 @@
import { UpdateNetworkTimeVerificationAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingDeviceException } from '../exception/missing-item'
export async function dispatchUpdateNetworkTimeVerification ({ action, cache }: {
action: UpdateNetworkTimeVerificationAction
cache: Cache
}) {
+ const oldDevice = await cache.database.device.findOne({
+ transaction: cache.transaction,
+ where: {
+ familyId: cache.familyId,
+ deviceId: action.deviceId
+ }
+ })
+
+ if (!oldDevice) {
+ throw new MissingDeviceException()
+ }
+
await cache.database.device.update({
networkTime: action.mode
}, {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updateparentblockedtimes.ts b/src/function/sync/apply-actions/dispatch-parent-action/updateparentblockedtimes.ts
index 304aff7..12a46e4 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updateparentblockedtimes.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updateparentblockedtimes.ts
@@ -17,6 +17,8 @@
import { UpdateParentBlockedTimesAction } from '../../../../action'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchUpdateParentBlockedTimes ({ action, cache, parentUserId }: {
action: UpdateParentBlockedTimesAction
@@ -24,10 +26,23 @@ export async function dispatchUpdateParentBlockedTimes ({ action, cache, parentU
parentUserId: string
}) {
if (parentUserId !== action.parentId && action.blockedTimes !== '') {
- throw new Error('only a parent itself can add limits')
+ throw new ApplyActionException({ staticMessage: 'only a parent itself can add limits' })
}
- const [affectedRows] = await cache.database.user.update({
+ const oldUser = await cache.database.user.findOne({
+ where: {
+ familyId: cache.familyId,
+ userId: action.parentId,
+ type: 'parent'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!oldUser) {
+ throw new MissingUserException()
+ }
+
+ await cache.database.user.update({
blockedTimes: action.blockedTimes
}, {
where: {
@@ -38,10 +53,6 @@ export async function dispatchUpdateParentBlockedTimes ({ action, cache, parentU
transaction: cache.transaction
})
- if (affectedRows === 0) {
- throw new Error('invalid parent user id provided')
- }
-
cache.invalidiateUserList = true
cache.areChangesImportant = true
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updateparentnotificationflags.ts b/src/function/sync/apply-actions/dispatch-parent-action/updateparentnotificationflags.ts
index 695f26e..2465de7 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updateparentnotificationflags.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updateparentnotificationflags.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
@@ -17,6 +17,7 @@
import { UpdateParentNotificationFlagsAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchUpdateParentNotificationFlags ({ action, cache }: {
action: UpdateParentNotificationFlagsAction
@@ -32,7 +33,7 @@ export async function dispatchUpdateParentNotificationFlags ({ action, cache }:
})
if (!parentEntry) {
- throw new Error('parent not found')
+ throw new MissingUserException()
}
if (action.set) {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatetimelimitrule.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatetimelimitrule.ts
index f8af940..d7bf4b5 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatetimelimitrule.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatetimelimitrule.ts
@@ -17,6 +17,7 @@
import { UpdateTimelimitRuleAction } from '../../../../action'
import { Cache } from '../cache'
+import { MissingRuleException } from '../exception/missing-item'
export async function dispatchUpdateTimelimitRule ({ action, cache }: {
action: UpdateTimelimitRuleAction
@@ -31,7 +32,7 @@ export async function dispatchUpdateTimelimitRule ({ action, cache }: {
})
if (!ruleEntry) {
- throw new Error('invalid rule id provided')
+ throw new MissingRuleException()
}
ruleEntry.applyToExtraTimeUsage = action.applyToExtraTimeUsage
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updateuserflags.ts b/src/function/sync/apply-actions/dispatch-parent-action/updateuserflags.ts
index d2433da..4e40900 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updateuserflags.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updateuserflags.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
@@ -17,6 +17,8 @@
import { UpdateUserFlagsAction } from '../../../../action'
import { Cache } from '../cache'
+import { IllegalStateException } from '../exception/illegal-state'
+import { MissingUserException } from '../exception/missing-item'
export async function dispatchUpdateUserFlagsAction ({ action, cache }: {
action: UpdateUserFlagsAction
@@ -31,13 +33,13 @@ export async function dispatchUpdateUserFlagsAction ({ action, cache }: {
})
if (!userEntry) {
- throw new Error('user not found')
+ throw new MissingUserException()
}
const oldFlags = parseInt(userEntry.flags, 10)
if (!Number.isSafeInteger(oldFlags)) {
- throw new Error()
+ throw new IllegalStateException({ staticMessage: 'oldFlags is not a safe integer' })
}
const newFlags = (oldFlags & ~action.modifiedBits) | action.newValues
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updateuserlimitlogincategory.ts b/src/function/sync/apply-actions/dispatch-parent-action/updateuserlimitlogincategory.ts
index d37c076..6b7e419 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updateuserlimitlogincategory.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updateuserlimitlogincategory.ts
@@ -17,6 +17,8 @@
import { UpdateUserLimitLoginCategory } from '../../../../action'
import { Cache } from '../cache'
+import { ApplyActionException } from '../exception/index'
+import { MissingCategoryException, MissingUserException } from '../exception/missing-item'
export async function dispatchUpdateUserLimitLoginCategoryAction ({ action, cache, parentUserId }: {
action: UpdateUserLimitLoginCategory
@@ -26,21 +28,20 @@ export async function dispatchUpdateUserLimitLoginCategoryAction ({ action, cach
const userEntry = await cache.database.user.findOne({
where: {
familyId: cache.familyId,
- userId: action.userId
+ userId: action.userId,
+ type: 'parent'
},
transaction: cache.transaction
})
if (!userEntry) {
- throw new Error('user not found')
- }
-
- if (userEntry.type !== 'parent') {
- throw new Error('user must be a parent')
+ throw new MissingUserException()
}
if (action.categoryId !== undefined && parentUserId !== action.userId) {
- throw new Error('only the user itself can add a limit')
+ throw new ApplyActionException({
+ staticMessage: 'only the parent user itself can add a limit login category'
+ })
}
await cache.database.userLimitLoginCategory.destroy({
@@ -61,7 +62,7 @@ export async function dispatchUpdateUserLimitLoginCategoryAction ({ action, cach
})
if (!categoryEntry) {
- throw new Error('category must exist')
+ throw new MissingCategoryException()
}
await cache.database.userLimitLoginCategory.create({
diff --git a/src/function/sync/apply-actions/exception/illegal-state.ts b/src/function/sync/apply-actions/exception/illegal-state.ts
new file mode 100644
index 0000000..61dad1a
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/illegal-state.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { ApplyActionException } from './index'
+
+export class IllegalStateException extends ApplyActionException {
+ constructor ({ staticMessage }: { staticMessage: string }) {
+ super({ staticMessage })
+ }
+}
+
+export class SourceDeviceNotFoundException extends IllegalStateException {
+ constructor () {
+ super({ staticMessage: 'source device not found' })
+ }
+}
+
+export class SourceUserNotFoundException extends IllegalStateException {
+ constructor () {
+ super({ staticMessage: 'source user not found' })
+ }
+}
+
+export class SourceFamilyNotFoundException extends IllegalStateException {
+ constructor () {
+ super({ staticMessage: 'family entry not found' })
+ }
+}
+
+export class ActionObjectTypeNotHandledException extends IllegalStateException {
+ constructor () {
+ super({ staticMessage: 'action object type not handled' })
+ }
+}
diff --git a/src/function/sync/apply-actions/exception/index.ts b/src/function/sync/apply-actions/exception/index.ts
new file mode 100644
index 0000000..de570f3
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/index.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { StaticMessageException } from '../../../../exception'
+
+export class ApplyActionException extends StaticMessageException {}
diff --git a/src/function/sync/apply-actions/exception/integrity.ts b/src/function/sync/apply-actions/exception/integrity.ts
new file mode 100644
index 0000000..3f733eb
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/integrity.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { ApplyActionException } from './index'
+
+export class ApplyActionIntegrityException extends ApplyActionException {}
+
+export class ParentDeviceActionWithoutParentDeviceException extends ApplyActionIntegrityException {
+ constructor () { super({ staticMessage: 'parent device action but no parent device' }) }
+}
+
+export class InvalidParentActionIntegrityValue extends ApplyActionIntegrityException {
+ constructor () { super({ staticMessage: 'invalid parent action integrity value' }) }
+}
+
+export class InvalidChildActionIntegrityValue extends ApplyActionIntegrityException {
+ constructor () { super({ staticMessage: 'invalid child action integrity value' }) }
+}
diff --git a/src/function/sync/apply-actions/exception/invalidaction.ts b/src/function/sync/apply-actions/exception/invalidaction.ts
new file mode 100644
index 0000000..89aefa3
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/invalidaction.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { InvalidActionParameterException } from '../../../../action/meta/exception'
+import { ApplyActionException } from './index'
+
+export class InvalidActionException extends ApplyActionException {}
+
+export class InvalidActionParamterException extends InvalidActionException {
+ constructor (cause: InvalidActionParameterException) {
+ super({
+ staticMessage: cause.staticMessage,
+ dynamicMessage: cause.dynamicMessage
+ })
+ }
+}
+
+export class BadEncodedActionJsonException extends InvalidActionException {
+ constructor () { super({ staticMessage: 'bad encoded action JSON' }) }
+}
+
+export class BadEncodedActionContentException extends InvalidActionException {}
+
+export class EncodedActionSchemaMismatchException extends BadEncodedActionContentException {
+ readonly action: object
+
+ constructor ({ type, action }: { type: 'parent' | 'child' | 'app logic', action: object }) {
+ super({
+ staticMessage: 'encoded ' + type + ' action does not match schema',
+ dynamicMessage: 'encoded action does not match schema: ' + JSON.stringify(action)
+ })
+
+ this.action = action
+ }
+}
diff --git a/src/function/sync/apply-actions/exception/missing-item.ts b/src/function/sync/apply-actions/exception/missing-item.ts
new file mode 100644
index 0000000..ec6d975
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/missing-item.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { ApplyActionException } from './index'
+
+export class MissingItemException extends ApplyActionException {}
+
+export class MissingCategoryException extends MissingItemException {
+ constructor () {
+ super({ staticMessage: 'referenced category which does not exist' })
+ }
+}
+
+export class MissingUserException extends MissingItemException {
+ constructor () {
+ super({ staticMessage: 'referenced user which does not exist' })
+ }
+}
+
+export class MissingRuleException extends MissingItemException {
+ constructor () {
+ super({ staticMessage: 'referenced rule which does not exist' })
+ }
+}
+
+export class MissingDeviceException extends MissingItemException {
+ constructor () {
+ super({ staticMessage: 'referenced device which does not exist' })
+ }
+}
diff --git a/src/function/sync/apply-actions/exception/premium.ts b/src/function/sync/apply-actions/exception/premium.ts
new file mode 100644
index 0000000..806f96c
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/premium.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { ApplyActionException } from './index'
+
+export class PremiumVersionMissingException extends ApplyActionException {
+ constructor () {
+ super({ staticMessage: 'premium version required but missing' })
+ }
+}
diff --git a/src/function/sync/apply-actions/exception/self-limit.ts b/src/function/sync/apply-actions/exception/self-limit.ts
new file mode 100644
index 0000000..f3b9241
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/self-limit.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 { ApplyActionException } from './index'
+
+export class SelfLimitationException extends ApplyActionException {}
+
+export class CanNotModifyOtherUsersBySelfLimitationException extends SelfLimitationException {
+ constructor () {
+ super({ staticMessage: 'can not modify other users with the self limitation' })
+ }
+}
+
+export class ActionNotSupportedBySelfLimitationException extends SelfLimitationException {
+ constructor () {
+ super({ staticMessage: 'can not dispatch this action with the self limitation' })
+ }
+}
+
+export class SelfLimitNotPossibleException extends SelfLimitationException {}
diff --git a/src/function/sync/apply-actions/exception/sequence.ts b/src/function/sync/apply-actions/exception/sequence.ts
new file mode 100644
index 0000000..d5edb20
--- /dev/null
+++ b/src/function/sync/apply-actions/exception/sequence.ts
@@ -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 .
+ */
+
+import { ApplyActionException } from './index'
+
+export class SequenceNumberRepeatedException extends ApplyActionException {
+ constructor () { super({ staticMessage: 'sequence number repeated' }) }
+}
diff --git a/src/function/sync/apply-actions/index.ts b/src/function/sync/apply-actions/index.ts
index 7743e35..692387c 100644
--- a/src/function/sync/apply-actions/index.ts
+++ b/src/function/sync/apply-actions/index.ts
@@ -15,20 +15,19 @@
* along with this program. If not, see .
*/
-import { BadRequest, Unauthorized } from 'http-errors'
-import { parseAppLogicAction, parseChildAction, parseParentAction } from '../../../action/serialization'
+import { BadRequest } from 'http-errors'
import { ClientPushChangesRequest } from '../../../api/schema'
-import { isSerializedAppLogicAction, isSerializedChildAction, isSerializedParentAction } from '../../../api/validator'
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
import { Database } from '../../../database'
-import { UserFlags } from '../../../model/userflags'
import { EventHandler } from '../../../monitoring/eventhandler'
import { WebsocketApi } from '../../../websocket'
import { notifyClientsAboutChangesDelayed } from '../../websocket'
+import { getApplyActionBaseInfo } from './baseinfo'
import { Cache } from './cache'
-import { dispatchAppLogicAction } from './dispatch-app-logic-action'
-import { dispatchChildAction } from './dispatch-child-action'
-import { dispatchParentAction } from './dispatch-parent-action'
+import { dispatchAppLogicAction, dispatchChildAction, dispatchParentAction } from './dispatch-helper'
+import { ApplyActionException } from './exception'
+import { IllegalStateException } from './exception/illegal-state'
+import { SequenceNumberRepeatedException } from './exception/sequence'
import { assertActionIntegrity } from './integrity'
export const applyActionsFromDevice = async ({ database, request, websocket, connectedDevicesManager, eventHandler }: {
@@ -37,6 +36,7 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
request: ClientPushChangesRequest
connectedDevicesManager: VisibleConnectedDevicesManager
eventHandler: EventHandler
+ // no transaction here because this is directly called from an API endpoint
}): Promise<{ shouldDoFullSync: boolean }> => {
eventHandler.countEvent('applyActionsFromDevice')
@@ -47,213 +47,83 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
}
return database.transaction(async (transaction) => {
- const deviceEntryUnsafe = await database.device.findOne({
- where: {
- deviceAuthToken: request.deviceAuthToken
- },
- attributes: ['familyId', 'deviceId', 'nextSequenceNumber'],
- transaction
- })
-
- if (!deviceEntryUnsafe) {
- throw new Unauthorized()
- }
-
- const deviceEntry = {
- familyId: deviceEntryUnsafe.familyId,
- deviceId: deviceEntryUnsafe.deviceId,
- nextSequenceNumber: deviceEntryUnsafe.nextSequenceNumber
- }
-
- const familyEntryUnsafe = await database.family.findOne({
- where: {
- familyId: deviceEntry.familyId
- },
- transaction,
- attributes: ['hasFullVersion']
- })
-
- if (!familyEntryUnsafe) {
- throw new Error('missing family entry')
- }
-
- const familyEntry = {
- hasFullVersion: familyEntryUnsafe.hasFullVersion
- }
+ const baseInfo = await getApplyActionBaseInfo({ database, transaction, deviceAuthToken: request.deviceAuthToken })
const cache = new Cache({
database,
- hasFullVersion: familyEntry.hasFullVersion,
+ hasFullVersion: baseInfo.hasFullVersion,
transaction,
- familyId: deviceEntry.familyId,
+ familyId: baseInfo.familyId,
connectedDevicesManager
})
- let { nextSequenceNumber } = deviceEntry
+ let { nextSequenceNumber } = baseInfo
for (const action of request.actions) {
- if (action.sequenceNumber < nextSequenceNumber) {
- // action was already received
-
- eventHandler.countEvent('applyActionsFromDevice sequenceNumberRepeated')
-
- cache.requireFullSync()
- continue
- }
-
try {
+ if (action.sequenceNumber < nextSequenceNumber) {
+ // action was already received
+ throw new SequenceNumberRepeatedException()
+ }
+
await cache.subtransaction(async () => {
// update the next sequence number
nextSequenceNumber = action.sequenceNumber + 1
const { isChildLimitAdding } = await assertActionIntegrity({
- deviceId: deviceEntry.deviceId,
+ deviceId: baseInfo.deviceId,
cache,
- eventHandler,
action
})
- const parsedSerializedAction = JSON.parse(action.encodedAction)
-
if (action.type === 'appLogic') {
- if (!isSerializedAppLogicAction(parsedSerializedAction)) {
- eventHandler.countEvent('applyActionsFromDevice invalidAppLogicAction')
-
- throw new Error('invalid action: ' + action.encodedAction)
- }
-
- eventHandler.countEvent('applyActionsFromDevice action:' + parsedSerializedAction.type)
-
- const parsedAction = parseAppLogicAction(parsedSerializedAction)
-
- try {
- await dispatchAppLogicAction({
- action: parsedAction,
- cache,
- deviceId: deviceEntry.deviceId,
- eventHandler
- })
- } catch (ex) {
- eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
-
- throw ex
- }
+ await dispatchAppLogicAction({
+ action,
+ cache,
+ deviceId: baseInfo.deviceId,
+ eventHandler
+ })
} else if (action.type === 'parent') {
- if (!isSerializedParentAction(parsedSerializedAction)) {
- eventHandler.countEvent('applyActionsFromDevice invalidParentAction')
-
- throw new Error('invalid action' + action.encodedAction)
- }
-
- eventHandler.countEvent('applyActionsFromDevice, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
-
- const parsedAction = parseParentAction(parsedSerializedAction)
-
- try {
- if (isChildLimitAdding) {
- const deviceEntryUnsafe2 = await cache.database.device.findOne({
- attributes: ['currentUserId'],
- where: {
- familyId: cache.familyId,
- deviceId: deviceEntry.deviceId,
- currentUserId: action.userId
- },
- transaction: cache.transaction
- })
-
- if (!deviceEntryUnsafe2) {
- throw new Error('illegal state')
- }
-
- const deviceUserId = deviceEntryUnsafe2.currentUserId
-
- if (!deviceUserId) {
- throw new Error('no device user id set but child add self limit action requested')
- }
-
- const deviceUserEntryUnsafe = await cache.database.user.findOne({
- attributes: ['flags'],
- where: {
- familyId: cache.familyId,
- userId: deviceUserId,
- type: 'child'
- },
- transaction: cache.transaction
- })
-
- if (!deviceUserEntryUnsafe) {
- throw new Error('no child user found for child limit adding action')
- }
-
- if ((parseInt(deviceUserEntryUnsafe.flags, 10) & UserFlags.ALLOW_SELF_LIMIT_ADD) !== UserFlags.ALLOW_SELF_LIMIT_ADD) {
- throw new Error('child add limit action found but not allowed')
- }
-
- await dispatchParentAction({
- action: parsedAction,
- cache,
- parentUserId: action.userId,
- sourceDeviceId: deviceEntry.deviceId,
- fromChildSelfLimitAddChildUserId: deviceUserId
- })
- } else {
- await dispatchParentAction({
- action: parsedAction,
- cache,
- parentUserId: action.userId,
- sourceDeviceId: deviceEntry.deviceId,
- fromChildSelfLimitAddChildUserId: null
- })
- }
- } catch (ex) {
- eventHandler.countEvent('applyActionsFromDeviceWithError, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
-
- throw ex
- }
+ await dispatchParentAction({
+ action,
+ cache,
+ deviceId: baseInfo.deviceId,
+ eventHandler,
+ isChildLimitAdding
+ })
} else if (action.type === 'child') {
- if (!isSerializedChildAction(parsedSerializedAction)) {
- eventHandler.countEvent('applyActionsFromDevice invalidChildAction')
-
- throw new Error('invalid action: ' + action.encodedAction)
- }
-
- eventHandler.countEvent('applyActionsFromDevice action:' + parsedSerializedAction.type)
-
- const parsedAction = parseChildAction(parsedSerializedAction)
-
- try {
- await dispatchChildAction({
- action: parsedAction,
- cache,
- childUserId: action.userId,
- deviceId: deviceEntry.deviceId
- })
- } catch (ex) {
- eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
-
- throw ex
- }
+ await dispatchChildAction({
+ action,
+ cache,
+ childUserId: action.userId,
+ deviceId: baseInfo.deviceId,
+ eventHandler
+ })
} else {
- throw new Error('illegal state')
+ throw new IllegalStateException({ staticMessage: 'not possible action.type value' })
}
})
} catch (ex) {
- eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction')
+ if (ex instanceof ApplyActionException) {
+ eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction:' + ex.staticMessage)
+ } else {
+ eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction:other')
+ }
cache.requireFullSync()
}
}
// save new next sequence number
- if (nextSequenceNumber !== deviceEntry.nextSequenceNumber) {
+ if (nextSequenceNumber !== baseInfo.nextSequenceNumber) {
eventHandler.countEvent('applyActionsFromDevice updateSequenceNumber')
await database.device.update({
nextSequenceNumber
}, {
where: {
- familyId: deviceEntry.familyId,
- deviceId: deviceEntry.deviceId
+ familyId: baseInfo.familyId,
+ deviceId: baseInfo.deviceId
},
transaction
})
@@ -262,8 +132,8 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
await cache.saveModifiedVersionNumbers()
await notifyClientsAboutChangesDelayed({
- familyId: deviceEntry.familyId,
- sourceDeviceId: deviceEntry.deviceId,
+ familyId: baseInfo.familyId,
+ sourceDeviceId: baseInfo.deviceId,
isImportant: cache.areChangesImportant,
websocket,
database,
diff --git a/src/function/sync/apply-actions/integrity.ts b/src/function/sync/apply-actions/integrity.ts
index f10083a..8e5bf6d 100644
--- a/src/function/sync/apply-actions/integrity.ts
+++ b/src/function/sync/apply-actions/integrity.ts
@@ -17,13 +17,14 @@
import { createHash } from 'crypto'
import { ClientPushChangesRequestAction } from '../../../api/schema'
-import { EventHandler } from '../../../monitoring/eventhandler'
import { Cache } from './cache'
+import {
+ InvalidChildActionIntegrityValue, InvalidParentActionIntegrityValue, ParentDeviceActionWithoutParentDeviceException
+} from './exception/integrity'
-export async function assertActionIntegrity ({ action, cache, eventHandler, deviceId }: {
+export async function assertActionIntegrity ({ action, cache, deviceId }: {
action: ClientPushChangesRequestAction
cache: Cache
- eventHandler: EventHandler
deviceId: string
}): Promise<{ isChildLimitAdding: boolean }> {
let isChildLimitAdding = false
@@ -42,7 +43,7 @@ export async function assertActionIntegrity ({ action, cache, eventHandler, devi
})
if (!deviceEntryUnsafe) {
- throw new Error('user is not signed in at this device')
+ throw new ParentDeviceActionWithoutParentDeviceException()
}
// this ensures that the parent exists
@@ -61,9 +62,7 @@ export async function assertActionIntegrity ({ action, cache, eventHandler, devi
const expectedIntegrityValue = createHash('sha512').update(integrityData).digest('hex')
if (action.integrity !== expectedIntegrityValue) {
- eventHandler.countEvent('applyActionsFromDevice parentActionInvalidIntegrityValue')
-
- throw new Error('invalid integrity value')
+ throw new InvalidParentActionIntegrityValue()
}
}
}
@@ -79,9 +78,7 @@ export async function assertActionIntegrity ({ action, cache, eventHandler, devi
const expectedIntegrityValue = createHash('sha512').update(integrityData).digest('hex')
if (action.integrity !== expectedIntegrityValue) {
- eventHandler.countEvent('applyActionsFromDevice childActionInvalidIntegrityValue')
-
- throw new Error('invalid integrity value')
+ throw new InvalidChildActionIntegrityValue()
}
}
diff --git a/src/function/sync/apply-actions/parse-encoded-action.ts b/src/function/sync/apply-actions/parse-encoded-action.ts
new file mode 100644
index 0000000..3f43559
--- /dev/null
+++ b/src/function/sync/apply-actions/parse-encoded-action.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 { InvalidActionParameterException } from '../../../action/meta/exception'
+import { ClientPushChangesRequestAction } from '../../../api/schema'
+import { BadEncodedActionJsonException, InvalidActionParamterException } from './exception/invalidaction'
+
+export function parseEncodedAction (action: ClientPushChangesRequestAction): object {
+ try {
+ const result = JSON.parse(action.encodedAction)
+
+ if (typeof result !== 'object') throw new BadEncodedActionJsonException()
+
+ return result
+ } catch (ex) {
+ if (ex instanceof SyntaxError) {
+ throw new BadEncodedActionJsonException()
+ } else if (ex instanceof InvalidActionParameterException) {
+ throw new InvalidActionParamterException(ex)
+ } else throw ex
+ }
+}
diff --git a/src/function/sync/get-server-data-status.ts b/src/function/sync/get-server-data-status.ts
index b2dc26d..44cd6b6 100644
--- a/src/function/sync/get-server-data-status.ts
+++ b/src/function/sync/get-server-data-status.ts
@@ -19,6 +19,7 @@ import { difference, filter, intersection } from 'lodash'
import * as Sequelize from 'sequelize'
import { config } from '../../config'
import { Database } from '../../database'
+import { StaticMessageException } from '../../exception'
import { getStatusMessage } from '../../function/statusmessage'
import { ClientDataStatus } from '../../object/clientdatastatus'
import {
@@ -48,7 +49,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
})
if (!familyEntryUnsafe) {
- throw new Error('illegal state')
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find family entry' })
}
const familyEntry = {
@@ -210,7 +211,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
const entry = serverInstalledAppsVersions.find((item) => item.deviceId === deviceId)
if (!entry) {
- throw new Error('illegal state')
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find device entry' })
}
return entry.installedAppsVersion
@@ -333,7 +334,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
const clientEntry = clientStatus.categories[categoryId]
if ((!serverEntry) || (!clientEntry)) {
- throw new Error('illegal state')
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category overview item again' })
}
if (serverEntry.baseVersion !== clientEntry.base) {
@@ -460,7 +461,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
if (!categoryEntry) {
- throw new Error('illegal state')
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
}
return categoryEntry.assignedAppsVersion
@@ -509,7 +510,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
if (!categoryEntry) {
- throw new Error('illegal state')
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
}
return categoryEntry.timeLimitRulesVersion
@@ -586,7 +587,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
if (!categoryEntry) {
- throw new Error('illegal state')
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
}
return categoryEntry.usedTimesVersion
@@ -614,3 +615,9 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
return result
}
+
+export class GetServerDataStatusIllegalStateException extends StaticMessageException {
+ constructor ({ staticMessage }: { staticMessage: string }) {
+ super({ staticMessage: 'GetServerDataStatusIllegalStateException: ' + staticMessage })
+ }
+}
diff --git a/src/model/appactivity.ts b/src/model/appactivity.ts
index ce5a47f..19ba967 100644
--- a/src/model/appactivity.ts
+++ b/src/model/appactivity.ts
@@ -15,7 +15,7 @@ export class AppActivityItem {
title: string
}) {
if ((!packageName) || (!activityName)) {
- throw new Error('incomplete app activity')
+ throw new IncompleteAppActivityItemException('incomplete app activity')
}
this.packageName = packageName
@@ -49,7 +49,7 @@ export class RemovedAppActivityItem {
activityName: string
}) {
if ((!packageName) || (!activityName)) {
- throw new Error('incomplete app activity')
+ throw new IncompleteAppActivityItemException('incomplete app activity')
}
this.packageName = packageName
@@ -68,3 +68,5 @@ export class RemovedAppActivityItem {
this.activityName
])
}
+
+export class IncompleteAppActivityItemException extends Error {}
diff --git a/src/model/timelimitrule.ts b/src/model/timelimitrule.ts
index f5d9e82..d82d627 100644
--- a/src/model/timelimitrule.ts
+++ b/src/model/timelimitrule.ts
@@ -57,7 +57,7 @@ export class TimelimitRule {
assertIdWithinFamily(categoryId)
if (maxTimeInMillis < 0 || (!Number.isSafeInteger(maxTimeInMillis))) {
- throw new Error('maxTimeInMillis must be >= 0')
+ throw new ParseTimeLimitRuleException('maxTimeInMillis must be >= 0')
}
if (!(
@@ -65,7 +65,7 @@ export class TimelimitRule {
dayMask < 0 ||
dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)
)) {
- throw new Error('invalid day mask')
+ throw new ParseTimeLimitRuleException('invalid day mask')
}
if (
@@ -74,15 +74,15 @@ export class TimelimitRule {
(!Number.isSafeInteger(sessionDurationMilliseconds)) ||
(!Number.isSafeInteger(sessionPauseMilliseconds))
) {
- throw new Error()
+ throw new ParseTimeLimitRuleException()
}
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
- throw new Error()
+ throw new ParseTimeLimitRuleException()
}
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
- throw new Error()
+ throw new ParseTimeLimitRuleException()
}
}
@@ -124,3 +124,5 @@ export interface SerializedTimeLimitRule {
dur?: number
pause?: number
}
+
+export class ParseTimeLimitRuleException extends Error {}
diff --git a/src/util/bitmask.ts b/src/util/bitmask.ts
index 2d3b529..dcbe010 100644
--- a/src/util/bitmask.ts
+++ b/src/util/bitmask.ts
@@ -21,7 +21,7 @@ export const serializedBitmaskRegex = /^(\d*,\d*(,\d*,\d*)*)?$/
export const validateBitmask = (bitmask: string, maxLength: number) => {
if (!serializedBitmaskRegex.test(bitmask)) {
- throw new Error('bitmask does not match regex')
+ throw new BitmapValidationException('bitmask does not match regex')
}
if (bitmask === '') {
@@ -31,22 +31,22 @@ export const validateBitmask = (bitmask: string, maxLength: number) => {
const splitpoints = split(bitmask, ',').map((item) => parseInt(item, 10))
if (splitpoints.findIndex((item) => !Number.isSafeInteger(item)) !== -1) {
- throw new Error('bitmask contains non-safe integers')
+ throw new BitmapValidationException('bitmask contains non-safe integers')
}
if (splitpoints.findIndex((item) => item < 0) !== -1) {
- throw new Error('bitmask contains negative integers')
+ throw new BitmapValidationException('bitmask contains negative integers')
}
if (splitpoints.findIndex((item) => item > maxLength) !== -1) {
- throw new Error('bitmask contains integers bigger than maxSize')
+ throw new BitmapValidationException('bitmask contains integers bigger than maxSize')
}
let previousValue = -1
splitpoints.forEach((item) => {
if (item <= previousValue) {
- throw new Error('bitmask numbers are not strictly sorted')
+ throw new BitmapValidationException('bitmask numbers are not strictly sorted')
}
previousValue = item
@@ -72,3 +72,5 @@ export const validateAndParseBitmask = (bitmask: string, maxLength: number) => {
return result
}
+
+export class BitmapValidationException extends Error {}
diff --git a/src/util/category.ts b/src/util/category.ts
index 310d918..c229a62 100644
--- a/src/util/category.ts
+++ b/src/util/category.ts
@@ -23,7 +23,7 @@ export function getCategoryWithParentCategories (categories: Array<{ categoryId:
const startCategory = categoryById.get(startCategoryId)
if (!startCategory) {
- throw new Error('start category not found')
+ throw new GetParentCategoriesException('start category not found')
}
const categoryIds = [ startCategoryId ]
@@ -38,3 +38,5 @@ export function getCategoryWithParentCategories (categories: Array<{ categoryId:
return categoryIds
}
+
+export class GetParentCategoriesException extends Error {}
diff --git a/src/util/hexstring.ts b/src/util/hexstring.ts
index ee90fb2..e2b5d44 100644
--- a/src/util/hexstring.ts
+++ b/src/util/hexstring.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,16 +15,26 @@
* along with this program. If not, see .
*/
-export function assertIsHexString (value: string) {
+export function checkIfHexString (value: string) {
if (value.length % 2 !== 0) {
- throw new Error('expected hex string but has got uneven length')
+ return false
}
for (let i = 0; i < value.length; i++) {
const char = value[i]
if ('0123456789abcdef'.indexOf(char) === -1) {
- throw new Error('expected hex string but got invalid char')
+ return false
}
}
+
+ return true
}
+
+export function assertIsHexString (value: string) {
+ if (!checkIfHexString(value)) {
+ throw new HexStringValidationException('wanted hex string but did not get one')
+ }
+}
+
+export class HexStringValidationException extends Error {}
diff --git a/src/util/list.ts b/src/util/list.ts
index 5c1c969..c89909b 100644
--- a/src/util/list.ts
+++ b/src/util/list.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
@@ -17,18 +17,6 @@
import { uniq } from 'lodash'
-export function assertNonEmptyListWithoutDuplicates (list: Array) {
- if (list.length === 0) {
- throw new Error('expected not empty list')
- }
-
- if (uniq(list).length !== list.length) {
- throw new Error('expected list without duplicates')
- }
-}
-
-export function assertListWithoutDuplicates (list: Array) {
- if (uniq(list).length !== list.length) {
- throw new Error('expected list without duplicates')
- }
+export function hasDuplicates (list: Array): boolean {
+ return uniq(list).length !== list.length
}
diff --git a/src/util/mail.ts b/src/util/mail.ts
index 989ef75..e38404f 100644
--- a/src/util/mail.ts
+++ b/src/util/mail.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
@@ -19,6 +19,7 @@ import { parseOneAddress } from 'email-addresses'
import * as Email from 'email-templates'
import { join } from 'path'
import { config } from '../config'
+import { IllegalStateException } from '../exception'
const mailimprint = process.env.MAIL_IMPRINT || 'not defined'
const mailServerBlacklist = (process.env.MAIL_SERVER_BLACKLIST || '').split(',').filter((item) => !!item)
@@ -129,7 +130,7 @@ export function sanitizeMailAddress (input: string): string | null {
const address = (parsed as any).address
if (typeof address !== 'string') {
- throw new Error('illegal state')
+ throw new IllegalStateException({ staticMessage: 'mail address is not a string' })
}
return address
diff --git a/src/util/token.ts b/src/util/token.ts
index 7f901f5..39f683a 100644
--- a/src/util/token.ts
+++ b/src/util/token.ts
@@ -16,6 +16,7 @@
*/
import * as TokenGenerator from 'tokgen'
+import { ValidationException } from '../exception'
const authTokenGenerator = new TokenGenerator({
length: 32,
@@ -33,7 +34,10 @@ export const generateIdWithinFamily = () => idWithinFamilyGenerator.generate()
export const isIdWithinFamily = (id: string) => id.length === 6 && /^[a-zA-Z0-9]+$/.test(id)
export const assertIdWithinFamily = (id: string) => {
if (!isIdWithinFamily(id)) {
- throw new Error('invalid id within family')
+ throw new ValidationException({
+ staticMessage: 'invalid id within family',
+ dynamicMessage: 'invalid id within family: ' + id
+ })
}
}