mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2025-10-03 09:49:32 +02:00
Refactor exception usage
This commit is contained in:
parent
d68b425e0e
commit
8d65c5d777
148 changed files with 2061 additions and 769 deletions
|
@ -15,9 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'AddCategoryApps'
|
||||||
|
|
||||||
export class AddCategoryAppsAction extends ParentAction {
|
export class AddCategoryAppsAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -26,8 +27,8 @@ export class AddCategoryAppsAction extends ParentAction {
|
||||||
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
|
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
assertNonEmptyListWithoutDuplicates(packageNames)
|
assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.packageNames = packageNames
|
this.packageNames = packageNames
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { anonymizedNetworkIdLength } from '../database/categorynetworkid'
|
import { anonymizedNetworkIdLength } from '../database/categorynetworkid'
|
||||||
import { assertIsHexString } from '../util/hexstring'
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertHexString, assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'AddCategoryNetworkIdAction'
|
||||||
|
|
||||||
export class AddCategoryNetworkIdAction extends ParentAction {
|
export class AddCategoryNetworkIdAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -32,10 +34,16 @@ export class AddCategoryNetworkIdAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
assertIdWithinFamily(itemId)
|
assertIdWithinFamily({ actionType, field: 'itemId', value: itemId })
|
||||||
assertIsHexString(hashedNetworkId)
|
assertHexString({ actionType, field: 'hashedNetworkId', value: hashedNetworkId })
|
||||||
if (hashedNetworkId.length !== anonymizedNetworkIdLength) throw new Error('wrong network id length')
|
|
||||||
|
if (hashedNetworkId.length !== anonymizedNetworkIdLength) {
|
||||||
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'wrong network id length'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.itemId = itemId
|
this.itemId = itemId
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { InstalledApp, SerializedInstalledApp } from '../model/installedapp'
|
import { InstalledApp, SerializedInstalledApp } from '../model/installedapp'
|
||||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
|
||||||
import { AppLogicAction } from './basetypes'
|
import { AppLogicAction } from './basetypes'
|
||||||
|
import { assertNonEmptyListWithoutDuplicates } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'AddInstalledAppsAction'
|
||||||
|
|
||||||
export class AddInstalledAppsAction extends AppLogicAction {
|
export class AddInstalledAppsAction extends AppLogicAction {
|
||||||
readonly apps: Array<InstalledApp>
|
readonly apps: Array<InstalledApp>
|
||||||
|
@ -25,7 +27,7 @@ export class AddInstalledAppsAction extends AppLogicAction {
|
||||||
constructor ({ apps }: {apps: Array<InstalledApp>}) {
|
constructor ({ apps }: {apps: Array<InstalledApp>}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertNonEmptyListWithoutDuplicates(apps.map((app) => app.packageName))
|
assertNonEmptyListWithoutDuplicates({ actionType, field: 'apps', list: apps.map((app) => app.packageName) })
|
||||||
|
|
||||||
this.apps = apps
|
this.apps = apps
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { AppLogicAction } from './basetypes'
|
import { AppLogicAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'AddUsedTimeAction'
|
||||||
|
|
||||||
export class AddUsedTimeAction extends AppLogicAction {
|
export class AddUsedTimeAction extends AppLogicAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -32,18 +35,30 @@ export class AddUsedTimeAction extends AppLogicAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) {
|
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))) {
|
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))) {
|
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
|
this.categoryId = categoryId
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { uniq } from 'lodash'
|
|
||||||
import { MinuteOfDay } from '../util/minuteofday'
|
import { MinuteOfDay } from '../util/minuteofday'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { AppLogicAction } from './basetypes'
|
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 {
|
export class AddUsedTimeActionVersion2 extends AppLogicAction {
|
||||||
readonly dayOfEpoch: number
|
readonly dayOfEpoch: number
|
||||||
|
@ -44,46 +46,54 @@ export class AddUsedTimeActionVersion2 extends AppLogicAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
if (dayOfEpoch < 0 || (!Number.isSafeInteger(dayOfEpoch))) {
|
assertSafeInteger({ actionType, field: 'dayOfEpoch', value: dayOfEpoch })
|
||||||
throw new Error('illegal dayOfEpoch')
|
|
||||||
|
if (dayOfEpoch < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'dayOfEpoch', value: dayOfEpoch })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trustedTimestamp < 0 || (!Number.isSafeInteger(trustedTimestamp))) {
|
assertSafeInteger({ actionType, field: 'trustedTimestamp', value: trustedTimestamp })
|
||||||
throw new Error('illegal trustedTimestamp')
|
|
||||||
|
if (trustedTimestamp < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'trustedTimestamp', value: trustedTimestamp })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length === 0) {
|
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) {
|
assertListWithoutDuplicates({
|
||||||
throw new Error('duplicate category ids')
|
actionType,
|
||||||
}
|
field: 'categoryIds',
|
||||||
|
list: items.map((item) => item.categoryId)
|
||||||
|
})
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
assertIdWithinFamily(item.categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: item.categoryId })
|
||||||
|
|
||||||
if (item.timeToAdd < 0 || (!Number.isSafeInteger(item.timeToAdd))) {
|
assertSafeInteger({ actionType, field: 'timeToAdd', value: item.timeToAdd })
|
||||||
throw new Error('illegal timeToAdd')
|
|
||||||
|
if (item.timeToAdd < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'timeToAdd', value: item.timeToAdd })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.extraTimeToSubtract < 0 || (!Number.isSafeInteger(item.extraTimeToSubtract))) {
|
assertSafeInteger({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract })
|
||||||
throw new Error('illegal extra time to subtract')
|
|
||||||
|
if (item.extraTimeToSubtract < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'extraTimeToSubtract', value: item.extraTimeToSubtract })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
assertListWithoutDuplicates({
|
||||||
uniq(item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))).length !==
|
actionType,
|
||||||
item.additionalCountingSlots.length
|
field: 'additionalCountingSlots',
|
||||||
) {
|
list: item.additionalCountingSlots.map((item) => JSON.stringify(item.serialize()))
|
||||||
throw new Error()
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
assertListWithoutDuplicates({
|
||||||
uniq(item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))).length !==
|
actionType,
|
||||||
item.sessionDurationLimits.length
|
field: 'sessionDurationLimits',
|
||||||
) {
|
list: item.sessionDurationLimits.map((item) => JSON.stringify(item.serialize()))
|
||||||
throw new Error()
|
})
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.dayOfEpoch = dayOfEpoch
|
this.dayOfEpoch = dayOfEpoch
|
||||||
|
@ -111,16 +121,15 @@ class AddUsedTimeActionItemAdditionalCountingSlot {
|
||||||
readonly end: number
|
readonly end: number
|
||||||
|
|
||||||
constructor ({ start, end }: { start: number, end: number }) {
|
constructor ({ start, end }: { start: number, end: number }) {
|
||||||
if ((!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end))) {
|
assertSafeInteger({ actionType, field: 'start', value: start })
|
||||||
throw new Error()
|
assertSafeInteger({ actionType, field: 'end', value: end })
|
||||||
}
|
|
||||||
|
|
||||||
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > 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) {
|
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
|
this.start = start
|
||||||
|
@ -139,19 +148,17 @@ class AddUsedTimeActionItemSessionDurationLimitSlot {
|
||||||
readonly pause: number
|
readonly pause: number
|
||||||
|
|
||||||
constructor ({ start, end, duration, pause }: { start: number, end: number, duration: number, pause: number }) {
|
constructor ({ start, end, duration, pause }: { start: number, end: number, duration: number, pause: number }) {
|
||||||
if (
|
assertSafeInteger({ actionType, field: 'start', value: start })
|
||||||
(!Number.isSafeInteger(start)) || (!Number.isSafeInteger(end)) ||
|
assertSafeInteger({ actionType, field: 'end', value: end })
|
||||||
(!Number.isSafeInteger(duration)) || (!Number.isSafeInteger(pause))
|
assertSafeInteger({ actionType, field: 'duration', value: duration })
|
||||||
) {
|
assertSafeInteger({ actionType, field: 'pause', value: pause })
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > 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 (duration <= 0 || pause <= 0) {
|
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
|
this.start = start
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertParentPasswordValid, ParentPassword } from '../api/schema'
|
import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'AddUserAction'
|
||||||
|
|
||||||
export class AddUserAction extends ParentAction {
|
export class AddUserAction extends ParentAction {
|
||||||
readonly userId: string
|
readonly userId: string
|
||||||
|
@ -35,7 +38,7 @@ export class AddUserAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
this.name = name
|
this.name = name
|
||||||
|
@ -45,12 +48,24 @@ export class AddUserAction extends ParentAction {
|
||||||
|
|
||||||
if (userType === 'parent') {
|
if (userType === 'parent') {
|
||||||
if (!password) {
|
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) {
|
if (password) {
|
||||||
assertParentPasswordValid(password)
|
try {
|
||||||
|
assertParentPasswordValid(password)
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex instanceof ParentPasswordValidationException) {
|
||||||
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'invalid password data'
|
||||||
|
})
|
||||||
|
} else throw ex
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createDecipheriv, createHash } from 'crypto'
|
import { createDecipheriv, createHash } from 'crypto'
|
||||||
import { assertIsHexString } from '../util/hexstring'
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertHexString, assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'ChangeParentPasswordAction'
|
||||||
|
|
||||||
export class ChangeParentPasswordAction extends ParentAction {
|
export class ChangeParentPasswordAction extends ParentAction {
|
||||||
readonly parentUserId: string
|
readonly parentUserId: string
|
||||||
|
@ -36,7 +38,7 @@ export class ChangeParentPasswordAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(parentUserId)
|
assertIdWithinFamily({ actionType, field: 'parentUserId', value: parentUserId })
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!parentUserId) ||
|
(!parentUserId) ||
|
||||||
|
@ -45,15 +47,25 @@ export class ChangeParentPasswordAction extends ParentAction {
|
||||||
(!newPasswordSecondHashEncrypted) ||
|
(!newPasswordSecondHashEncrypted) ||
|
||||||
(!integrity)
|
(!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) {
|
if (integrity.length !== 128) {
|
||||||
throw new Error('wrong length of integrity data')
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'wrong length of integrity data'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
assertIsHexString(newPasswordSecondHashEncrypted)
|
assertHexString({ actionType, field: 'newPasswordSecondHashEncrypted', value: newPasswordSecondHashEncrypted })
|
||||||
assertIsHexString(integrity)
|
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.parentUserId = parentUserId
|
||||||
this.newPasswordFirstHash = newPasswordFirstHash
|
this.newPasswordFirstHash = newPasswordFirstHash
|
||||||
|
@ -82,15 +94,11 @@ export class ChangeParentPasswordAction extends ParentAction {
|
||||||
const expected = createHash('sha512').update(integrityData).digest('hex')
|
const expected = createHash('sha512').update(integrityData).digest('hex')
|
||||||
|
|
||||||
if (expected !== this.integrity) {
|
if (expected !== this.integrity) {
|
||||||
throw new Error('invalid integrity for change parent password action')
|
throw new InvalidChangeParentPasswordIntegrityException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptSecondHash ({ oldPasswordSecondHash }: {oldPasswordSecondHash: string}) {
|
decryptSecondHash ({ oldPasswordSecondHash }: { oldPasswordSecondHash: string }) {
|
||||||
if (this.newPasswordSecondHashEncrypted.length <= 70) {
|
|
||||||
throw new Error('wrong length of the new password')
|
|
||||||
}
|
|
||||||
|
|
||||||
const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32)
|
const ivHex = this.newPasswordSecondHashEncrypted.substring(0, 32)
|
||||||
const salt = this.newPasswordSecondHashEncrypted.substring(32, 64)
|
const salt = this.newPasswordSecondHashEncrypted.substring(32, 64)
|
||||||
const encryptedData = this.newPasswordSecondHashEncrypted.substring(64)
|
const encryptedData = this.newPasswordSecondHashEncrypted.substring(64)
|
||||||
|
@ -115,3 +123,7 @@ export interface SerializedChangeParentPasswordAction {
|
||||||
secondHashEncrypted: string
|
secondHashEncrypted: string
|
||||||
integrity: string
|
integrity: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InvalidChangeParentPasswordIntegrityException extends Error {
|
||||||
|
constructor () { super('invalid integrity for change parent password action') }
|
||||||
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertParentPasswordValid, ParentPassword } from '../api/schema'
|
import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
|
||||||
import { ChildAction } from './basetypes'
|
import { ChildAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
|
||||||
|
const actionType = 'ChildChangePasswordAction'
|
||||||
|
|
||||||
export class ChildChangePasswordAction extends ChildAction {
|
export class ChildChangePasswordAction extends ChildAction {
|
||||||
readonly password: ParentPassword
|
readonly password: ParentPassword
|
||||||
|
@ -26,7 +29,16 @@ export class ChildChangePasswordAction extends ChildAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
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
|
this.password = password
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class ChildSignInAction extends ChildAction {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse = (action: SerializedChildSignInAction) => (
|
static parse = (_: SerializedChildSignInAction) => (
|
||||||
new ChildSignInAction()
|
new ChildSignInAction()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'CreateCategoryAction'
|
||||||
|
|
||||||
export class CreateCategoryAction extends ParentAction {
|
export class CreateCategoryAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -26,8 +28,8 @@ export class CreateCategoryAction extends ParentAction {
|
||||||
constructor ({ categoryId, childId, title }: {categoryId: string, childId: string, title: string}) {
|
constructor ({ categoryId, childId, title }: {categoryId: string, childId: string, title: string}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
assertIdWithinFamily(childId)
|
assertIdWithinFamily({ actionType, field: 'childId', value: childId })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.childId = childId
|
this.childId = childId
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule'
|
import { ParseTimeLimitRuleException, SerializedTimeLimitRule, TimelimitRule } from '../model/timelimitrule'
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
|
||||||
|
const actionType = 'CreateTimeLimitRuleAction'
|
||||||
|
|
||||||
export class CreateTimeLimitRuleAction extends ParentAction {
|
export class CreateTimeLimitRuleAction extends ParentAction {
|
||||||
rule: TimelimitRule
|
rule: TimelimitRule
|
||||||
|
@ -27,11 +30,21 @@ export class CreateTimeLimitRuleAction extends ParentAction {
|
||||||
this.rule = rule
|
this.rule = rule
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => (
|
static parse = ({ rule }: SerializedCreateTimelimtRuleAction) => {
|
||||||
new CreateTimeLimitRuleAction({
|
try {
|
||||||
rule: TimelimitRule.parse(rule)
|
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 {
|
export interface SerializedCreateTimelimtRuleAction {
|
||||||
|
|
|
@ -15,16 +15,18 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'DeleteCategoryAction'
|
||||||
|
|
||||||
export class DeleteCategoryAction extends ParentAction {
|
export class DeleteCategoryAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
|
||||||
constructor ({ categoryId }: {categoryId: string}) {
|
constructor ({ categoryId }: { categoryId: string }) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'DeleteTimeLimitRuleAction'
|
||||||
|
|
||||||
export class DeleteTimeLimitRuleAction extends ParentAction {
|
export class DeleteTimeLimitRuleAction extends ParentAction {
|
||||||
readonly ruleId: string
|
readonly ruleId: string
|
||||||
|
@ -24,7 +26,7 @@ export class DeleteTimeLimitRuleAction extends ParentAction {
|
||||||
constructor ({ ruleId }: {ruleId: string}) {
|
constructor ({ ruleId }: {ruleId: string}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(ruleId)
|
assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
|
||||||
|
|
||||||
this.ruleId = ruleId
|
this.ruleId = ruleId
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DeviceHadManipulationFlags } from '../database/device'
|
import { DeviceHadManipulationFlags } from '../database/device'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'IgnoreManipulationAction'
|
||||||
|
|
||||||
export class IgnoreManipulationAction extends ParentAction {
|
export class IgnoreManipulationAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -51,13 +53,14 @@ export class IgnoreManipulationAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
|
assertSafeInteger({ actionType, field: 'ignoreHadManipulationFlags', value: ignoreHadManipulationFlags })
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!Number.isSafeInteger(ignoreHadManipulationFlags)) ||
|
|
||||||
ignoreHadManipulationFlags < 0 || ignoreHadManipulationFlags > DeviceHadManipulationFlags.ALL
|
ignoreHadManipulationFlags < 0 || ignoreHadManipulationFlags > DeviceHadManipulationFlags.ALL
|
||||||
) {
|
) {
|
||||||
throw new Error('invalid ignoreHadManipulationFlags')
|
throwOutOfRange({ actionType, field: 'ignoreHadManipulationFlags', value: ignoreHadManipulationFlags })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'IncrementCategoryExtraTimeAction'
|
||||||
|
|
||||||
export class IncrementCategoryExtraTimeAction extends ParentAction {
|
export class IncrementCategoryExtraTimeAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -26,14 +28,18 @@ export class IncrementCategoryExtraTimeAction extends ParentAction {
|
||||||
constructor ({ categoryId, addedExtraTime, day }: {categoryId: string, addedExtraTime: number, day: number}) {
|
constructor ({ categoryId, addedExtraTime, day }: {categoryId: string, addedExtraTime: number, day: number}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
if (addedExtraTime <= 0 || (!Number.isSafeInteger(addedExtraTime))) {
|
assertSafeInteger({ actionType, field: 'addedExtraTime', value: addedExtraTime })
|
||||||
throw new Error('must add some extra time with IncrementCategoryExtraTimeAction')
|
|
||||||
|
if (addedExtraTime < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'addedExtraTime', value: addedExtraTime })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (day < -1 || (!Number.isSafeInteger(day))) {
|
assertSafeInteger({ actionType, field: 'day', value: day })
|
||||||
throw Error('day must be valid')
|
|
||||||
|
if (day < -1) {
|
||||||
|
throwOutOfRange({ actionType, field: 'day', value: day })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
|
|
40
src/action/meta/exception.ts
Normal file
40
src/action/meta/exception.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
104
src/action/meta/util.ts
Normal file
104
src/action/meta/util.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
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<string>
|
||||||
|
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(';')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'RemoveCategoryAppsAction'
|
||||||
|
|
||||||
export class RemoveCategoryAppsAction extends ParentAction {
|
export class RemoveCategoryAppsAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -26,8 +27,8 @@ export class RemoveCategoryAppsAction extends ParentAction {
|
||||||
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
|
constructor ({ categoryId, packageNames }: {categoryId: string, packageNames: Array<string>}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
assertNonEmptyListWithoutDuplicates(packageNames)
|
assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.packageNames = packageNames
|
this.packageNames = packageNames
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertNonEmptyListWithoutDuplicates } from '../util/list'
|
|
||||||
import { AppLogicAction } from './basetypes'
|
import { AppLogicAction } from './basetypes'
|
||||||
|
import { assertNonEmptyListWithoutDuplicates } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'RemoveInstalledAppsAction'
|
||||||
|
|
||||||
export class RemoveInstalledAppsAction extends AppLogicAction {
|
export class RemoveInstalledAppsAction extends AppLogicAction {
|
||||||
readonly packageNames: Array<string>
|
readonly packageNames: Array<string>
|
||||||
|
@ -24,7 +26,7 @@ export class RemoveInstalledAppsAction extends AppLogicAction {
|
||||||
constructor ({ packageNames }: {packageNames: Array<string>}) {
|
constructor ({ packageNames }: {packageNames: Array<string>}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertNonEmptyListWithoutDuplicates(packageNames)
|
assertNonEmptyListWithoutDuplicates({ actionType, field: 'packageNames', list: packageNames })
|
||||||
|
|
||||||
this.packageNames = packageNames
|
this.packageNames = packageNames
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'RemoveUserAction'
|
||||||
|
|
||||||
export class RemoveUserAction extends ParentAction {
|
export class RemoveUserAction extends ParentAction {
|
||||||
readonly userId: string
|
readonly userId: string
|
||||||
|
@ -31,7 +33,7 @@ export class RemoveUserAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
this.authentication = authentication
|
this.authentication = authentication
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'RenameChildAction'
|
||||||
|
|
||||||
export class RenameChildAction extends ParentAction {
|
export class RenameChildAction extends ParentAction {
|
||||||
readonly childId: string
|
readonly childId: string
|
||||||
|
@ -28,10 +31,13 @@ export class RenameChildAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(childId)
|
assertIdWithinFamily({ actionType, field: 'childId', value: childId })
|
||||||
|
|
||||||
if (newName === '') {
|
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
|
this.childId = childId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'ResetCategoryNetworkIdsAction'
|
||||||
|
|
||||||
export class ResetCategoryNetworkIdsAction extends ParentAction {
|
export class ResetCategoryNetworkIdsAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -26,7 +28,7 @@ export class ResetCategoryNetworkIdsAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'ResetParentBlockedTimesAction'
|
||||||
|
|
||||||
export class ResetParentBlockedTimesAction extends ParentAction {
|
export class ResetParentBlockedTimesAction extends ParentAction {
|
||||||
readonly parentId: string
|
readonly parentId: string
|
||||||
|
@ -26,7 +28,7 @@ export class ResetParentBlockedTimesAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(parentId)
|
assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
|
||||||
|
|
||||||
this.parentId = parentId
|
this.parentId = parentId
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { AddUsedTimeAction, SerializedAddUsedTimeAction } from '../addusedtime'
|
||||||
import { AddUsedTimeActionVersion2, SerializedAddUsedTimeActionVersion2 } from '../addusedtime2'
|
import { AddUsedTimeActionVersion2, SerializedAddUsedTimeActionVersion2 } from '../addusedtime2'
|
||||||
import { AppLogicAction } from '../basetypes'
|
import { AppLogicAction } from '../basetypes'
|
||||||
import { ForceSyncAction, SerializedForceSyncAction } from '../forcesync'
|
import { ForceSyncAction, SerializedForceSyncAction } from '../forcesync'
|
||||||
|
import { UnknownActionTypeException } from '../meta/exception'
|
||||||
import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps'
|
import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps'
|
||||||
import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice'
|
import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice'
|
||||||
import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin'
|
import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin'
|
||||||
|
@ -57,6 +58,6 @@ export const parseAppLogicAction = (serialized: SerializedAppLogicAction): AppLo
|
||||||
} else if (serialized.type === 'UPDATE_DEVICE_STATUS') {
|
} else if (serialized.type === 'UPDATE_DEVICE_STATUS') {
|
||||||
return UpdateDeviceStatusAction.parse(serialized)
|
return UpdateDeviceStatusAction.parse(serialized)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('illegal state: unsupported type at parseAppLogicAction')
|
throw new UnknownActionTypeException({ group: 'app logic' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import { ChildChangePasswordAction, SerializedChildChangePasswordAction } from '../childchangepassword'
|
import { ChildChangePasswordAction, SerializedChildChangePasswordAction } from '../childchangepassword'
|
||||||
import { ChildSignInAction, SerializedChildSignInAction } from '../childsignin'
|
import { ChildSignInAction, SerializedChildSignInAction } from '../childsignin'
|
||||||
|
import { UnknownActionTypeException } from '../meta/exception'
|
||||||
|
|
||||||
export type SerializedChildAction = SerializedChildChangePasswordAction | SerializedChildSignInAction
|
export type SerializedChildAction = SerializedChildChangePasswordAction | SerializedChildSignInAction
|
||||||
|
|
||||||
|
@ -26,6 +27,6 @@ export const parseChildAction = (serialized: SerializedChildAction) => {
|
||||||
} else if (serialized.type === 'CHILD_SIGN_IN') {
|
} else if (serialized.type === 'CHILD_SIGN_IN') {
|
||||||
return ChildSignInAction.parse(serialized)
|
return ChildSignInAction.parse(serialized)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('illegal state: unsupported type at parseChildAction')
|
throw new UnknownActionTypeException({ group: 'child' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { DeleteCategoryAction, SerializedDeleteCategoryAction } from '../deletec
|
||||||
import { DeleteTimeLimitRuleAction, SerializedDeleteTimeLimitRuleAction } from '../deletetimelimitrule'
|
import { DeleteTimeLimitRuleAction, SerializedDeleteTimeLimitRuleAction } from '../deletetimelimitrule'
|
||||||
import { IgnoreManipulationAction, SerializedIgnoreManipulationAction } from '../ignoremanipulation'
|
import { IgnoreManipulationAction, SerializedIgnoreManipulationAction } from '../ignoremanipulation'
|
||||||
import { IncrementCategoryExtraTimeAction, SerializedIncrementCategoryExtraTimeAction } from '../incrementcategoryextratime'
|
import { IncrementCategoryExtraTimeAction, SerializedIncrementCategoryExtraTimeAction } from '../incrementcategoryextratime'
|
||||||
|
import { UnknownActionTypeException } from '../meta/exception'
|
||||||
import { RemoveCategoryAppsAction, SerializedRemoveCategoryAppsAction } from '../removecategoryapps'
|
import { RemoveCategoryAppsAction, SerializedRemoveCategoryAppsAction } from '../removecategoryapps'
|
||||||
import { RemoveUserAction, SerializedRemoveUserAction } from '../removeuser'
|
import { RemoveUserAction, SerializedRemoveUserAction } from '../removeuser'
|
||||||
import { RenameChildAction, SerializedRenameChildAction } from '../renamechild'
|
import { RenameChildAction, SerializedRenameChildAction } from '../renamechild'
|
||||||
|
@ -193,6 +194,6 @@ export const parseParentAction = (action: SerializedParentAction): ParentAction
|
||||||
} else if (action.type === 'UPDATE_USER_LIMIT_LOGIN_CATEGORY') {
|
} else if (action.type === 'UPDATE_USER_LIMIT_LOGIN_CATEGORY') {
|
||||||
return UpdateUserLimitLoginCategory.parse(action)
|
return UpdateUserLimitLoginCategory.parse(action)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('illegal state: invalid type for action at parseParentAction')
|
throw new UnknownActionTypeException({ group: 'parent' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetCategoryExtraTimeAction'
|
||||||
|
|
||||||
export class SetCategoryExtraTimeAction extends ParentAction {
|
export class SetCategoryExtraTimeAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -26,14 +28,18 @@ export class SetCategoryExtraTimeAction extends ParentAction {
|
||||||
constructor ({ categoryId, newExtraTime, day }: {categoryId: string, newExtraTime: number, day: number}) {
|
constructor ({ categoryId, newExtraTime, day }: {categoryId: string, newExtraTime: number, day: number}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
if (newExtraTime < 0 || (!Number.isSafeInteger(newExtraTime))) {
|
assertSafeInteger({ actionType, field: 'newExtraTime', value: newExtraTime })
|
||||||
throw Error('newExtraTime must be >= 0')
|
|
||||||
|
if (newExtraTime < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'newExtraTime', value: newExtraTime })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (day < -1 || (!Number.isSafeInteger(day))) {
|
assertSafeInteger({ actionType, field: 'day', value: day })
|
||||||
throw Error('day must be valid')
|
|
||||||
|
if (day < -1) {
|
||||||
|
throwOutOfRange({ actionType, field: 'day', value: day })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetCategoryForUnassignedAppsAction'
|
||||||
|
|
||||||
export class SetCategoryForUnassignedAppsAction extends ParentAction {
|
export class SetCategoryForUnassignedAppsAction extends ParentAction {
|
||||||
readonly childId: string
|
readonly childId: string
|
||||||
|
@ -28,10 +30,10 @@ export class SetCategoryForUnassignedAppsAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(childId)
|
assertIdWithinFamily({ actionType, field: 'childId', value: childId })
|
||||||
|
|
||||||
if (categoryId !== '') {
|
if (categoryId !== '') {
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.childId = childId
|
this.childId = childId
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertParentPasswordValid, ParentPassword } from '../api/schema'
|
import { assertParentPasswordValid, ParentPassword, ParentPasswordValidationException } from '../api/schema'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetChildPasswordAction'
|
||||||
|
|
||||||
export class SetChildPasswordAction extends ParentAction {
|
export class SetChildPasswordAction extends ParentAction {
|
||||||
readonly childUserId: string
|
readonly childUserId: string
|
||||||
|
@ -29,8 +32,18 @@ export class SetChildPasswordAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(childUserId)
|
assertIdWithinFamily({ actionType, field: 'childUserId', value: childUserId })
|
||||||
assertParentPasswordValid(newPassword)
|
|
||||||
|
try {
|
||||||
|
assertParentPasswordValid(newPassword)
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex instanceof ParentPasswordValidationException) {
|
||||||
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'invalid parent password'
|
||||||
|
})
|
||||||
|
} else throw ex
|
||||||
|
}
|
||||||
|
|
||||||
this.childUserId = childUserId
|
this.childUserId = childUserId
|
||||||
this.newPassword = newPassword
|
this.newPassword = newPassword
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetConsiderRebootManipulationAction'
|
||||||
|
|
||||||
export class SetConsiderRebootManipulationAction extends ParentAction {
|
export class SetConsiderRebootManipulationAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -25,7 +27,7 @@ export class SetConsiderRebootManipulationAction extends ParentAction {
|
||||||
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
|
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
this.enable = enable
|
this.enable = enable
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetDeviceDefaultUserAction'
|
||||||
|
|
||||||
export class SetDeviceDefaultUserAction extends ParentAction {
|
export class SetDeviceDefaultUserAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -28,10 +30,10 @@ export class SetDeviceDefaultUserAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
if (defaultUserId !== '') {
|
if (defaultUserId !== '') {
|
||||||
assertIdWithinFamily(defaultUserId)
|
assertIdWithinFamily({ actionType, field: 'defaultUserId', value: defaultUserId })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetDeviceDefaultUserTimeoutAction'
|
||||||
|
|
||||||
export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
|
export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -28,10 +31,15 @@ export class SetDeviceDefaultUserTimeoutAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
assertSafeInteger({ actionType, field: 'timeout', value: timeout })
|
||||||
|
|
||||||
if ((!Number.isInteger(timeout)) || (timeout < 0)) {
|
if (timeout < 0) {
|
||||||
throw new Error('timeout must be a non-negative integer')
|
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
|
this.deviceId = deviceId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetDeviceUserAction'
|
||||||
|
|
||||||
export class SetDeviceUserAction extends ParentAction {
|
export class SetDeviceUserAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -28,10 +30,10 @@ export class SetDeviceUserAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
if (userId !== '') {
|
if (userId !== '') {
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetKeepSignedInAction'
|
||||||
|
|
||||||
export class SetKeepSignedInAction extends ParentAction {
|
export class SetKeepSignedInAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -28,7 +30,7 @@ export class SetKeepSignedInAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
this.keepSignedIn = keepSignedIn
|
this.keepSignedIn = keepSignedIn
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetParentCategoryAction'
|
||||||
|
|
||||||
export class SetParentCategoryAction extends ParentAction {
|
export class SetParentCategoryAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -28,10 +30,10 @@ export class SetParentCategoryAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
if (parentCategory !== '') {
|
if (parentCategory !== '') {
|
||||||
assertIdWithinFamily(parentCategory)
|
assertIdWithinFamily({ actionType, field: 'parentCategory', value: parentCategory })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetRelaxPrimaryDeviceAction'
|
||||||
|
|
||||||
export class SetRelaxPrimaryDeviceAction extends ParentAction {
|
export class SetRelaxPrimaryDeviceAction extends ParentAction {
|
||||||
readonly userId: string
|
readonly userId: string
|
||||||
|
@ -28,7 +30,7 @@ export class SetRelaxPrimaryDeviceAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
this.relax = relax
|
this.relax = relax
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetSendDeviceConnected'
|
||||||
|
|
||||||
export class SetSendDeviceConnected extends ParentAction {
|
export class SetSendDeviceConnected extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -28,7 +30,7 @@ export class SetSendDeviceConnected extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
this.enable = enable
|
this.enable = enable
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetUserDisableLimitsUntilAction'
|
||||||
|
|
||||||
export class SetUserDisableLimitsUntilAction extends ParentAction {
|
export class SetUserDisableLimitsUntilAction extends ParentAction {
|
||||||
readonly childId: string
|
readonly childId: string
|
||||||
|
@ -28,10 +31,15 @@ export class SetUserDisableLimitsUntilAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(childId)
|
assertIdWithinFamily({ actionType, field: 'childId', value: childId })
|
||||||
|
assertSafeInteger({ actionType, field: 'timestamp', value: timestamp })
|
||||||
|
|
||||||
if (timestamp < 0 || (!Number.isSafeInteger(timestamp))) {
|
if (timestamp < 0) {
|
||||||
throw new Error('timestamp for set user disabe limits until must be >= 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
|
this.childId = childId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'SetUserTimezoneAction'
|
||||||
|
|
||||||
export class SetUserTimezoneAction extends ParentAction {
|
export class SetUserTimezoneAction extends ParentAction {
|
||||||
readonly userId: string
|
readonly userId: string
|
||||||
|
@ -28,7 +30,7 @@ export class SetUserTimezoneAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
this.timezone = timezone
|
this.timezone = timezone
|
||||||
|
|
|
@ -24,7 +24,7 @@ export class SignOutAtDeviceAction extends AppLogicAction {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse = (action: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance
|
static parse = (_: SerializedSignOutAtDeviceAction) => SignOutAtDeviceAction.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SerializedSignOutAtDeviceAction {
|
export interface SerializedSignOutAtDeviceAction {
|
||||||
|
|
|
@ -15,9 +15,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AppActivityItem, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem } from '../model/appactivity'
|
import {
|
||||||
import { assertListWithoutDuplicates } from '../util/list'
|
AppActivityItem, IncompleteAppActivityItemException, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem
|
||||||
|
} from '../model/appactivity'
|
||||||
import { AppLogicAction } from './basetypes'
|
import { AppLogicAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertListWithoutDuplicates } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateAppActivitiesAction'
|
||||||
|
|
||||||
export class UpdateAppActivitiesAction extends AppLogicAction {
|
export class UpdateAppActivitiesAction extends AppLogicAction {
|
||||||
readonly removed: Array<RemovedAppActivityItem>
|
readonly removed: Array<RemovedAppActivityItem>
|
||||||
|
@ -29,23 +34,44 @@ export class UpdateAppActivitiesAction extends AppLogicAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertListWithoutDuplicates(removed.map((item) => item.packageName + ':' + item.activityName))
|
assertListWithoutDuplicates({
|
||||||
assertListWithoutDuplicates(updatedOrAdded.map((item) => item.packageName + ':' + item.activityName))
|
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) {
|
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.removed = removed
|
||||||
this.updatedOrAdded = updatedOrAdded
|
this.updatedOrAdded = updatedOrAdded
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => (
|
static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => {
|
||||||
new UpdateAppActivitiesAction({
|
try {
|
||||||
removed: removed.map((item) => RemovedAppActivityItem.parse(item)),
|
return new UpdateAppActivitiesAction({
|
||||||
updatedOrAdded: updatedOrAdded.map((item) => AppActivityItem.parse(item))
|
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 {
|
export interface SerializedUpdateAppActivitiesAction {
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategoryBatteryLimitAction'
|
||||||
|
|
||||||
export class UpdateCategoryBatteryLimitAction extends ParentAction {
|
export class UpdateCategoryBatteryLimitAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -30,17 +32,21 @@ export class UpdateCategoryBatteryLimitAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
if (chargeLimit !== undefined) {
|
if (chargeLimit !== undefined) {
|
||||||
if ((!Number.isSafeInteger(chargeLimit)) || chargeLimit < 0 || chargeLimit > 100) {
|
assertSafeInteger({ actionType, field: 'chargeLimit', value: chargeLimit })
|
||||||
throw new Error('charge limit out of range')
|
|
||||||
|
if (chargeLimit < 0 || chargeLimit > 100) {
|
||||||
|
throwOutOfRange({ actionType, field: 'chargeLimit', value: chargeLimit })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mobileLimit !== undefined) {
|
if (mobileLimit !== undefined) {
|
||||||
if ((!Number.isSafeInteger(mobileLimit)) || mobileLimit < 0 || mobileLimit > 100) {
|
assertSafeInteger({ actionType, field: 'mobileLimit', value: mobileLimit })
|
||||||
throw new Error('mobile limit out of range')
|
|
||||||
|
if (mobileLimit < 0 || mobileLimit > 100) {
|
||||||
|
throwOutOfRange({ actionType, field: 'mobileLimit', value: mobileLimit })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategoryBlockAllNotificationsAction'
|
||||||
|
|
||||||
export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
|
export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -25,7 +27,7 @@ export class UpdateCategoryBlockAllNotificationsAction extends ParentAction {
|
||||||
constructor ({ categoryId, blocked }: {categoryId: string, blocked: boolean}) {
|
constructor ({ categoryId, blocked }: {categoryId: string, blocked: boolean}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.blocked = blocked
|
this.blocked = blocked
|
||||||
|
|
|
@ -15,12 +15,15 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { validateBitmask } from '../util/bitmask'
|
import { BitmapValidationException, validateBitmask } from '../util/bitmask'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
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 */
|
export const blockedTimesBitmaskLength = 60 * 24 * 7 /* number of minutes per week */
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategoryBlockedTimesAction'
|
||||||
|
|
||||||
export class UpdateCategoryBlockedTimesAction extends ParentAction {
|
export class UpdateCategoryBlockedTimesAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
readonly blockedTimes: string
|
readonly blockedTimes: string
|
||||||
|
@ -31,8 +34,18 @@ export class UpdateCategoryBlockedTimesAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
validateBitmask(blockedTimes, blockedTimesBitmaskLength)
|
|
||||||
|
try {
|
||||||
|
validateBitmask(blockedTimes, blockedTimesBitmaskLength)
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex instanceof BitmapValidationException) {
|
||||||
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'invalid bitmask'
|
||||||
|
})
|
||||||
|
} else throw ex
|
||||||
|
}
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.blockedTimes = blockedTimes
|
this.blockedTimes = blockedTimes
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { uniq } from 'lodash'
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertNonEmptyListWithoutDuplicates } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategorySortingAction'
|
||||||
|
|
||||||
export class UpdateCategorySortingAction extends ParentAction {
|
export class UpdateCategorySortingAction extends ParentAction {
|
||||||
readonly categoryIds: Array<string>
|
readonly categoryIds: Array<string>
|
||||||
|
@ -27,15 +28,13 @@ export class UpdateCategorySortingAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
if (categoryIds.length === 0) {
|
assertNonEmptyListWithoutDuplicates({ actionType, field: 'categoryIds', list: categoryIds })
|
||||||
throw new Error('empty category sorting list')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uniq(categoryIds).length !== categoryIds.length) {
|
categoryIds.forEach((categoryId) => assertIdWithinFamily({
|
||||||
throw new Error('category sorting list has duplicates')
|
actionType,
|
||||||
}
|
field: 'categoryIds',
|
||||||
|
value: categoryId
|
||||||
categoryIds.forEach((categoryId) => assertIdWithinFamily(categoryId))
|
}))
|
||||||
|
|
||||||
this.categoryIds = categoryIds
|
this.categoryIds = categoryIds
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategoryTemporarilyBlockedAction'
|
||||||
|
|
||||||
export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
|
export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -30,15 +33,16 @@ export class UpdateCategoryTemporarilyBlockedAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
if (endTime !== undefined) {
|
if (endTime !== undefined) {
|
||||||
if (!Number.isSafeInteger(endTime)) {
|
assertSafeInteger({ actionType, field: 'endTime', value: endTime })
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!blocked) {
|
if (!blocked) {
|
||||||
throw new Error()
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'can not set a end time when disabling blocking'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { allowedTimeWarningFlags } from '../database/category'
|
import { allowedTimeWarningFlags } from '../database/category'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategoryTimeWarningsAction'
|
||||||
|
|
||||||
export class UpdateCategoryTimeWarningsAction extends ParentAction {
|
export class UpdateCategoryTimeWarningsAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -31,10 +33,11 @@ export class UpdateCategoryTimeWarningsAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
assertSafeInteger({ actionType, field: 'flags', value: flags })
|
||||||
|
|
||||||
if ((flags & allowedTimeWarningFlags) !== flags) {
|
if ((flags & allowedTimeWarningFlags) !== flags) {
|
||||||
throw new Error('illegal flags')
|
throwOutOfRange({ actionType, field: 'flags', value: flags })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateCategoryTitleAction'
|
||||||
|
|
||||||
export class UpdateCategoryTitleAction extends ParentAction {
|
export class UpdateCategoryTitleAction extends ParentAction {
|
||||||
readonly categoryId: string
|
readonly categoryId: string
|
||||||
|
@ -25,7 +27,7 @@ export class UpdateCategoryTitleAction extends ParentAction {
|
||||||
constructor ({ categoryId, newTitle }: {categoryId: string, newTitle: string}) {
|
constructor ({ categoryId, newTitle }: {categoryId: string, newTitle: string}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
|
|
||||||
this.categoryId = categoryId
|
this.categoryId = categoryId
|
||||||
this.newTitle = newTitle
|
this.newTitle = newTitle
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateDeviceNameAction'
|
||||||
|
|
||||||
export class UpdateDeviceNameAction extends ParentAction {
|
export class UpdateDeviceNameAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -31,10 +34,13 @@ export class UpdateDeviceNameAction extends ParentAction {
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
this.name = name
|
this.name = name
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
if (name.trim().length === 0) {
|
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'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ import { NewPermissionStatus } from '../model/newpermissionstatus'
|
||||||
import { ProtectionLevel } from '../model/protectionlevel'
|
import { ProtectionLevel } from '../model/protectionlevel'
|
||||||
import { RuntimePermissionStatus } from '../model/runtimepermissionstatus'
|
import { RuntimePermissionStatus } from '../model/runtimepermissionstatus'
|
||||||
import { AppLogicAction } from './basetypes'
|
import { AppLogicAction } from './basetypes'
|
||||||
|
import { assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateDeviceStatusAction'
|
||||||
|
|
||||||
export class UpdateDeviceStatusAction extends AppLogicAction {
|
export class UpdateDeviceStatusAction extends AppLogicAction {
|
||||||
readonly newProtetionLevel?: ProtectionLevel
|
readonly newProtetionLevel?: ProtectionLevel
|
||||||
|
@ -52,8 +55,10 @@ export class UpdateDeviceStatusAction extends AppLogicAction {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
if (newAppVersion !== undefined) {
|
if (newAppVersion !== undefined) {
|
||||||
if (!Number.isSafeInteger(newAppVersion) || (newAppVersion < 0)) {
|
assertSafeInteger({ actionType, field: 'newAppVersion', value: newAppVersion })
|
||||||
throw new Error('invalid new ap version')
|
|
||||||
|
if (newAppVersion < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'newAppVersion', value: newAppVersion })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateEnableActivityLevelBlockingAction'
|
||||||
|
|
||||||
export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
|
export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -25,7 +27,7 @@ export class UpdateEnableActivityLevelBlockingAction extends ParentAction {
|
||||||
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
|
constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
this.enable = enable
|
this.enable = enable
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateNetworkTimeVerificationAction'
|
||||||
|
|
||||||
export class UpdateNetworkTimeVerificationAction extends ParentAction {
|
export class UpdateNetworkTimeVerificationAction extends ParentAction {
|
||||||
readonly deviceId: string
|
readonly deviceId: string
|
||||||
|
@ -28,7 +30,7 @@ export class UpdateNetworkTimeVerificationAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(deviceId)
|
assertIdWithinFamily({ actionType, field: 'deviceId', value: deviceId })
|
||||||
|
|
||||||
this.deviceId = deviceId
|
this.deviceId = deviceId
|
||||||
this.mode = mode
|
this.mode = mode
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { validateAndParseBitmask } from '../util/bitmask'
|
import { BitmapValidationException, validateAndParseBitmask } from '../util/bitmask'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateParentBlockedTimesAction'
|
||||||
|
|
||||||
export class UpdateParentBlockedTimesAction extends ParentAction {
|
export class UpdateParentBlockedTimesAction extends ParentAction {
|
||||||
readonly parentId: string
|
readonly parentId: string
|
||||||
|
@ -29,9 +32,9 @@ export class UpdateParentBlockedTimesAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(parentId)
|
assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
|
||||||
|
|
||||||
{
|
try {
|
||||||
const parsedBlockedTimes = validateAndParseBitmask(blockedTimes, 60 * 24 * 7 /* number of minutes per week */)
|
const parsedBlockedTimes = validateAndParseBitmask(blockedTimes, 60 * 24 * 7 /* number of minutes per week */)
|
||||||
|
|
||||||
for (let day = 0; day < 7; day++) {
|
for (let day = 0; day < 7; day++) {
|
||||||
|
@ -44,9 +47,19 @@ export class UpdateParentBlockedTimesAction extends ParentAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockedMinutes > 60 * 18 /* 18 hours */) {
|
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
|
this.parentId = parentId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateParentNotificationFlagsAction'
|
||||||
|
|
||||||
export class UpdateParentNotificationFlagsAction extends ParentAction {
|
export class UpdateParentNotificationFlagsAction extends ParentAction {
|
||||||
readonly parentId: string
|
readonly parentId: string
|
||||||
|
@ -30,14 +32,12 @@ export class UpdateParentNotificationFlagsAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(parentId)
|
assertIdWithinFamily({ actionType, field: 'parentId', value: parentId })
|
||||||
|
|
||||||
if (!Number.isSafeInteger(flags)) {
|
assertSafeInteger({ actionType, field: 'flags', value: flags })
|
||||||
throw new Error('flags must be an integer')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags < 0 || flags > 1) {
|
if (flags < 0 || flags > 1) {
|
||||||
throw new Error('flags are out of the valid range')
|
throwOutOfRange({ actionType, field: 'flags', value: flags })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.parentId = parentId
|
this.parentId = parentId
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MinuteOfDay } from '../util/minuteofday'
|
import { MinuteOfDay } from '../util/minuteofday'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger, throwOutOfRange } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateTimelimitRuleAction'
|
||||||
|
|
||||||
export class UpdateTimelimitRuleAction extends ParentAction {
|
export class UpdateTimelimitRuleAction extends ParentAction {
|
||||||
readonly ruleId: string
|
readonly ruleId: string
|
||||||
|
@ -53,35 +56,37 @@ export class UpdateTimelimitRuleAction extends ParentAction {
|
||||||
this.sessionDurationMilliseconds = sessionDurationMilliseconds
|
this.sessionDurationMilliseconds = sessionDurationMilliseconds
|
||||||
this.sessionPauseMilliseconds = sessionPauseMilliseconds
|
this.sessionPauseMilliseconds = sessionPauseMilliseconds
|
||||||
|
|
||||||
assertIdWithinFamily(ruleId)
|
assertIdWithinFamily({ actionType, field: 'ruleId', value: ruleId })
|
||||||
|
|
||||||
if (maximumTimeInMillis < 0 || (!Number.isSafeInteger(maximumTimeInMillis))) {
|
assertSafeInteger({ actionType, field: 'maximumTimeInMillis', value: maximumTimeInMillis })
|
||||||
throw new Error('maximumTimeInMillis must be >= 0')
|
|
||||||
|
if (maximumTimeInMillis < 0) {
|
||||||
|
throwOutOfRange({ actionType, field: 'maximumTimeInMillis', value: maximumTimeInMillis })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(
|
assertSafeInteger({ actionType, field: 'dayMask', value: dayMask })
|
||||||
Number.isSafeInteger(dayMask) ||
|
|
||||||
dayMask < 0 ||
|
if (dayMask < 0 || dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)) {
|
||||||
dayMask > (1 | 2 | 4 | 8 | 16 | 32 | 64)
|
throwOutOfRange({ actionType, field: 'dayMask', value: dayMask })
|
||||||
)) {
|
|
||||||
throw new Error('invalid day mask')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
assertSafeInteger({ actionType, field: 'start', value: start })
|
||||||
(!Number.isSafeInteger(start)) ||
|
assertSafeInteger({ actionType, field: 'end', value: end })
|
||||||
(!Number.isSafeInteger(end)) ||
|
assertSafeInteger({ actionType, field: 'sessionDurationMilliseconds', value: sessionDurationMilliseconds })
|
||||||
(!Number.isSafeInteger(sessionDurationMilliseconds)) ||
|
assertSafeInteger({ actionType, field: 'sessionPauseMilliseconds', value: sessionPauseMilliseconds })
|
||||||
(!Number.isSafeInteger(sessionPauseMilliseconds))
|
|
||||||
) {
|
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start < MinuteOfDay.MIN || end > MinuteOfDay.MAX || start > end) {
|
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) {
|
if (sessionDurationMilliseconds < 0 || sessionPauseMilliseconds < 0) {
|
||||||
throw new Error()
|
throw new InvalidActionParameterException({
|
||||||
|
actionType,
|
||||||
|
staticMessage: 'session duration lesser than zero'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UserFlags } from '../model/userflags'
|
import { UserFlags } from '../model/userflags'
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { InvalidActionParameterException } from './meta/exception'
|
||||||
|
import { assertIdWithinFamily, assertSafeInteger } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateUserFlagsAction'
|
||||||
|
|
||||||
export class UpdateUserFlagsAction extends ParentAction {
|
export class UpdateUserFlagsAction extends ParentAction {
|
||||||
readonly userId: string
|
readonly userId: string
|
||||||
|
@ -31,14 +34,16 @@ export class UpdateUserFlagsAction extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
|
assertSafeInteger({ actionType, field: 'modifiedBits', value: modifiedBits })
|
||||||
if ((!Number.isSafeInteger(modifiedBits)) || (!Number.isSafeInteger(newValues))) {
|
assertSafeInteger({ actionType, field: 'newValues', value: newValues })
|
||||||
throw new Error('flags must be an integer')
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((modifiedBits | UserFlags.ALL_FLAGS) !== UserFlags.ALL_FLAGS || (modifiedBits | newValues) !== modifiedBits) {
|
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
|
this.userId = userId
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertIdWithinFamily } from '../util/token'
|
|
||||||
import { ParentAction } from './basetypes'
|
import { ParentAction } from './basetypes'
|
||||||
|
import { assertIdWithinFamily } from './meta/util'
|
||||||
|
|
||||||
|
const actionType = 'UpdateUserLimitLoginCategory'
|
||||||
|
|
||||||
export class UpdateUserLimitLoginCategory extends ParentAction {
|
export class UpdateUserLimitLoginCategory extends ParentAction {
|
||||||
readonly userId: string
|
readonly userId: string
|
||||||
|
@ -28,10 +30,10 @@ export class UpdateUserLimitLoginCategory extends ParentAction {
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
assertIdWithinFamily(userId)
|
assertIdWithinFamily({ actionType, field: 'userId', value: userId })
|
||||||
|
|
||||||
if (categoryId !== undefined) {
|
if (categoryId !== undefined) {
|
||||||
assertIdWithinFamily(categoryId)
|
assertIdWithinFamily({ actionType, field: 'categoryId', value: categoryId })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
|
|
|
@ -52,14 +52,16 @@ export interface ParentPassword {
|
||||||
|
|
||||||
export const assertParentPasswordValid = (password: ParentPassword) => {
|
export const assertParentPasswordValid = (password: ParentPassword) => {
|
||||||
if (password.hash === '' || password.secondHash === '' || password.secondSalt === '') {
|
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))) {
|
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 {
|
export interface CreateFamilyByMailTokenRequest {
|
||||||
mailAuthToken: string
|
mailAuthToken: string
|
||||||
parentPassword: ParentPassword
|
parentPassword: ParentPassword
|
||||||
|
|
|
@ -85,7 +85,7 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
|
||||||
throw new BadRequest()
|
throw new BadRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
await database.transaction(async (transaction) => {
|
const serverStatus = await database.transaction(async (transaction) => {
|
||||||
const deviceEntryUnsafe = await database.device.findOne({
|
const deviceEntryUnsafe = await database.device.findOne({
|
||||||
where: {
|
where: {
|
||||||
deviceAuthToken: body.deviceAuthToken
|
deviceAuthToken: body.deviceAuthToken
|
||||||
|
@ -112,23 +112,23 @@ export const createSyncRouter = ({ database, websocket, connectedDevicesManager,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverStatus = await generateServerDataStatus({
|
return generateServerDataStatus({
|
||||||
database,
|
database,
|
||||||
familyId,
|
familyId,
|
||||||
clientStatus: body.status,
|
clientStatus: body.status,
|
||||||
transaction
|
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) {
|
} catch (ex) {
|
||||||
next(ex)
|
next(ex)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ function parseYesNo (value: string) {
|
||||||
} else if (value === 'no') {
|
} else if (value === 'no') {
|
||||||
return false
|
return false
|
||||||
} else {
|
} 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,
|
pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000,
|
||||||
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false
|
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ParseYesNoException extends Error {}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
|
import { ValidationException } from '../exception'
|
||||||
import { MinuteOfDay } from '../util/minuteofday'
|
import { MinuteOfDay } from '../util/minuteofday'
|
||||||
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
|
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
|
||||||
import { SequelizeAttributes } from './types'
|
import { SequelizeAttributes } from './types'
|
||||||
|
@ -85,11 +86,11 @@ export const attributesVersion1: SequelizeAttributes<SessionDurationAttributesVe
|
||||||
const startMinuteOfDay = this.startMinuteOfDay
|
const startMinuteOfDay = this.startMinuteOfDay
|
||||||
|
|
||||||
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
|
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 session duration' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startMinuteOfDay > endMinuteOfDay) {
|
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 session duration' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
|
import { ValidationException } from '../exception'
|
||||||
import { MinuteOfDay } from '../util/minuteofday'
|
import { MinuteOfDay } from '../util/minuteofday'
|
||||||
import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns'
|
import { booleanColumn, familyIdColumn, idWithinFamilyColumn } from './columns'
|
||||||
import { SequelizeAttributes } from './types'
|
import { SequelizeAttributes } from './types'
|
||||||
|
@ -90,11 +91,11 @@ export const attributesVersion2: SequelizeAttributes<TimelimitRuleAttributesVers
|
||||||
const startMinuteOfDay = this.startMinuteOfDay
|
const startMinuteOfDay = this.startMinuteOfDay
|
||||||
|
|
||||||
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
|
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 time limit rule' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startMinuteOfDay > endMinuteOfDay) {
|
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 time limit rule' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
|
import { ValidationException } from '../exception'
|
||||||
import { MinuteOfDay } from '../util/minuteofday'
|
import { MinuteOfDay } from '../util/minuteofday'
|
||||||
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
|
import { familyIdColumn, idWithinFamilyColumn, timestampColumn } from './columns'
|
||||||
import { SequelizeAttributes } from './types'
|
import { SequelizeAttributes } from './types'
|
||||||
|
@ -100,11 +101,11 @@ export const attributesVersion3: SequelizeAttributes<UsedTimeAttributesVersion3>
|
||||||
const startMinuteOfDay = this.startMinuteOfDay
|
const startMinuteOfDay = this.startMinuteOfDay
|
||||||
|
|
||||||
if (typeof endMinuteOfDay !== 'number' || typeof startMinuteOfDay !== 'number') {
|
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) {
|
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' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/exception/index.ts
Normal file
19
src/exception/index.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { StaticMessageException, IllegalStateException } from './static-message-exception'
|
||||||
|
export { ValidationException } from './validation'
|
30
src/exception/static-message-exception.ts
Normal file
30
src/exception/static-message-exception.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 {}
|
20
src/exception/validation.ts
Normal file
20
src/exception/validation.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StaticMessageException } from './static-message-exception'
|
||||||
|
|
||||||
|
export class ValidationException extends StaticMessageException {}
|
|
@ -15,7 +15,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Conflict, Unauthorized } from 'http-errors'
|
import { Conflict, InternalServerError, Unauthorized } from 'http-errors'
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { config } from '../../config'
|
import { config } from '../../config'
|
||||||
import { Database } from '../../database'
|
import { Database } from '../../database'
|
||||||
|
@ -147,7 +147,7 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
|
||||||
throw new Conflict()
|
throw new Conflict()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('illegal state')
|
throw new InternalServerError('illegal state')
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalidiate user list
|
// invalidiate user list
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Unauthorized } from 'http-errors'
|
||||||
import { Database } from '../../database'
|
import { Database } from '../../database'
|
||||||
import { generateAuthToken, generateVersionId } from '../../util/token'
|
import { generateAuthToken, generateVersionId } from '../../util/token'
|
||||||
import { WebsocketApi } from '../../websocket'
|
import { WebsocketApi } from '../../websocket'
|
||||||
|
@ -83,7 +84,7 @@ export async function reportDeviceRemoved ({ database, deviceAuthToken, websocke
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!oldDeviceEntry) {
|
if (!oldDeviceEntry) {
|
||||||
throw new Error('device not found')
|
throw new Unauthorized('device not found')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Database, Transaction } from '../../database'
|
import { Database, Transaction } from '../../database'
|
||||||
|
import { StaticMessageException } from '../../exception'
|
||||||
import { requireMailByAuthToken } from '../authentication'
|
import { requireMailByAuthToken } from '../authentication'
|
||||||
|
|
||||||
const getStatusByMailAddress = async ({
|
const getStatusByMailAddress = async ({
|
||||||
mail, database, transaction
|
mail, database, transaction
|
||||||
}: { mail: string, database: Database, transaction: Transaction }) => {
|
}: { mail: string, database: Database, transaction: Transaction }) => {
|
||||||
if (!mail) {
|
if (!mail) {
|
||||||
throw new Error('no mail address')
|
throw new StaticMessageException({ staticMessage: 'getStatusByMailAddress: no mail address provided' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = await database.user.findOne({
|
const entry = await database.user.findOne({
|
||||||
|
|
72
src/function/sync/apply-actions/baseinfo.ts
Normal file
72
src/function/sync/apply-actions/baseinfo.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<ApplyActionBaseInfo> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ import { config } from '../../../config'
|
||||||
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
|
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
|
||||||
import { Database } from '../../../database'
|
import { Database } from '../../../database'
|
||||||
import { generateVersionId } from '../../../util/token'
|
import { generateVersionId } from '../../../util/token'
|
||||||
|
import { SourceUserNotFoundException } from './exception/illegal-state'
|
||||||
|
import { InvalidChildActionIntegrityValue } from './exception/integrity'
|
||||||
|
|
||||||
export class Cache {
|
export class Cache {
|
||||||
readonly familyId: string
|
readonly familyId: string
|
||||||
|
@ -84,7 +86,7 @@ export class Cache {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!userEntryUnsafe) {
|
if (!userEntryUnsafe) {
|
||||||
throw new Error('user not found')
|
throw new SourceUserNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
return userEntryUnsafe.secondPasswordHash
|
return userEntryUnsafe.secondPasswordHash
|
||||||
|
@ -102,11 +104,11 @@ export class Cache {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!userEntryUnsafe) {
|
if (!userEntryUnsafe) {
|
||||||
throw new Error('user not found')
|
throw new SourceUserNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userEntryUnsafe.secondPasswordHash) {
|
if (!userEntryUnsafe.secondPasswordHash) {
|
||||||
throw new Error('user does not have a password')
|
throw new InvalidChildActionIntegrityValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
return userEntryUnsafe.secondPasswordHash
|
return userEntryUnsafe.secondPasswordHash
|
||||||
|
|
|
@ -19,6 +19,7 @@ import * as Sequelize from 'sequelize'
|
||||||
import { AddUsedTimeAction } from '../../../../action'
|
import { AddUsedTimeAction } from '../../../../action'
|
||||||
import { MinuteOfDay } from '../../../../util/minuteofday'
|
import { MinuteOfDay } from '../../../../util/minuteofday'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingCategoryException } from '../exception/missing-item'
|
||||||
|
|
||||||
export const getRoundedTimestamp = () => {
|
export const getRoundedTimestamp = () => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
@ -29,7 +30,7 @@ export const getRoundedTimestamp = () => {
|
||||||
const dayLengthInMinutes = MinuteOfDay.LENGTH
|
const dayLengthInMinutes = MinuteOfDay.LENGTH
|
||||||
const dayLengthInMs = dayLengthInMinutes * 1000 * 60
|
const dayLengthInMs = dayLengthInMinutes * 1000 * 60
|
||||||
|
|
||||||
export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
|
export async function dispatchAddUsedTime ({ action, cache }: {
|
||||||
deviceId: string
|
deviceId: string
|
||||||
action: AddUsedTimeAction
|
action: AddUsedTimeAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
|
@ -50,7 +51,7 @@ export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
|
||||||
})
|
})
|
||||||
// verify that the category exists
|
// verify that the category exists
|
||||||
if (!categoryEntryUnsafe) {
|
if (!categoryEntryUnsafe) {
|
||||||
throw new Error('invalid category id')
|
throw new MissingCategoryException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryEntry = {
|
const categoryEntry = {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { AddUsedTimeActionVersion2 } from '../../../../action'
|
||||||
import { EventHandler } from '../../../../monitoring/eventhandler'
|
import { EventHandler } from '../../../../monitoring/eventhandler'
|
||||||
import { MinuteOfDay } from '../../../../util/minuteofday'
|
import { MinuteOfDay } from '../../../../util/minuteofday'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||||
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
|
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
|
||||||
|
|
||||||
export const getRoundedTimestampForSessionDuration = () => {
|
export const getRoundedTimestampForSessionDuration = () => {
|
||||||
|
@ -44,7 +45,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!deviceEntryUnsafe) {
|
if (!deviceEntryUnsafe) {
|
||||||
throw new Error('source device not found')
|
throw new SourceDeviceNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceEntry = {
|
const deviceEntry = {
|
||||||
|
@ -56,9 +57,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
||||||
|
|
||||||
let addUsedTimeForADifferentUserThanTheCurrentUserOfTheDevice = false
|
let addUsedTimeForADifferentUserThanTheCurrentUserOfTheDevice = false
|
||||||
|
|
||||||
for (let i = 0; i < action.items.length; i++) {
|
for (const item of action.items) {
|
||||||
const item = action.items[i]
|
|
||||||
|
|
||||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||||
where: {
|
where: {
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
|
@ -73,9 +72,10 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
||||||
|
|
||||||
// verify that the category exists
|
// verify that the category exists
|
||||||
if (!categoryEntryUnsafe) {
|
if (!categoryEntryUnsafe) {
|
||||||
|
eventHandler.countEvent('add used time category to add time for not found')
|
||||||
cache.requireFullSync()
|
cache.requireFullSync()
|
||||||
|
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryEntry = {
|
const categoryEntry = {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
} from '../../../../action'
|
} from '../../../../action'
|
||||||
import { EventHandler } from '../../../../monitoring/eventhandler'
|
import { EventHandler } from '../../../../monitoring/eventhandler'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
|
||||||
import { dispatchAddInstalledApps } from './addinstalledapps'
|
import { dispatchAddInstalledApps } from './addinstalledapps'
|
||||||
import { dispatchAddUsedTime } from './addusedtime'
|
import { dispatchAddUsedTime } from './addusedtime'
|
||||||
import { dispatchAddUsedTimeVersion2 } from './addusedtime2'
|
import { dispatchAddUsedTimeVersion2 } from './addusedtime2'
|
||||||
|
@ -64,6 +65,6 @@ export const dispatchAppLogicAction = async ({ action, deviceId, cache, eventHan
|
||||||
} else if (action instanceof TriedDisablingDeviceAdminAction) {
|
} else if (action instanceof TriedDisablingDeviceAdminAction) {
|
||||||
await dispatchTriedDisablingDeviceAdmin({ deviceId, action, cache })
|
await dispatchTriedDisablingDeviceAdmin({ deviceId, action, cache })
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unsupported action type')
|
throw new ActionObjectTypeNotHandledException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -18,14 +18,16 @@
|
||||||
import { SetDeviceUserAction, SignOutAtDeviceAction } from '../../../../action'
|
import { SetDeviceUserAction, SignOutAtDeviceAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
import { dispatchSetDeviceUser } from '../dispatch-parent-action/setdeviceuser'
|
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
|
deviceId: string
|
||||||
action: SignOutAtDeviceAction
|
action: SignOutAtDeviceAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
}) {
|
}) {
|
||||||
if (!cache.hasFullVersion) {
|
if (!cache.hasFullVersion) {
|
||||||
throw new Error('action requires full version')
|
throw new PremiumVersionMissingException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceEntry = await cache.database.device.findOne({
|
const deviceEntry = await cache.database.device.findOne({
|
||||||
|
@ -37,11 +39,13 @@ export async function dispatchSignOutAtDevice ({ deviceId, action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!deviceEntry) {
|
if (!deviceEntry) {
|
||||||
throw new Error('illegal state: missing device which dispatched the action')
|
throw new SourceDeviceNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceEntry.defaultUserId === '') {
|
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) {
|
if (deviceEntry.currentUserId !== deviceEntry.defaultUserId) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* 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 { hasDeviceManipulation } from '../../../../database/device'
|
||||||
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
|
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
|
||||||
import { Cache } from '../cache'
|
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
|
deviceId: string
|
||||||
action: TriedDisablingDeviceAdminAction
|
action: TriedDisablingDeviceAdminAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
|
@ -34,7 +35,7 @@ export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, action, cac
|
||||||
})
|
})
|
||||||
|
|
||||||
if (deviceEntry === null) {
|
if (deviceEntry === null) {
|
||||||
throw new Error('illegal state: missing device which dispatched the action')
|
throw new SourceDeviceNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
|
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* 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) {
|
if (action.updatedOrAdded.length > 0) {
|
||||||
const chuncks = chunk(action.updatedOrAdded, 500)
|
const chuncks = chunk(action.updatedOrAdded, 500)
|
||||||
|
|
||||||
for (let i = 0; i < chuncks.length; i++) {
|
for (const items of chuncks) {
|
||||||
const items = chuncks[i]
|
|
||||||
|
|
||||||
await cache.database.appActivity.destroy({
|
await cache.database.appActivity.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
|
@ -62,9 +60,7 @@ export async function dispatchUpdateAppActivities ({ deviceId, action, cache }:
|
||||||
if (action.removed.length > 0) {
|
if (action.removed.length > 0) {
|
||||||
const chunks = chunk(action.removed, 500)
|
const chunks = chunk(action.removed, 500)
|
||||||
|
|
||||||
for (let i = 0; i < chunks.length; i++) {
|
for (const items of chunks) {
|
||||||
const items = chunks[i]
|
|
||||||
|
|
||||||
await cache.database.appActivity.destroy({
|
await cache.database.appActivity.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* 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 { enumMax } from '../../../../util/enum'
|
||||||
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
|
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||||
|
|
||||||
export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
|
export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
|
||||||
deviceId: string
|
deviceId: string
|
||||||
|
@ -38,7 +39,7 @@ export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!deviceEntry) {
|
if (!deviceEntry) {
|
||||||
throw new Error('device not found')
|
throw new SourceDeviceNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
|
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { ChildChangePasswordAction } from '../../../../action'
|
import { ChildChangePasswordAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { SourceUserNotFoundException } from '../exception/illegal-state'
|
||||||
|
|
||||||
export const dispatchChildChangePassword = async ({ action, childUserId, cache }: {
|
export const dispatchChildChangePassword = async ({ action, childUserId, cache }: {
|
||||||
action: ChildChangePasswordAction
|
action: ChildChangePasswordAction
|
||||||
|
@ -35,7 +36,7 @@ export const dispatchChildChangePassword = async ({ action, childUserId, cache }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!childEntry) {
|
if (!childEntry) {
|
||||||
throw new Error('child entry not found')
|
throw new SourceUserNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
childEntry.passwordHash = action.password.hash
|
childEntry.passwordHash = action.password.hash
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -18,15 +18,17 @@
|
||||||
import { ChildSignInAction, SetDeviceUserAction } from '../../../../action'
|
import { ChildSignInAction, SetDeviceUserAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
import { dispatchSetDeviceUser } from '../dispatch-parent-action/setdeviceuser'
|
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
|
action: ChildSignInAction
|
||||||
deviceId: string
|
deviceId: string
|
||||||
childUserId: string
|
childUserId: string
|
||||||
cache: Cache
|
cache: Cache
|
||||||
}) => {
|
}) => {
|
||||||
if (!cache.hasFullVersion) {
|
if (!cache.hasFullVersion) {
|
||||||
throw new Error('action requires full version')
|
throw new PremiumVersionMissingException()
|
||||||
}
|
}
|
||||||
|
|
||||||
await dispatchSetDeviceUser({
|
await dispatchSetDeviceUser({
|
||||||
|
@ -50,7 +52,7 @@ export const dispatchChildSignIn = async ({ action, deviceId, childUserId, cache
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!userEntryUnsafe) {
|
if (!userEntryUnsafe) {
|
||||||
throw new Error('illegal state')
|
throw new SourceUserNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userEntryUnsafe.currentDevice === deviceId) {
|
if (userEntryUnsafe.currentDevice === deviceId) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -21,6 +21,7 @@ import {
|
||||||
ChildSignInAction
|
ChildSignInAction
|
||||||
} from '../../../../action'
|
} from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
|
||||||
import { dispatchChildChangePassword } from './childchangepassword'
|
import { dispatchChildChangePassword } from './childchangepassword'
|
||||||
import { dispatchChildSignIn } from './childsignin'
|
import { dispatchChildSignIn } from './childsignin'
|
||||||
|
|
||||||
|
@ -35,6 +36,6 @@ export const dispatchChildAction = async ({ action, deviceId, childUserId, cache
|
||||||
} else if (action instanceof ChildSignInAction) {
|
} else if (action instanceof ChildSignInAction) {
|
||||||
await dispatchChildSignIn({ action, childUserId, deviceId, cache })
|
await dispatchChildSignIn({ action, childUserId, deviceId, cache })
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unsupported action type')
|
throw new ActionObjectTypeNotHandledException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
54
src/function/sync/apply-actions/dispatch-helper/helper.ts
Normal file
54
src/function/sync/apply-actions/dispatch-helper/helper.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<T1 extends { type: string }, T2> ({ 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<void>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
20
src/function/sync/apply-actions/dispatch-helper/index.ts
Normal file
20
src/function/sync/apply-actions/dispatch-helper/index.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { dispatchAppLogicAction } from './app-logic-action'
|
||||||
|
export { dispatchChildAction } from './child-action'
|
||||||
|
export { dispatchParentAction } from './parent-action'
|
106
src/function/sync/apply-actions/dispatch-helper/parent-action.ts
Normal file
106
src/function/sync/apply-actions/dispatch-helper/parent-action.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -18,8 +18,11 @@
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { AddCategoryAppsAction } from '../../../../action'
|
import { AddCategoryAppsAction } from '../../../../action'
|
||||||
import { CategoryAppAttributes } from '../../../../database/categoryapp'
|
import { CategoryAppAttributes } from '../../../../database/categoryapp'
|
||||||
import { getCategoryWithParentCategories } from '../../../../util/category'
|
import { getCategoryWithParentCategories, GetParentCategoriesException } from '../../../../util/category'
|
||||||
import { Cache } from '../cache'
|
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 }: {
|
export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: AddCategoryAppsAction
|
action: AddCategoryAppsAction
|
||||||
|
@ -36,14 +39,14 @@ export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLim
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!categoryEntryUnsafe) {
|
if (!categoryEntryUnsafe) {
|
||||||
throw new Error('invalid category id')
|
throw new MissingCategoryException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { childId } = categoryEntryUnsafe
|
const { childId } = categoryEntryUnsafe
|
||||||
|
|
||||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||||
if (childId !== fromChildSelfLimitAddChildUserId) {
|
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)
|
}).map((item) => item.categoryId)
|
||||||
|
|
||||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||||
const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
|
try {
|
||||||
const userEntryUnsafe = await cache.database.user.findOne({
|
const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
|
||||||
attributes: [ 'categoryForNotAssignedApps' ],
|
const userEntryUnsafe = await cache.database.user.findOne({
|
||||||
where: {
|
attributes: [ 'categoryForNotAssignedApps' ],
|
||||||
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' ],
|
|
||||||
where: {
|
where: {
|
||||||
familyId: cache.familyId,
|
familyId: cache.familyId,
|
||||||
categoryId: {
|
userId: fromChildSelfLimitAddChildUserId
|
||||||
[Sequelize.Op.in]: userCategoryIds
|
|
||||||
},
|
|
||||||
packageName: packageName
|
|
||||||
},
|
},
|
||||||
transaction: cache.transaction
|
transaction: cache.transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
|
if (!userEntryUnsafe) {
|
||||||
|
throw new SourceUserNotFoundException()
|
||||||
|
}
|
||||||
|
|
||||||
if (categoryAppEntry === null) {
|
const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
|
||||||
if ((isApp && allowUnassignedElements) || (!isApp)) {
|
const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
|
||||||
// allow
|
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 {
|
} else {
|
||||||
throw new Error('can not assign apps without category as child')
|
if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
|
||||||
}
|
// allow
|
||||||
} else {
|
} else {
|
||||||
if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
|
throw new SelfLimitationException({
|
||||||
// allow
|
staticMessage: 'can not add app which is not contained in the parent category as child'
|
||||||
} else {
|
})
|
||||||
throw new Error('can not add app which is not contained in the parent category')
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < action.packageNames.length; i++) {
|
for (let i = 0; i < action.packageNames.length; i++) {
|
||||||
const packageName = action.packageNames[i]
|
const packageName = action.packageNames[i]
|
||||||
|
|
||||||
if (packageName.indexOf(':') !== -1) {
|
if (packageName.indexOf(':') !== -1) {
|
||||||
await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
|
await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
|
||||||
await assertCanAddApp(packageName, false)
|
await assertCanAddApp(packageName, false)
|
||||||
} else {
|
} else {
|
||||||
await assertCanAddApp(packageName, true)
|
await assertCanAddApp(packageName, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
if (ex instanceof GetParentCategoriesException) {
|
||||||
|
throw new MissingCategoryException()
|
||||||
|
} else throw ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
import { AddCategoryNetworkIdAction } from '../../../../action'
|
import { AddCategoryNetworkIdAction } from '../../../../action'
|
||||||
import { maxNetworkIdsPerCategory } from '../../../../database/categorynetworkid'
|
import { maxNetworkIdsPerCategory } from '../../../../database/categorynetworkid'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { ApplyActionException } from '../exception/index'
|
||||||
|
import { MissingCategoryException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||||
action: AddCategoryNetworkIdAction
|
action: AddCategoryNetworkIdAction
|
||||||
|
@ -33,7 +35,7 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!categoryEntryUnsafe) {
|
if (!categoryEntryUnsafe) {
|
||||||
throw new Error('invalid category id for new rule')
|
throw new MissingCategoryException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = await cache.database.categoryNetworkId.count({
|
const count = await cache.database.categoryNetworkId.count({
|
||||||
|
@ -45,7 +47,9 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (count + 1 > maxNetworkIdsPerCategory) {
|
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({
|
const hasOldItem = (await cache.database.categoryNetworkId.count({
|
||||||
|
@ -58,7 +62,9 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||||
})) !== 0
|
})) !== 0
|
||||||
|
|
||||||
if (hasOldItem) {
|
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({
|
await cache.database.categoryNetworkId.create({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -16,8 +16,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { ChangeParentPasswordAction } from '../../../../action'
|
import { ChangeParentPasswordAction, InvalidChangeParentPasswordIntegrityException } from '../../../../action/changeparentpassword'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { ApplyActionException } from '../exception/index'
|
||||||
|
import { MissingUserException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchChangeParentPassword ({ action, cache }: {
|
export async function dispatchChangeParentPassword ({ action, cache }: {
|
||||||
action: ChangeParentPasswordAction
|
action: ChangeParentPasswordAction
|
||||||
|
@ -34,10 +36,17 @@ export async function dispatchChangeParentPassword ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!parentEntry) {
|
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 })
|
const newSecondPasswordHash = action.decryptSecondHash({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
|
||||||
|
|
||||||
parentEntry.passwordHash = action.newPasswordFirstHash
|
parentEntry.passwordHash = action.newPasswordFirstHash
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
import { CreateCategoryAction } from '../../../../action'
|
import { CreateCategoryAction } from '../../../../action'
|
||||||
import { generateVersionId } from '../../../../util/token'
|
import { generateVersionId } from '../../../../util/token'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingUserException } from '../exception/missing-item'
|
||||||
|
import { CanNotModifyOtherUsersBySelfLimitationException } from '../exception/self-limit'
|
||||||
|
|
||||||
export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: CreateCategoryAction
|
action: CreateCategoryAction
|
||||||
|
@ -26,7 +28,7 @@ export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimi
|
||||||
}) {
|
}) {
|
||||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||||
if (fromChildSelfLimitAddChildUserId !== action.childId) {
|
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) {
|
if (!childEntry) {
|
||||||
throw new Error('missing child for new category')
|
throw new MissingUserException()
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldMaxSort: number = await cache.database.category.max('sort', {
|
const oldMaxSort: number = await cache.database.category.max('sort', {
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
import { CreateTimeLimitRuleAction } from '../../../../action'
|
import { CreateTimeLimitRuleAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingCategoryException } from '../exception/missing-item'
|
||||||
|
import { CanNotModifyOtherUsersBySelfLimitationException } from '../exception/self-limit'
|
||||||
|
|
||||||
export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||||
action: CreateTimeLimitRuleAction
|
action: CreateTimeLimitRuleAction
|
||||||
|
@ -33,12 +35,12 @@ export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSel
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!categoryEntryUnsafe) {
|
if (!categoryEntryUnsafe) {
|
||||||
throw new Error('invalid category id for new rule')
|
throw new MissingCategoryException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||||
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
|
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
|
||||||
throw new Error('can not add rules for other users')
|
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -17,15 +17,25 @@
|
||||||
|
|
||||||
import { DeleteCategoryAction } from '../../../../action'
|
import { DeleteCategoryAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingCategoryException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchDeleteCategory ({ action, cache }: {
|
export async function dispatchDeleteCategory ({ action, cache }: {
|
||||||
action: DeleteCategoryAction
|
action: DeleteCategoryAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
}) {
|
}) {
|
||||||
// no version number needs to be updated
|
|
||||||
const { familyId, transaction } = cache
|
const { familyId, transaction } = cache
|
||||||
const { categoryId } = action
|
const { categoryId } = action
|
||||||
|
|
||||||
|
const categoryEntry = await cache.database.category.findOne({
|
||||||
|
where: {
|
||||||
|
familyId,
|
||||||
|
categoryId
|
||||||
|
},
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!categoryEntry) { throw new MissingCategoryException() }
|
||||||
|
|
||||||
await cache.database.timelimitRule.destroy({
|
await cache.database.timelimitRule.destroy({
|
||||||
where: {
|
where: {
|
||||||
familyId,
|
familyId,
|
||||||
|
@ -75,4 +85,6 @@ export async function dispatchDeleteCategory ({ action, cache }: {
|
||||||
if (affectedUserRows !== 0) {
|
if (affectedUserRows !== 0) {
|
||||||
cache.invalidiateUserList = true
|
cache.invalidiateUserList = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no version number needs to be updated
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import { DeleteTimeLimitRuleAction } from '../../../../action'
|
import { DeleteTimeLimitRuleAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingRuleException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
|
export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
|
||||||
action: DeleteTimeLimitRuleAction
|
action: DeleteTimeLimitRuleAction
|
||||||
|
@ -30,10 +31,12 @@ export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
|
||||||
transaction: cache.transaction
|
transaction: cache.transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
if (ruleEntry) {
|
if (!ruleEntry) {
|
||||||
await ruleEntry.destroy({ transaction: cache.transaction })
|
throw new MissingRuleException()
|
||||||
|
|
||||||
cache.categoriesWithModifiedTimeLimitRules.push(ruleEntry.categoryId)
|
|
||||||
cache.areChangesImportant = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ruleEntry.destroy({ transaction: cache.transaction })
|
||||||
|
|
||||||
|
cache.categoriesWithModifiedTimeLimitRules.push(ruleEntry.categoryId)
|
||||||
|
cache.areChangesImportant = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import { IgnoreManipulationAction } from '../../../../action'
|
import { IgnoreManipulationAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||||
|
|
||||||
export async function dispatchIgnoreManipulation ({ action, cache }: {
|
export async function dispatchIgnoreManipulation ({ action, cache }: {
|
||||||
action: IgnoreManipulationAction
|
action: IgnoreManipulationAction
|
||||||
|
@ -31,7 +32,7 @@ export async function dispatchIgnoreManipulation ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (deviceEntry === null) {
|
if (deviceEntry === null) {
|
||||||
throw new Error('illegal state: missing device which dispatched the action')
|
throw new SourceDeviceNotFoundException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.ignoreDeviceAdminManipulation) {
|
if (action.ignoreDeviceAdminManipulation) {
|
||||||
|
|
|
@ -18,13 +18,15 @@
|
||||||
import { IncrementCategoryExtraTimeAction } from '../../../../action'
|
import { IncrementCategoryExtraTimeAction } from '../../../../action'
|
||||||
import { CategoryModel } from '../../../../database/category'
|
import { CategoryModel } from '../../../../database/category'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingCategoryException } from '../exception/missing-item'
|
||||||
|
import { PremiumVersionMissingException } from '../exception/premium'
|
||||||
|
|
||||||
export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
|
export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
|
||||||
action: IncrementCategoryExtraTimeAction
|
action: IncrementCategoryExtraTimeAction
|
||||||
cache: Cache
|
cache: Cache
|
||||||
}) {
|
}) {
|
||||||
if (!cache.hasFullVersion) {
|
if (!cache.hasFullVersion) {
|
||||||
throw new Error('action requires full version')
|
throw new PremiumVersionMissingException()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCategory (category: CategoryModel) {
|
async function handleCategory (category: CategoryModel) {
|
||||||
|
@ -51,7 +53,7 @@ export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!categoryEntry) {
|
if (!categoryEntry) {
|
||||||
throw new Error(`tried to add extra time to ${action.categoryId} but it does not exist`)
|
throw new MissingCategoryException()
|
||||||
}
|
}
|
||||||
|
|
||||||
await handleCategory(categoryEntry)
|
await handleCategory(categoryEntry)
|
||||||
|
|
|
@ -62,6 +62,8 @@ import {
|
||||||
UpdateUserLimitLoginCategory
|
UpdateUserLimitLoginCategory
|
||||||
} from '../../../../action'
|
} from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
|
||||||
|
import { ActionNotSupportedBySelfLimitationException } from '../exception/self-limit'
|
||||||
import { dispatchAddCategoryApps } from './addcategoryapps'
|
import { dispatchAddCategoryApps } from './addcategoryapps'
|
||||||
import { dispatchAddCategoryNetworkId } from './addcategorynetworkid'
|
import { dispatchAddCategoryNetworkId } from './addcategorynetworkid'
|
||||||
import { dispatchAddUser } from './adduser'
|
import { dispatchAddUser } from './adduser'
|
||||||
|
@ -129,7 +131,9 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
|
||||||
return dispatchUpdateCategoryBlockedTimes({ action, cache, fromChildSelfLimitAddChildUserId })
|
return dispatchUpdateCategoryBlockedTimes({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromChildSelfLimitAddChildUserId === null) {
|
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||||
|
throw new ActionNotSupportedBySelfLimitationException()
|
||||||
|
} else {
|
||||||
if (action instanceof AddCategoryNetworkIdAction) {
|
if (action instanceof AddCategoryNetworkIdAction) {
|
||||||
return dispatchAddCategoryNetworkId({ action, cache })
|
return dispatchAddCategoryNetworkId({ action, cache })
|
||||||
} else if (action instanceof AddUserAction) {
|
} else if (action instanceof AddUserAction) {
|
||||||
|
@ -202,8 +206,8 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
|
||||||
return dispatchUpdateUserFlagsAction({ action, cache })
|
return dispatchUpdateUserFlagsAction({ action, cache })
|
||||||
} else if (action instanceof UpdateUserLimitLoginCategory) {
|
} else if (action instanceof UpdateUserLimitLoginCategory) {
|
||||||
return dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
|
return dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
|
||||||
|
} else {
|
||||||
|
throw new ActionObjectTypeNotHandledException()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw new Error('unsupported action type')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { RemoveCategoryAppsAction } from '../../../../action'
|
import { RemoveCategoryAppsAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingItemException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchRemoveCategoryApps ({ action, cache }: {
|
export async function dispatchRemoveCategoryApps ({ action, cache }: {
|
||||||
action: RemoveCategoryAppsAction
|
action: RemoveCategoryAppsAction
|
||||||
|
@ -35,7 +36,9 @@ export async function dispatchRemoveCategoryApps ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (affectedRows !== action.packageNames.length) {
|
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)
|
cache.categoriesWithModifiedApps.push(action.categoryId)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* 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 * as Sequelize from 'sequelize'
|
||||||
import { RemoveUserAction } from '../../../../action'
|
import { RemoveUserAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
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 }: {
|
export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||||
action: RemoveUserAction
|
action: RemoveUserAction
|
||||||
|
@ -36,7 +39,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('invalid user id')
|
throw new MissingUserException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.type === 'parent') {
|
if (user.type === 'parent') {
|
||||||
|
@ -45,7 +48,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentUserId === action.userId) {
|
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(
|
const expectedIntegrityValue = createHash('sha512').update(
|
||||||
|
@ -53,7 +56,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||||
).digest('hex').substring(0, 16)
|
).digest('hex').substring(0, 16)
|
||||||
|
|
||||||
if (expectedIntegrityValue !== action.authentication) {
|
if (expectedIntegrityValue !== action.authentication) {
|
||||||
throw new Error('invalid authentication value')
|
throw new ApplyActionIntegrityException({ staticMessage: 'invalid authentication value for removing a user' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.mail !== '') {
|
if (user.mail !== '') {
|
||||||
|
@ -69,7 +72,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (usersWithLinkedMail <= 1) {
|
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)
|
const allOtherParentUserIds = allParentUserIds.filter((item) => item !== action.userId)
|
||||||
|
|
||||||
if (difference(allOtherParentUserIds, usersWithLimitLoginCategories).length === 0) {
|
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' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -17,12 +17,26 @@
|
||||||
|
|
||||||
import { RenameChildAction } from '../../../../action'
|
import { RenameChildAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingUserException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchRenameChild ({ action, cache }: {
|
export async function dispatchRenameChild ({ action, cache }: {
|
||||||
action: RenameChildAction
|
action: RenameChildAction
|
||||||
cache: Cache
|
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
|
name: action.newName
|
||||||
}, {
|
}, {
|
||||||
where: {
|
where: {
|
||||||
|
@ -33,10 +47,6 @@ export async function dispatchRenameChild ({ action, cache }: {
|
||||||
transaction: cache.transaction
|
transaction: cache.transaction
|
||||||
})
|
})
|
||||||
|
|
||||||
if (affectedRows !== 1) {
|
|
||||||
throw new Error('can not update child name if child does not exist')
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.invalidiateUserList = true
|
cache.invalidiateUserList = true
|
||||||
cache.doesUserExist.cache.set(action.childId, false)
|
cache.doesUserExist.cache.set(action.childId, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import { ResetCategoryNetworkIdsAction } from '../../../../action'
|
import { ResetCategoryNetworkIdsAction } from '../../../../action'
|
||||||
import { Cache } from '../cache'
|
import { Cache } from '../cache'
|
||||||
|
import { MissingCategoryException } from '../exception/missing-item'
|
||||||
|
|
||||||
export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
|
export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
|
||||||
action: ResetCategoryNetworkIdsAction
|
action: ResetCategoryNetworkIdsAction
|
||||||
|
@ -32,7 +33,7 @@ export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!categoryEntryUnsafe) {
|
if (!categoryEntryUnsafe) {
|
||||||
throw new Error('invalid category id for new rule')
|
throw new MissingCategoryException()
|
||||||
}
|
}
|
||||||
|
|
||||||
await cache.database.categoryNetworkId.destroy({
|
await cache.database.categoryNetworkId.destroy({
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue