mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
Refactor exception usage
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
* 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 { config } from '../../config'
|
||||
import { Database } from '../../database'
|
||||
@@ -147,7 +147,7 @@ export const setPrimaryDevice = async ({ database, websocket, deviceAuthToken, c
|
||||
throw new Conflict()
|
||||
}
|
||||
} else {
|
||||
throw new Error('illegal state')
|
||||
throw new InternalServerError('illegal state')
|
||||
}
|
||||
|
||||
// invalidiate user list
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Unauthorized } from 'http-errors'
|
||||
import { Database } from '../../database'
|
||||
import { generateAuthToken, generateVersionId } from '../../util/token'
|
||||
import { WebsocketApi } from '../../websocket'
|
||||
@@ -83,7 +84,7 @@ export async function reportDeviceRemoved ({ database, deviceAuthToken, websocke
|
||||
})
|
||||
|
||||
if (!oldDeviceEntry) {
|
||||
throw new Error('device not found')
|
||||
throw new Unauthorized('device not found')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
*/
|
||||
|
||||
import { Database, Transaction } from '../../database'
|
||||
import { StaticMessageException } from '../../exception'
|
||||
import { requireMailByAuthToken } from '../authentication'
|
||||
|
||||
const getStatusByMailAddress = async ({
|
||||
mail, database, transaction
|
||||
}: { mail: string, database: Database, transaction: Transaction }) => {
|
||||
if (!mail) {
|
||||
throw new Error('no mail address')
|
||||
throw new StaticMessageException({ staticMessage: 'getStatusByMailAddress: no mail address provided' })
|
||||
}
|
||||
|
||||
const entry = await database.user.findOne({
|
||||
|
||||
@@ -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 { Database } from '../../../database'
|
||||
import { generateVersionId } from '../../../util/token'
|
||||
import { SourceUserNotFoundException } from './exception/illegal-state'
|
||||
import { InvalidChildActionIntegrityValue } from './exception/integrity'
|
||||
|
||||
export class Cache {
|
||||
readonly familyId: string
|
||||
@@ -84,7 +86,7 @@ export class Cache {
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Error('user not found')
|
||||
throw new SourceUserNotFoundException()
|
||||
}
|
||||
|
||||
return userEntryUnsafe.secondPasswordHash
|
||||
@@ -102,11 +104,11 @@ export class Cache {
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Error('user not found')
|
||||
throw new SourceUserNotFoundException()
|
||||
}
|
||||
|
||||
if (!userEntryUnsafe.secondPasswordHash) {
|
||||
throw new Error('user does not have a password')
|
||||
throw new InvalidChildActionIntegrityValue()
|
||||
}
|
||||
|
||||
return userEntryUnsafe.secondPasswordHash
|
||||
|
||||
@@ -19,6 +19,7 @@ import * as Sequelize from 'sequelize'
|
||||
import { AddUsedTimeAction } from '../../../../action'
|
||||
import { MinuteOfDay } from '../../../../util/minuteofday'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export const getRoundedTimestamp = () => {
|
||||
const now = Date.now()
|
||||
@@ -29,7 +30,7 @@ export const getRoundedTimestamp = () => {
|
||||
const dayLengthInMinutes = MinuteOfDay.LENGTH
|
||||
const dayLengthInMs = dayLengthInMinutes * 1000 * 60
|
||||
|
||||
export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
|
||||
export async function dispatchAddUsedTime ({ action, cache }: {
|
||||
deviceId: string
|
||||
action: AddUsedTimeAction
|
||||
cache: Cache
|
||||
@@ -50,7 +51,7 @@ export async function dispatchAddUsedTime ({ deviceId, action, cache }: {
|
||||
})
|
||||
// verify that the category exists
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const categoryEntry = {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { AddUsedTimeActionVersion2 } from '../../../../action'
|
||||
import { EventHandler } from '../../../../monitoring/eventhandler'
|
||||
import { MinuteOfDay } from '../../../../util/minuteofday'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||
import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime'
|
||||
|
||||
export const getRoundedTimestampForSessionDuration = () => {
|
||||
@@ -44,7 +45,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe) {
|
||||
throw new Error('source device not found')
|
||||
throw new SourceDeviceNotFoundException()
|
||||
}
|
||||
|
||||
const deviceEntry = {
|
||||
@@ -56,9 +57,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
||||
|
||||
let addUsedTimeForADifferentUserThanTheCurrentUserOfTheDevice = false
|
||||
|
||||
for (let i = 0; i < action.items.length; i++) {
|
||||
const item = action.items[i]
|
||||
|
||||
for (const item of action.items) {
|
||||
const categoryEntryUnsafe = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
@@ -73,9 +72,10 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev
|
||||
|
||||
// verify that the category exists
|
||||
if (!categoryEntryUnsafe) {
|
||||
eventHandler.countEvent('add used time category to add time for not found')
|
||||
cache.requireFullSync()
|
||||
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
const categoryEntry = {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '../../../../action'
|
||||
import { EventHandler } from '../../../../monitoring/eventhandler'
|
||||
import { Cache } from '../cache'
|
||||
import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
|
||||
import { dispatchAddInstalledApps } from './addinstalledapps'
|
||||
import { dispatchAddUsedTime } from './addusedtime'
|
||||
import { dispatchAddUsedTimeVersion2 } from './addusedtime2'
|
||||
@@ -64,6 +65,6 @@ export const dispatchAppLogicAction = async ({ action, deviceId, cache, eventHan
|
||||
} else if (action instanceof TriedDisablingDeviceAdminAction) {
|
||||
await dispatchTriedDisablingDeviceAdmin({ deviceId, action, cache })
|
||||
} else {
|
||||
throw new Error('unsupported action type')
|
||||
throw new ActionObjectTypeNotHandledException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -18,14 +18,16 @@
|
||||
import { SetDeviceUserAction, SignOutAtDeviceAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { dispatchSetDeviceUser } from '../dispatch-parent-action/setdeviceuser'
|
||||
import { IllegalStateException, SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||
import { PremiumVersionMissingException } from '../exception/premium'
|
||||
|
||||
export async function dispatchSignOutAtDevice ({ deviceId, action, cache }: {
|
||||
export async function dispatchSignOutAtDevice ({ deviceId, cache }: {
|
||||
deviceId: string
|
||||
action: SignOutAtDeviceAction
|
||||
cache: Cache
|
||||
}) {
|
||||
if (!cache.hasFullVersion) {
|
||||
throw new Error('action requires full version')
|
||||
throw new PremiumVersionMissingException()
|
||||
}
|
||||
|
||||
const deviceEntry = await cache.database.device.findOne({
|
||||
@@ -37,11 +39,13 @@ export async function dispatchSignOutAtDevice ({ deviceId, action, cache }: {
|
||||
})
|
||||
|
||||
if (!deviceEntry) {
|
||||
throw new Error('illegal state: missing device which dispatched the action')
|
||||
throw new SourceDeviceNotFoundException()
|
||||
}
|
||||
|
||||
if (deviceEntry.defaultUserId === '') {
|
||||
throw new Error('no default user available')
|
||||
throw new IllegalStateException({
|
||||
staticMessage: 'tried to switch to the default user where it does not exist'
|
||||
})
|
||||
}
|
||||
|
||||
if (deviceEntry.currentUserId !== deviceEntry.defaultUserId) {
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -19,8 +19,9 @@ import { TriedDisablingDeviceAdminAction } from '../../../../action'
|
||||
import { hasDeviceManipulation } from '../../../../database/device'
|
||||
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||
|
||||
export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, action, cache }: {
|
||||
export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, cache }: {
|
||||
deviceId: string
|
||||
action: TriedDisablingDeviceAdminAction
|
||||
cache: Cache
|
||||
@@ -34,7 +35,7 @@ export async function dispatchTriedDisablingDeviceAdmin ({ deviceId, action, cac
|
||||
})
|
||||
|
||||
if (deviceEntry === null) {
|
||||
throw new Error('illegal state: missing device which dispatched the action')
|
||||
throw new SourceDeviceNotFoundException()
|
||||
}
|
||||
|
||||
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -29,9 +29,7 @@ export async function dispatchUpdateAppActivities ({ deviceId, action, cache }:
|
||||
if (action.updatedOrAdded.length > 0) {
|
||||
const chuncks = chunk(action.updatedOrAdded, 500)
|
||||
|
||||
for (let i = 0; i < chuncks.length; i++) {
|
||||
const items = chuncks[i]
|
||||
|
||||
for (const items of chuncks) {
|
||||
await cache.database.appActivity.destroy({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
@@ -62,9 +60,7 @@ export async function dispatchUpdateAppActivities ({ deviceId, action, cache }:
|
||||
if (action.removed.length > 0) {
|
||||
const chunks = chunk(action.removed, 500)
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const items = chunks[i]
|
||||
|
||||
for (const items of chunks) {
|
||||
await cache.database.appActivity.destroy({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -23,6 +23,7 @@ import { runtimePermissionStatusValues } from '../../../../model/runtimepermissi
|
||||
import { enumMax } from '../../../../util/enum'
|
||||
import { sendManipulationWarnings } from '../../../warningmail/manipulation'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||
|
||||
export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
|
||||
deviceId: string
|
||||
@@ -38,7 +39,7 @@ export async function dispatchUpdateDeviceStatus ({ deviceId, action, cache }: {
|
||||
})
|
||||
|
||||
if (!deviceEntry) {
|
||||
throw new Error('device not found')
|
||||
throw new SourceDeviceNotFoundException()
|
||||
}
|
||||
|
||||
const hadManipulationBefore = hasDeviceManipulation(deviceEntry)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -18,6 +18,7 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { ChildChangePasswordAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceUserNotFoundException } from '../exception/illegal-state'
|
||||
|
||||
export const dispatchChildChangePassword = async ({ action, childUserId, cache }: {
|
||||
action: ChildChangePasswordAction
|
||||
@@ -35,7 +36,7 @@ export const dispatchChildChangePassword = async ({ action, childUserId, cache }
|
||||
})
|
||||
|
||||
if (!childEntry) {
|
||||
throw new Error('child entry not found')
|
||||
throw new SourceUserNotFoundException()
|
||||
}
|
||||
|
||||
childEntry.passwordHash = action.password.hash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -18,15 +18,17 @@
|
||||
import { ChildSignInAction, SetDeviceUserAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { dispatchSetDeviceUser } from '../dispatch-parent-action/setdeviceuser'
|
||||
import { SourceUserNotFoundException } from '../exception/illegal-state'
|
||||
import { PremiumVersionMissingException } from '../exception/premium'
|
||||
|
||||
export const dispatchChildSignIn = async ({ action, deviceId, childUserId, cache }: {
|
||||
export const dispatchChildSignIn = async ({ deviceId, childUserId, cache }: {
|
||||
action: ChildSignInAction
|
||||
deviceId: string
|
||||
childUserId: string
|
||||
cache: Cache
|
||||
}) => {
|
||||
if (!cache.hasFullVersion) {
|
||||
throw new Error('action requires full version')
|
||||
throw new PremiumVersionMissingException()
|
||||
}
|
||||
|
||||
await dispatchSetDeviceUser({
|
||||
@@ -50,7 +52,7 @@ export const dispatchChildSignIn = async ({ action, deviceId, childUserId, cache
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Error('illegal state')
|
||||
throw new SourceUserNotFoundException()
|
||||
}
|
||||
|
||||
if (userEntryUnsafe.currentDevice === deviceId) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ChildSignInAction
|
||||
} from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
|
||||
import { dispatchChildChangePassword } from './childchangepassword'
|
||||
import { dispatchChildSignIn } from './childsignin'
|
||||
|
||||
@@ -35,6 +36,6 @@ export const dispatchChildAction = async ({ action, deviceId, childUserId, cache
|
||||
} else if (action instanceof ChildSignInAction) {
|
||||
await dispatchChildSignIn({ action, childUserId, deviceId, cache })
|
||||
} else {
|
||||
throw new Error('unsupported action type')
|
||||
throw new ActionObjectTypeNotHandledException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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 { AddCategoryAppsAction } from '../../../../action'
|
||||
import { CategoryAppAttributes } from '../../../../database/categoryapp'
|
||||
import { getCategoryWithParentCategories } from '../../../../util/category'
|
||||
import { getCategoryWithParentCategories, GetParentCategoriesException } from '../../../../util/category'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceUserNotFoundException } from '../exception/illegal-state'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: AddCategoryAppsAction
|
||||
@@ -36,14 +39,14 @@ export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLim
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const { childId } = categoryEntryUnsafe
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (childId !== fromChildSelfLimitAddChildUserId) {
|
||||
throw new Error('can not add apps to other users')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,64 +80,74 @@ export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLim
|
||||
}).map((item) => item.categoryId)
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
|
||||
const userEntryUnsafe = await cache.database.user.findOne({
|
||||
attributes: [ 'categoryForNotAssignedApps' ],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: fromChildSelfLimitAddChildUserId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!userEntryUnsafe) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
|
||||
const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
|
||||
const allowUnassignedElements = validatedDefaultCategoryId !== undefined &&
|
||||
parentCategoriesOfTargetCategory.indexOf(validatedDefaultCategoryId) !== -1
|
||||
|
||||
const assertCanAddApp = async (packageName: string, isApp: boolean) => {
|
||||
const categoryAppEntryUnsafe = await cache.database.categoryApp.findOne({
|
||||
attributes: [ 'categoryId' ],
|
||||
try {
|
||||
const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
|
||||
const userEntryUnsafe = await cache.database.user.findOne({
|
||||
attributes: [ 'categoryForNotAssignedApps' ],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: {
|
||||
[Sequelize.Op.in]: userCategoryIds
|
||||
},
|
||||
packageName: packageName
|
||||
userId: fromChildSelfLimitAddChildUserId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
|
||||
if (!userEntryUnsafe) {
|
||||
throw new SourceUserNotFoundException()
|
||||
}
|
||||
|
||||
if (categoryAppEntry === null) {
|
||||
if ((isApp && allowUnassignedElements) || (!isApp)) {
|
||||
// allow
|
||||
const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
|
||||
const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
|
||||
const allowUnassignedElements = validatedDefaultCategoryId !== undefined &&
|
||||
parentCategoriesOfTargetCategory.indexOf(validatedDefaultCategoryId) !== -1
|
||||
|
||||
const assertCanAddApp = async (packageName: string, isApp: boolean) => {
|
||||
const categoryAppEntryUnsafe = await cache.database.categoryApp.findOne({
|
||||
attributes: [ 'categoryId' ],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: {
|
||||
[Sequelize.Op.in]: userCategoryIds
|
||||
},
|
||||
packageName: packageName
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
|
||||
|
||||
if (categoryAppEntry === null) {
|
||||
if ((isApp && allowUnassignedElements) || (!isApp)) {
|
||||
// allow
|
||||
} else {
|
||||
throw new SelfLimitationException({
|
||||
staticMessage: 'can not assign apps without category as child'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
throw new Error('can not assign apps without category as child')
|
||||
}
|
||||
} else {
|
||||
if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
|
||||
// allow
|
||||
} else {
|
||||
throw new Error('can not add app which is not contained in the parent category')
|
||||
if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
|
||||
// allow
|
||||
} else {
|
||||
throw new SelfLimitationException({
|
||||
staticMessage: 'can not add app which is not contained in the parent category as child'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < action.packageNames.length; i++) {
|
||||
const packageName = action.packageNames[i]
|
||||
for (let i = 0; i < action.packageNames.length; i++) {
|
||||
const packageName = action.packageNames[i]
|
||||
|
||||
if (packageName.indexOf(':') !== -1) {
|
||||
await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
|
||||
await assertCanAddApp(packageName, false)
|
||||
} else {
|
||||
await assertCanAddApp(packageName, true)
|
||||
if (packageName.indexOf(':') !== -1) {
|
||||
await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
|
||||
await assertCanAddApp(packageName, false)
|
||||
} else {
|
||||
await assertCanAddApp(packageName, true)
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
if (ex instanceof GetParentCategoriesException) {
|
||||
throw new MissingCategoryException()
|
||||
} else throw ex
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
import { AddCategoryNetworkIdAction } from '../../../../action'
|
||||
import { maxNetworkIdsPerCategory } from '../../../../database/categorynetworkid'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||
action: AddCategoryNetworkIdAction
|
||||
@@ -33,7 +35,7 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for new rule')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const count = await cache.database.categoryNetworkId.count({
|
||||
@@ -45,7 +47,9 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (count + 1 > maxNetworkIdsPerCategory) {
|
||||
throw new Error('category network limit reached')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'can not add a category network id because the category network limit reached'
|
||||
})
|
||||
}
|
||||
|
||||
const hasOldItem = (await cache.database.categoryNetworkId.count({
|
||||
@@ -58,7 +62,9 @@ export async function dispatchAddCategoryNetworkId ({ action, cache }: {
|
||||
})) !== 0
|
||||
|
||||
if (hasOldItem) {
|
||||
throw new Error('id already used')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'can not add a category network id because the id is already used'
|
||||
})
|
||||
}
|
||||
|
||||
await cache.database.categoryNetworkId.create({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -16,8 +16,10 @@
|
||||
*/
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { ChangeParentPasswordAction } from '../../../../action'
|
||||
import { ChangeParentPasswordAction, InvalidChangeParentPasswordIntegrityException } from '../../../../action/changeparentpassword'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchChangeParentPassword ({ action, cache }: {
|
||||
action: ChangeParentPasswordAction
|
||||
@@ -34,10 +36,17 @@ export async function dispatchChangeParentPassword ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!parentEntry) {
|
||||
throw new Error('parent entry not found')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
try {
|
||||
action.assertIntegrityValid({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
|
||||
} catch (ex) {
|
||||
if (ex instanceof InvalidChangeParentPasswordIntegrityException) {
|
||||
throw new ApplyActionException({ staticMessage: 'invalid new password integrity' })
|
||||
} else throw ex
|
||||
}
|
||||
|
||||
action.assertIntegrityValid({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
|
||||
const newSecondPasswordHash = action.decryptSecondHash({ oldPasswordSecondHash: parentEntry.secondPasswordHash })
|
||||
|
||||
parentEntry.passwordHash = action.newPasswordFirstHash
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
import { CreateCategoryAction } from '../../../../action'
|
||||
import { generateVersionId } from '../../../../util/token'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: CreateCategoryAction
|
||||
@@ -26,7 +28,7 @@ export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimi
|
||||
}) {
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== action.childId) {
|
||||
throw new Error('can not create categories for other child users')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +43,7 @@ export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimi
|
||||
})
|
||||
|
||||
if (!childEntry) {
|
||||
throw new Error('missing child for new category')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
const oldMaxSort: number = await cache.database.category.max('sort', {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
import { CreateTimeLimitRuleAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: CreateTimeLimitRuleAction
|
||||
@@ -33,12 +35,12 @@ export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSel
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for new rule')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
|
||||
throw new Error('can not add rules for other users')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,15 +17,25 @@
|
||||
|
||||
import { DeleteCategoryAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchDeleteCategory ({ action, cache }: {
|
||||
action: DeleteCategoryAction
|
||||
cache: Cache
|
||||
}) {
|
||||
// no version number needs to be updated
|
||||
const { familyId, transaction } = cache
|
||||
const { categoryId } = action
|
||||
|
||||
const categoryEntry = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId,
|
||||
categoryId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!categoryEntry) { throw new MissingCategoryException() }
|
||||
|
||||
await cache.database.timelimitRule.destroy({
|
||||
where: {
|
||||
familyId,
|
||||
@@ -75,4 +85,6 @@ export async function dispatchDeleteCategory ({ action, cache }: {
|
||||
if (affectedUserRows !== 0) {
|
||||
cache.invalidiateUserList = true
|
||||
}
|
||||
|
||||
// no version number needs to be updated
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { DeleteTimeLimitRuleAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingRuleException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
|
||||
action: DeleteTimeLimitRuleAction
|
||||
@@ -30,10 +31,12 @@ export async function dispatchDeleteTimeLimitRule ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (ruleEntry) {
|
||||
await ruleEntry.destroy({ transaction: cache.transaction })
|
||||
|
||||
cache.categoriesWithModifiedTimeLimitRules.push(ruleEntry.categoryId)
|
||||
cache.areChangesImportant = true
|
||||
if (!ruleEntry) {
|
||||
throw new MissingRuleException()
|
||||
}
|
||||
|
||||
await ruleEntry.destroy({ transaction: cache.transaction })
|
||||
|
||||
cache.categoriesWithModifiedTimeLimitRules.push(ruleEntry.categoryId)
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { IgnoreManipulationAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceDeviceNotFoundException } from '../exception/illegal-state'
|
||||
|
||||
export async function dispatchIgnoreManipulation ({ action, cache }: {
|
||||
action: IgnoreManipulationAction
|
||||
@@ -31,7 +32,7 @@ export async function dispatchIgnoreManipulation ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (deviceEntry === null) {
|
||||
throw new Error('illegal state: missing device which dispatched the action')
|
||||
throw new SourceDeviceNotFoundException()
|
||||
}
|
||||
|
||||
if (action.ignoreDeviceAdminManipulation) {
|
||||
|
||||
+4
-2
@@ -18,13 +18,15 @@
|
||||
import { IncrementCategoryExtraTimeAction } from '../../../../action'
|
||||
import { CategoryModel } from '../../../../database/category'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { PremiumVersionMissingException } from '../exception/premium'
|
||||
|
||||
export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
|
||||
action: IncrementCategoryExtraTimeAction
|
||||
cache: Cache
|
||||
}) {
|
||||
if (!cache.hasFullVersion) {
|
||||
throw new Error('action requires full version')
|
||||
throw new PremiumVersionMissingException()
|
||||
}
|
||||
|
||||
async function handleCategory (category: CategoryModel) {
|
||||
@@ -51,7 +53,7 @@ export async function dispatchIncrementCategoryExtraTime ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error(`tried to add extra time to ${action.categoryId} but it does not exist`)
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
await handleCategory(categoryEntry)
|
||||
|
||||
@@ -62,6 +62,8 @@ import {
|
||||
UpdateUserLimitLoginCategory
|
||||
} from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ActionObjectTypeNotHandledException } from '../exception/illegal-state'
|
||||
import { ActionNotSupportedBySelfLimitationException } from '../exception/self-limit'
|
||||
import { dispatchAddCategoryApps } from './addcategoryapps'
|
||||
import { dispatchAddCategoryNetworkId } from './addcategorynetworkid'
|
||||
import { dispatchAddUser } from './adduser'
|
||||
@@ -129,7 +131,9 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
|
||||
return dispatchUpdateCategoryBlockedTimes({ action, cache, fromChildSelfLimitAddChildUserId })
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId === null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
throw new ActionNotSupportedBySelfLimitationException()
|
||||
} else {
|
||||
if (action instanceof AddCategoryNetworkIdAction) {
|
||||
return dispatchAddCategoryNetworkId({ action, cache })
|
||||
} else if (action instanceof AddUserAction) {
|
||||
@@ -202,8 +206,8 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source
|
||||
return dispatchUpdateUserFlagsAction({ action, cache })
|
||||
} else if (action instanceof UpdateUserLimitLoginCategory) {
|
||||
return dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
|
||||
} else {
|
||||
throw new ActionObjectTypeNotHandledException()
|
||||
}
|
||||
} else {
|
||||
throw new Error('unsupported action type')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -18,6 +18,7 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { RemoveCategoryAppsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingItemException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchRemoveCategoryApps ({ action, cache }: {
|
||||
action: RemoveCategoryAppsAction
|
||||
@@ -35,7 +36,9 @@ export async function dispatchRemoveCategoryApps ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (affectedRows !== action.packageNames.length) {
|
||||
throw new Error('could not delete as much entries as requested')
|
||||
throw new MissingItemException({
|
||||
staticMessage: 'could not remove all requested category app items'
|
||||
})
|
||||
}
|
||||
|
||||
cache.categoriesWithModifiedApps.push(action.categoryId)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -21,6 +21,9 @@ import { difference } from 'lodash'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { RemoveUserAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { ApplyActionIntegrityException } from '../exception/integrity'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||
action: RemoveUserAction
|
||||
@@ -36,7 +39,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
throw new Error('invalid user id')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
if (user.type === 'parent') {
|
||||
@@ -45,7 +48,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||
}
|
||||
|
||||
if (parentUserId === action.userId) {
|
||||
throw new Error('users can not delete themself')
|
||||
throw new ApplyActionException({ staticMessage: 'users can not delete themself' })
|
||||
}
|
||||
|
||||
const expectedIntegrityValue = createHash('sha512').update(
|
||||
@@ -53,7 +56,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||
).digest('hex').substring(0, 16)
|
||||
|
||||
if (expectedIntegrityValue !== action.authentication) {
|
||||
throw new Error('invalid authentication value')
|
||||
throw new ApplyActionIntegrityException({ staticMessage: 'invalid authentication value for removing a user' })
|
||||
}
|
||||
|
||||
if (user.mail !== '') {
|
||||
@@ -69,7 +72,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||
})
|
||||
|
||||
if (usersWithLinkedMail <= 1) {
|
||||
throw new Error('this user is the last one with a linked mail address')
|
||||
throw new ApplyActionException({ staticMessage: 'this user is the last one with a linked mail address' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ export async function dispatchRemoveUser ({ action, cache, parentUserId }: {
|
||||
const allOtherParentUserIds = allParentUserIds.filter((item) => item !== action.userId)
|
||||
|
||||
if (difference(allOtherParentUserIds, usersWithLimitLoginCategories).length === 0) {
|
||||
throw new Error('can not delete the last user without limit login category')
|
||||
throw new ApplyActionException({ staticMessage: 'can not delete the last user without limit login category' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,26 @@
|
||||
|
||||
import { RenameChildAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchRenameChild ({ action, cache }: {
|
||||
action: RenameChildAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
const oldItem = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.childId,
|
||||
type: 'child'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldItem) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
await cache.database.user.update({
|
||||
name: action.newName
|
||||
}, {
|
||||
where: {
|
||||
@@ -33,10 +47,6 @@ export async function dispatchRenameChild ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows !== 1) {
|
||||
throw new Error('can not update child name if child does not exist')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.doesUserExist.cache.set(action.childId, false)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { ResetCategoryNetworkIdsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
|
||||
action: ResetCategoryNetworkIdsAction
|
||||
@@ -32,7 +33,7 @@ export async function dispatchResetCategoryNetworkIds ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for new rule')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
await cache.database.categoryNetworkId.destroy({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,26 @@
|
||||
|
||||
import { ResetParentBlockedTimesAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchResetParentBlockedTimes ({ action, cache }: {
|
||||
action: ResetParentBlockedTimesAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
const oldItem = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.parentId,
|
||||
type: 'parent'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldItem) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
await cache.database.user.update({
|
||||
blockedTimes: ''
|
||||
}, {
|
||||
where: {
|
||||
@@ -33,10 +47,6 @@ export async function dispatchResetParentBlockedTimes ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('invalid parent user id provided')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -17,16 +17,30 @@
|
||||
|
||||
import { SetCategoryExtraTimeAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { PremiumVersionMissingException } from '../exception/premium'
|
||||
|
||||
export async function dispatchSetCategoryExtraTime ({ action, cache }: {
|
||||
action: SetCategoryExtraTimeAction
|
||||
cache: Cache
|
||||
}) {
|
||||
if (!cache.hasFullVersion) {
|
||||
throw new Error('action requires full version')
|
||||
throw new PremiumVersionMissingException()
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.category.update({
|
||||
const oldItem = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: action.categoryId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldItem) {
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
await cache.database.category.update({
|
||||
extraTimeInMillis: action.newExtraTime,
|
||||
extraTimeDay: action.day
|
||||
}, {
|
||||
@@ -37,8 +51,6 @@ export async function dispatchSetCategoryExtraTime ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows !== 0) {
|
||||
cache.categoriesWithModifiedBaseData.push(action.categoryId)
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
cache.categoriesWithModifiedBaseData.push(action.categoryId)
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
+23
-8
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,11 +17,26 @@
|
||||
|
||||
import { SetCategoryForUnassignedAppsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
|
||||
action: SetCategoryForUnassignedAppsAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const oldUserEntry = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.childId,
|
||||
type: 'child'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldUserEntry) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
if (action.categoryId === '') {
|
||||
// nothing to check
|
||||
} else {
|
||||
@@ -35,7 +50,9 @@ export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('can not set a category which does not exist as category for unassigned apps')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'can not set a category which does not exist as category for unassigned apps'
|
||||
})
|
||||
}
|
||||
|
||||
const categoryEntry = {
|
||||
@@ -43,11 +60,13 @@ export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
|
||||
}
|
||||
|
||||
if (categoryEntry.childId !== action.childId) {
|
||||
throw new Error('can not set a category of one child as category for unassigned apps for an other child')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'can not set a category of one child as category for unassigned apps for an other child'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
await cache.database.user.update({
|
||||
categoryForNotAssignedApps: action.categoryId
|
||||
}, {
|
||||
where: {
|
||||
@@ -58,10 +77,6 @@ export async function dispatchSetCategoryForUnassignedApps ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows !== 1) {
|
||||
throw new Error('could not find a child with matching id for setting the category for not assigned apps')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -18,6 +18,7 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { SetChildPasswordAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetChildPassword ({ action, cache }: {
|
||||
action: SetChildPasswordAction
|
||||
@@ -34,7 +35,7 @@ export async function dispatchSetChildPassword ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!childEntry) {
|
||||
throw new Error('parent entry not found')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
childEntry.passwordHash = action.newPassword.hash
|
||||
|
||||
+15
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,25 @@
|
||||
|
||||
import { SetConsiderRebootManipulationAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetConsiderRebootManipulation ({ action, cache }: {
|
||||
action: SetConsiderRebootManipulationAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
const oldDevice = await cache.database.device.findOne({
|
||||
transaction: cache.transaction,
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
}
|
||||
})
|
||||
|
||||
if (!oldDevice) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
await cache.database.device.update({
|
||||
considerRebootManipulation: action.enable
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -32,10 +45,6 @@ export async function dispatchSetConsiderRebootManipulation ({ action, cache }:
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find device to update consider reboot manipulation')
|
||||
}
|
||||
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { SetDeviceDefaultUserAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException, MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetDeviceDefaultUser ({ action, cache }: {
|
||||
action: SetDeviceDefaultUserAction
|
||||
@@ -26,11 +27,23 @@ export async function dispatchSetDeviceDefaultUser ({ action, cache }: {
|
||||
const doesUserExist = await cache.doesUserExist(action.defaultUserId)
|
||||
|
||||
if (!doesUserExist) {
|
||||
throw new Error('can not set invalid user as default user')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
const oldDeviceItem = await cache.database.device.findOne({
|
||||
transaction: cache.transaction,
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
}
|
||||
})
|
||||
|
||||
if (!oldDeviceItem) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
await cache.database.device.update({
|
||||
defaultUserId: action.defaultUserId
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -40,10 +53,6 @@ export async function dispatchSetDeviceDefaultUser ({ action, cache }: {
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find device to update default user')
|
||||
}
|
||||
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
+15
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,25 @@
|
||||
|
||||
import { SetDeviceDefaultUserTimeoutAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetDeviceDefaultUserTimeout ({ action, cache }: {
|
||||
action: SetDeviceDefaultUserTimeoutAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
const oldDeviceItem = await cache.database.device.findOne({
|
||||
transaction: cache.transaction,
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
}
|
||||
})
|
||||
|
||||
if (!oldDeviceItem) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
await cache.database.device.update({
|
||||
defaultUserTimeout: action.timeout
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -32,10 +45,6 @@ export async function dispatchSetDeviceDefaultUserTimeout ({ action, cache }: {
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find device to update default user timeout')
|
||||
}
|
||||
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { SetDeviceUserAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException, MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetDeviceUser ({ action, cache }: {
|
||||
action: SetDeviceUserAction
|
||||
@@ -26,11 +27,23 @@ export async function dispatchSetDeviceUser ({ action, cache }: {
|
||||
const doesUserExist = await cache.doesUserExist(action.userId)
|
||||
|
||||
if (!doesUserExist) {
|
||||
throw new Error('invalid user id provided')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
const oldDeviceItem = await cache.database.device.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldDeviceItem) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
await cache.database.device.update({
|
||||
currentUserId: action.userId,
|
||||
isUserKeptSignedIn: false
|
||||
}, {
|
||||
@@ -41,8 +54,6 @@ export async function dispatchSetDeviceUser ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows !== 0) {
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
import { SetKeepSignedInAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { SourceUserNotFoundException } from '../exception/illegal-state'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingDeviceException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetKeepSignedIn ({ action, cache, parentUserId }: {
|
||||
action: SetKeepSignedInAction
|
||||
@@ -26,7 +29,7 @@ export async function dispatchSetKeepSignedIn ({ action, cache, parentUserId }:
|
||||
const doesUserExist = await cache.doesUserExist(parentUserId)
|
||||
|
||||
if (!doesUserExist) {
|
||||
throw new Error('invalid user id provided')
|
||||
throw new SourceUserNotFoundException()
|
||||
}
|
||||
|
||||
const deviceEntry = await cache.database.device.findOne({
|
||||
@@ -38,12 +41,12 @@ export async function dispatchSetKeepSignedIn ({ action, cache, parentUserId }:
|
||||
})
|
||||
|
||||
if (!deviceEntry) {
|
||||
throw new Error('device does not exist')
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
if (deviceEntry.currentUserId !== parentUserId) {
|
||||
if (action.keepSignedIn) {
|
||||
throw new Error('only the user itself can disable asking for the password')
|
||||
throw new ApplyActionException({ staticMessage: 'only the user itself can disable asking for the password' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
*/
|
||||
|
||||
import { SetParentCategoryAction } from '../../../../action'
|
||||
import { getCategoryWithParentCategories } from '../../../../util/category'
|
||||
import { getCategoryWithParentCategories, GetParentCategoriesException } from '../../../../util/category'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchSetParentCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: SetParentCategoryAction
|
||||
@@ -33,12 +36,12 @@ export async function dispatchSetParentCategory ({ action, cache, fromChildSelfL
|
||||
})
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('tried to set parent category of non existent category')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (categoryEntry.childId !== fromChildSelfLimitAddChildUserId) {
|
||||
throw new Error('can not set parent category for other user')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +59,7 @@ export async function dispatchSetParentCategory ({ action, cache, fromChildSelfL
|
||||
}))
|
||||
|
||||
if (categoriesByUserId.find((item) => item.categoryId === action.parentCategory) === undefined) {
|
||||
throw new Error('selected parent category does not exist')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const childCategoryIds = new Set<string>()
|
||||
@@ -80,16 +83,26 @@ export async function dispatchSetParentCategory ({ action, cache, fromChildSelfL
|
||||
}
|
||||
|
||||
if (childCategoryIds.has(action.parentCategory) || action.parentCategory === action.categoryId) {
|
||||
throw new Error('can not set a category as parent which is a child of the category')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'can not set a category as parent which is a child of the category'
|
||||
})
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
const ownParentCategory = categoriesByUserId.find((item) => item.categoryId === categoryEntry.parentCategoryId)
|
||||
const enableDueToLimitAddingWhenChild = ownParentCategory === undefined ||
|
||||
getCategoryWithParentCategories(categoriesByUserId, action.parentCategory).indexOf(ownParentCategory.categoryId) !== -1
|
||||
try {
|
||||
const ownParentCategory = categoriesByUserId.find((item) => item.categoryId === categoryEntry.parentCategoryId)
|
||||
const enableDueToLimitAddingWhenChild = ownParentCategory === undefined ||
|
||||
getCategoryWithParentCategories(categoriesByUserId, action.parentCategory).indexOf(ownParentCategory.categoryId) !== -1
|
||||
|
||||
if (!enableDueToLimitAddingWhenChild) {
|
||||
throw new Error('can not change parent categories in a way which reduces limits')
|
||||
if (!enableDueToLimitAddingWhenChild) {
|
||||
throw new SelfLimitationException({
|
||||
staticMessage: 'can not change parent categories in a way which reduces limits'
|
||||
})
|
||||
}
|
||||
} catch (ex) {
|
||||
if (ex instanceof GetParentCategoriesException) {
|
||||
throw new MissingCategoryException()
|
||||
} else throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,26 @@
|
||||
|
||||
import { SetRelaxPrimaryDeviceAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetRelaxPrimaryDevice ({ action, cache }: {
|
||||
action: SetRelaxPrimaryDeviceAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
const oldUser = cache.database.user.findOne({
|
||||
transaction: cache.transaction,
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.userId,
|
||||
type: 'child'
|
||||
}
|
||||
})
|
||||
|
||||
if (!oldUser) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
await cache.database.user.update({
|
||||
relaxPrimaryDeviceRule: action.relax
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -33,10 +47,6 @@ export async function dispatchSetRelaxPrimaryDevice ({ action, cache }: {
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find user to update relax primary device')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { SetSendDeviceConnected } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
|
||||
export async function dispatchSetSendDeviceConnected ({ action, cache, sourceDeviceId }: {
|
||||
action: SetSendDeviceConnected
|
||||
@@ -24,10 +25,12 @@ export async function dispatchSetSendDeviceConnected ({ action, cache, sourceDev
|
||||
sourceDeviceId: string | null
|
||||
}) {
|
||||
if (sourceDeviceId === null || action.deviceId !== sourceDeviceId) {
|
||||
throw new Error('only can do that from the device itself')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'only can do that from the device itself if the connection status should be sent'
|
||||
})
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
await cache.database.device.update({
|
||||
showDeviceConnected: action.enable
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -37,10 +40,6 @@ export async function dispatchSetSendDeviceConnected ({ action, cache, sourceDev
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find device to update send if connected')
|
||||
}
|
||||
|
||||
cache.devicesWithModifiedShowDeviceConnected.set(action.deviceId, action.enable)
|
||||
cache.invalidiateDeviceList = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
import { SetUserDisableLimitsUntilAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
import { PremiumVersionMissingException } from '../exception/premium'
|
||||
|
||||
export async function dispatchUserSetDisableLimitsUntil ({ action, cache }: {
|
||||
action: SetUserDisableLimitsUntilAction
|
||||
@@ -24,11 +26,24 @@ export async function dispatchUserSetDisableLimitsUntil ({ action, cache }: {
|
||||
}) {
|
||||
if (action.timestamp !== 0) {
|
||||
if (!cache.hasFullVersion) {
|
||||
throw new Error('action requires full version')
|
||||
throw new PremiumVersionMissingException()
|
||||
}
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
const oldUser = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.childId,
|
||||
type: 'child'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldUser) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
await cache.database.user.update({
|
||||
disableTimelimitsUntil: action.timestamp.toString(10)
|
||||
}, {
|
||||
where: {
|
||||
@@ -39,10 +54,6 @@ export async function dispatchUserSetDisableLimitsUntil ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('invalid user id provided')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,25 @@
|
||||
|
||||
import { SetUserTimezoneAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchSetUserTimezone ({ action, cache }: {
|
||||
action: SetUserTimezoneAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
const oldUser = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.userId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldUser) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
await cache.database.user.update({
|
||||
timeZone: action.timezone
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -32,10 +45,6 @@ export async function dispatchSetUserTimezone ({ action, cache }: {
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find user to update timezone')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
import { UpdateCategoryBatteryLimitAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateCategoryBatteryLimit ({ action, cache }: {
|
||||
action: UpdateCategoryBatteryLimitAction
|
||||
@@ -31,7 +32,7 @@ export async function dispatchUpdateCategoryBatteryLimit ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('invalid category id')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
if (action.chargeLimit !== undefined) {
|
||||
|
||||
+5
-3
@@ -17,6 +17,8 @@
|
||||
|
||||
import { UpdateCategoryBlockAllNotificationsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: UpdateCategoryBlockAllNotificationsAction
|
||||
@@ -33,16 +35,16 @@ export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cac
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for updating notification blocking')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
|
||||
throw new Error('can not add rules for other users')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
|
||||
if (!action.blocked) {
|
||||
throw new Error('can not disable filter as child')
|
||||
throw new SelfLimitationException({ staticMessage: 'can not disable notification filter as child' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -18,6 +18,8 @@
|
||||
import { blockedTimesBitmaskLength, UpdateCategoryBlockedTimesAction } from '../../../../action/updatecategoryblockedtimes'
|
||||
import { validateAndParseBitmask } from '../../../../util/bitmask'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: UpdateCategoryBlockedTimesAction
|
||||
@@ -34,7 +36,7 @@ export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromC
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('can not update blocked time areas for a category which does not exist')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const categoryEntry = {
|
||||
@@ -44,7 +46,7 @@ export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromC
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (categoryEntry.childId !== fromChildSelfLimitAddChildUserId) {
|
||||
throw new Error('can not update blocked time areas for other child users')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
|
||||
const oldBlocked = validateAndParseBitmask(categoryEntry.blockedMinutesInWeek, blockedTimesBitmaskLength)
|
||||
@@ -52,7 +54,7 @@ export async function dispatchUpdateCategoryBlockedTimes ({ action, cache, fromC
|
||||
|
||||
oldBlocked.forEach((value, index) => {
|
||||
if (value && !newBlocked[index]) {
|
||||
throw new Error('new blocked time areas are smaller')
|
||||
throw new SelfLimitationException({ staticMessage: 'new blocked time areas are smaller' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+8
-5
@@ -17,6 +17,9 @@
|
||||
|
||||
import { UpdateCategoryTemporarilyBlockedAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
import { PremiumVersionMissingException } from '../exception/premium'
|
||||
import { CanNotModifyOtherUsersBySelfLimitationException, SelfLimitationException } from '../exception/self-limit'
|
||||
|
||||
export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache, fromChildSelfLimitAddChildUserId }: {
|
||||
action: UpdateCategoryTemporarilyBlockedAction
|
||||
@@ -25,7 +28,7 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache,
|
||||
}) {
|
||||
if (action.blocked === true) {
|
||||
if (!cache.hasFullVersion) {
|
||||
throw new Error('action requires full version')
|
||||
throw new PremiumVersionMissingException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +42,7 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache,
|
||||
})
|
||||
|
||||
if (!categoryEntryUnsafe) {
|
||||
throw new Error('invalid category id for updating temporarily blocking')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const categoryEntry = {
|
||||
@@ -50,16 +53,16 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache,
|
||||
|
||||
if (fromChildSelfLimitAddChildUserId !== null) {
|
||||
if (fromChildSelfLimitAddChildUserId !== categoryEntry.childId) {
|
||||
throw new Error('can not update temporarily blocking as child for other users')
|
||||
throw new CanNotModifyOtherUsersBySelfLimitationException()
|
||||
}
|
||||
|
||||
if (action.endTime === undefined || !action.blocked) {
|
||||
throw new Error('the child may only enable a temporarily blocking')
|
||||
throw new SelfLimitationException({ staticMessage: 'the child may only enable a temporarily blocking' })
|
||||
}
|
||||
|
||||
if (categoryEntry.temporarilyBlocked) {
|
||||
if (action.endTime < categoryEntry.temporarilyBlockedEndTime || categoryEntry.temporarilyBlockedEndTime === 0) {
|
||||
throw new Error('the child may not reduce the temporarily blocking')
|
||||
throw new SelfLimitationException({ staticMessage: 'the child may not reduce the temporarily blocking' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { UpdateCategoryTimeWarningsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateCategoryTimeWarnings ({ action, cache }: {
|
||||
action: UpdateCategoryTimeWarningsAction
|
||||
@@ -31,7 +32,7 @@ export async function dispatchUpdateCategoryTimeWarnings ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('invalid category id')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
if (action.enable) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,11 +17,24 @@
|
||||
|
||||
import { UpdateCategoryTitleAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingCategoryException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateCategoryTitle ({ action, cache }: {
|
||||
action: UpdateCategoryTitleAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const oldCategory = await cache.database.category.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
categoryId: action.categoryId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldCategory) {
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.category.update({
|
||||
title: action.newTitle
|
||||
}, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,11 +17,24 @@
|
||||
|
||||
import { UpdateDeviceNameAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateDeviceName ({ action, cache }: {
|
||||
action: UpdateDeviceNameAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const oldDevice = await cache.database.device.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldDevice) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
name: action.name
|
||||
}, {
|
||||
@@ -32,9 +45,7 @@ export async function dispatchUpdateDeviceName ({ action, cache }: {
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('invalid device id')
|
||||
} else {
|
||||
if (affectedRows !== 0) {
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
+15
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,12 +17,25 @@
|
||||
|
||||
import { UpdateEnableActivityLevelBlockingAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateEnableActivityLevelBlocking ({ action, cache }: {
|
||||
action: UpdateEnableActivityLevelBlockingAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const [affectedRows] = await cache.database.device.update({
|
||||
const oldDevice = await cache.database.device.findOne({
|
||||
transaction: cache.transaction,
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
}
|
||||
})
|
||||
|
||||
if (!oldDevice) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
await cache.database.device.update({
|
||||
activityLevelBlocking: action.enable
|
||||
}, {
|
||||
transaction: cache.transaction,
|
||||
@@ -32,10 +45,6 @@ export async function dispatchUpdateEnableActivityLevelBlocking ({ action, cache
|
||||
}
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('did not find device to update activity level blocking')
|
||||
}
|
||||
|
||||
cache.invalidiateDeviceList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
+13
@@ -17,11 +17,24 @@
|
||||
|
||||
import { UpdateNetworkTimeVerificationAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingDeviceException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateNetworkTimeVerification ({ action, cache }: {
|
||||
action: UpdateNetworkTimeVerificationAction
|
||||
cache: Cache
|
||||
}) {
|
||||
const oldDevice = await cache.database.device.findOne({
|
||||
transaction: cache.transaction,
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: action.deviceId
|
||||
}
|
||||
})
|
||||
|
||||
if (!oldDevice) {
|
||||
throw new MissingDeviceException()
|
||||
}
|
||||
|
||||
await cache.database.device.update({
|
||||
networkTime: action.mode
|
||||
}, {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
import { UpdateParentBlockedTimesAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateParentBlockedTimes ({ action, cache, parentUserId }: {
|
||||
action: UpdateParentBlockedTimesAction
|
||||
@@ -24,10 +26,23 @@ export async function dispatchUpdateParentBlockedTimes ({ action, cache, parentU
|
||||
parentUserId: string
|
||||
}) {
|
||||
if (parentUserId !== action.parentId && action.blockedTimes !== '') {
|
||||
throw new Error('only a parent itself can add limits')
|
||||
throw new ApplyActionException({ staticMessage: 'only a parent itself can add limits' })
|
||||
}
|
||||
|
||||
const [affectedRows] = await cache.database.user.update({
|
||||
const oldUser = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.parentId,
|
||||
type: 'parent'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!oldUser) {
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
await cache.database.user.update({
|
||||
blockedTimes: action.blockedTimes
|
||||
}, {
|
||||
where: {
|
||||
@@ -38,10 +53,6 @@ export async function dispatchUpdateParentBlockedTimes ({ action, cache, parentU
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (affectedRows === 0) {
|
||||
throw new Error('invalid parent user id provided')
|
||||
}
|
||||
|
||||
cache.invalidiateUserList = true
|
||||
cache.areChangesImportant = true
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { UpdateParentNotificationFlagsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateParentNotificationFlags ({ action, cache }: {
|
||||
action: UpdateParentNotificationFlagsAction
|
||||
@@ -32,7 +33,7 @@ export async function dispatchUpdateParentNotificationFlags ({ action, cache }:
|
||||
})
|
||||
|
||||
if (!parentEntry) {
|
||||
throw new Error('parent not found')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
if (action.set) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { UpdateTimelimitRuleAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { MissingRuleException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateTimelimitRule ({ action, cache }: {
|
||||
action: UpdateTimelimitRuleAction
|
||||
@@ -31,7 +32,7 @@ export async function dispatchUpdateTimelimitRule ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!ruleEntry) {
|
||||
throw new Error('invalid rule id provided')
|
||||
throw new MissingRuleException()
|
||||
}
|
||||
|
||||
ruleEntry.applyToExtraTimeUsage = action.applyToExtraTimeUsage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
import { UpdateUserFlagsAction } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { IllegalStateException } from '../exception/illegal-state'
|
||||
import { MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateUserFlagsAction ({ action, cache }: {
|
||||
action: UpdateUserFlagsAction
|
||||
@@ -31,13 +33,13 @@ export async function dispatchUpdateUserFlagsAction ({ action, cache }: {
|
||||
})
|
||||
|
||||
if (!userEntry) {
|
||||
throw new Error('user not found')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
const oldFlags = parseInt(userEntry.flags, 10)
|
||||
|
||||
if (!Number.isSafeInteger(oldFlags)) {
|
||||
throw new Error()
|
||||
throw new IllegalStateException({ staticMessage: 'oldFlags is not a safe integer' })
|
||||
}
|
||||
|
||||
const newFlags = (oldFlags & ~action.modifiedBits) | action.newValues
|
||||
|
||||
+9
-8
@@ -17,6 +17,8 @@
|
||||
|
||||
import { UpdateUserLimitLoginCategory } from '../../../../action'
|
||||
import { Cache } from '../cache'
|
||||
import { ApplyActionException } from '../exception/index'
|
||||
import { MissingCategoryException, MissingUserException } from '../exception/missing-item'
|
||||
|
||||
export async function dispatchUpdateUserLimitLoginCategoryAction ({ action, cache, parentUserId }: {
|
||||
action: UpdateUserLimitLoginCategory
|
||||
@@ -26,21 +28,20 @@ export async function dispatchUpdateUserLimitLoginCategoryAction ({ action, cach
|
||||
const userEntry = await cache.database.user.findOne({
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: action.userId
|
||||
userId: action.userId,
|
||||
type: 'parent'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!userEntry) {
|
||||
throw new Error('user not found')
|
||||
}
|
||||
|
||||
if (userEntry.type !== 'parent') {
|
||||
throw new Error('user must be a parent')
|
||||
throw new MissingUserException()
|
||||
}
|
||||
|
||||
if (action.categoryId !== undefined && parentUserId !== action.userId) {
|
||||
throw new Error('only the user itself can add a limit')
|
||||
throw new ApplyActionException({
|
||||
staticMessage: 'only the parent user itself can add a limit login category'
|
||||
})
|
||||
}
|
||||
|
||||
await cache.database.userLimitLoginCategory.destroy({
|
||||
@@ -61,7 +62,7 @@ export async function dispatchUpdateUserLimitLoginCategoryAction ({ action, cach
|
||||
})
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('category must exist')
|
||||
throw new MissingCategoryException()
|
||||
}
|
||||
|
||||
await cache.database.userLimitLoginCategory.create({
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class IllegalStateException extends ApplyActionException {
|
||||
constructor ({ staticMessage }: { staticMessage: string }) {
|
||||
super({ staticMessage })
|
||||
}
|
||||
}
|
||||
|
||||
export class SourceDeviceNotFoundException extends IllegalStateException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'source device not found' })
|
||||
}
|
||||
}
|
||||
|
||||
export class SourceUserNotFoundException extends IllegalStateException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'source user not found' })
|
||||
}
|
||||
}
|
||||
|
||||
export class SourceFamilyNotFoundException extends IllegalStateException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'family entry not found' })
|
||||
}
|
||||
}
|
||||
|
||||
export class ActionObjectTypeNotHandledException extends IllegalStateException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'action object type not handled' })
|
||||
}
|
||||
}
|
||||
@@ -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 '../../../../exception'
|
||||
|
||||
export class ApplyActionException extends StaticMessageException {}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class ApplyActionIntegrityException extends ApplyActionException {}
|
||||
|
||||
export class ParentDeviceActionWithoutParentDeviceException extends ApplyActionIntegrityException {
|
||||
constructor () { super({ staticMessage: 'parent device action but no parent device' }) }
|
||||
}
|
||||
|
||||
export class InvalidParentActionIntegrityValue extends ApplyActionIntegrityException {
|
||||
constructor () { super({ staticMessage: 'invalid parent action integrity value' }) }
|
||||
}
|
||||
|
||||
export class InvalidChildActionIntegrityValue extends ApplyActionIntegrityException {
|
||||
constructor () { super({ staticMessage: 'invalid child action integrity value' }) }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { InvalidActionParameterException } from '../../../../action/meta/exception'
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class InvalidActionException extends ApplyActionException {}
|
||||
|
||||
export class InvalidActionParamterException extends InvalidActionException {
|
||||
constructor (cause: InvalidActionParameterException) {
|
||||
super({
|
||||
staticMessage: cause.staticMessage,
|
||||
dynamicMessage: cause.dynamicMessage
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class BadEncodedActionJsonException extends InvalidActionException {
|
||||
constructor () { super({ staticMessage: 'bad encoded action JSON' }) }
|
||||
}
|
||||
|
||||
export class BadEncodedActionContentException extends InvalidActionException {}
|
||||
|
||||
export class EncodedActionSchemaMismatchException extends BadEncodedActionContentException {
|
||||
readonly action: object
|
||||
|
||||
constructor ({ type, action }: { type: 'parent' | 'child' | 'app logic', action: object }) {
|
||||
super({
|
||||
staticMessage: 'encoded ' + type + ' action does not match schema',
|
||||
dynamicMessage: 'encoded action does not match schema: ' + JSON.stringify(action)
|
||||
})
|
||||
|
||||
this.action = action
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class MissingItemException extends ApplyActionException {}
|
||||
|
||||
export class MissingCategoryException extends MissingItemException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'referenced category which does not exist' })
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingUserException extends MissingItemException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'referenced user which does not exist' })
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingRuleException extends MissingItemException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'referenced rule which does not exist' })
|
||||
}
|
||||
}
|
||||
|
||||
export class MissingDeviceException extends MissingItemException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'referenced device which does not exist' })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class PremiumVersionMissingException extends ApplyActionException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'premium version required but missing' })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class SelfLimitationException extends ApplyActionException {}
|
||||
|
||||
export class CanNotModifyOtherUsersBySelfLimitationException extends SelfLimitationException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'can not modify other users with the self limitation' })
|
||||
}
|
||||
}
|
||||
|
||||
export class ActionNotSupportedBySelfLimitationException extends SelfLimitationException {
|
||||
constructor () {
|
||||
super({ staticMessage: 'can not dispatch this action with the self limitation' })
|
||||
}
|
||||
}
|
||||
|
||||
export class SelfLimitNotPossibleException extends SelfLimitationException {}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplyActionException } from './index'
|
||||
|
||||
export class SequenceNumberRepeatedException extends ApplyActionException {
|
||||
constructor () { super({ staticMessage: 'sequence number repeated' }) }
|
||||
}
|
||||
@@ -15,20 +15,19 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { BadRequest, Unauthorized } from 'http-errors'
|
||||
import { parseAppLogicAction, parseChildAction, parseParentAction } from '../../../action/serialization'
|
||||
import { BadRequest } from 'http-errors'
|
||||
import { ClientPushChangesRequest } from '../../../api/schema'
|
||||
import { isSerializedAppLogicAction, isSerializedChildAction, isSerializedParentAction } from '../../../api/validator'
|
||||
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
|
||||
import { Database } from '../../../database'
|
||||
import { UserFlags } from '../../../model/userflags'
|
||||
import { EventHandler } from '../../../monitoring/eventhandler'
|
||||
import { WebsocketApi } from '../../../websocket'
|
||||
import { notifyClientsAboutChangesDelayed } from '../../websocket'
|
||||
import { getApplyActionBaseInfo } from './baseinfo'
|
||||
import { Cache } from './cache'
|
||||
import { dispatchAppLogicAction } from './dispatch-app-logic-action'
|
||||
import { dispatchChildAction } from './dispatch-child-action'
|
||||
import { dispatchParentAction } from './dispatch-parent-action'
|
||||
import { dispatchAppLogicAction, dispatchChildAction, dispatchParentAction } from './dispatch-helper'
|
||||
import { ApplyActionException } from './exception'
|
||||
import { IllegalStateException } from './exception/illegal-state'
|
||||
import { SequenceNumberRepeatedException } from './exception/sequence'
|
||||
import { assertActionIntegrity } from './integrity'
|
||||
|
||||
export const applyActionsFromDevice = async ({ database, request, websocket, connectedDevicesManager, eventHandler }: {
|
||||
@@ -37,6 +36,7 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
||||
request: ClientPushChangesRequest
|
||||
connectedDevicesManager: VisibleConnectedDevicesManager
|
||||
eventHandler: EventHandler
|
||||
// no transaction here because this is directly called from an API endpoint
|
||||
}): Promise<{ shouldDoFullSync: boolean }> => {
|
||||
eventHandler.countEvent('applyActionsFromDevice')
|
||||
|
||||
@@ -47,213 +47,83 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
||||
}
|
||||
|
||||
return database.transaction(async (transaction) => {
|
||||
const deviceEntryUnsafe = await database.device.findOne({
|
||||
where: {
|
||||
deviceAuthToken: request.deviceAuthToken
|
||||
},
|
||||
attributes: ['familyId', 'deviceId', 'nextSequenceNumber'],
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe) {
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
const deviceEntry = {
|
||||
familyId: deviceEntryUnsafe.familyId,
|
||||
deviceId: deviceEntryUnsafe.deviceId,
|
||||
nextSequenceNumber: deviceEntryUnsafe.nextSequenceNumber
|
||||
}
|
||||
|
||||
const familyEntryUnsafe = await database.family.findOne({
|
||||
where: {
|
||||
familyId: deviceEntry.familyId
|
||||
},
|
||||
transaction,
|
||||
attributes: ['hasFullVersion']
|
||||
})
|
||||
|
||||
if (!familyEntryUnsafe) {
|
||||
throw new Error('missing family entry')
|
||||
}
|
||||
|
||||
const familyEntry = {
|
||||
hasFullVersion: familyEntryUnsafe.hasFullVersion
|
||||
}
|
||||
const baseInfo = await getApplyActionBaseInfo({ database, transaction, deviceAuthToken: request.deviceAuthToken })
|
||||
|
||||
const cache = new Cache({
|
||||
database,
|
||||
hasFullVersion: familyEntry.hasFullVersion,
|
||||
hasFullVersion: baseInfo.hasFullVersion,
|
||||
transaction,
|
||||
familyId: deviceEntry.familyId,
|
||||
familyId: baseInfo.familyId,
|
||||
connectedDevicesManager
|
||||
})
|
||||
|
||||
let { nextSequenceNumber } = deviceEntry
|
||||
let { nextSequenceNumber } = baseInfo
|
||||
|
||||
for (const action of request.actions) {
|
||||
if (action.sequenceNumber < nextSequenceNumber) {
|
||||
// action was already received
|
||||
|
||||
eventHandler.countEvent('applyActionsFromDevice sequenceNumberRepeated')
|
||||
|
||||
cache.requireFullSync()
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
if (action.sequenceNumber < nextSequenceNumber) {
|
||||
// action was already received
|
||||
throw new SequenceNumberRepeatedException()
|
||||
}
|
||||
|
||||
await cache.subtransaction(async () => {
|
||||
// update the next sequence number
|
||||
nextSequenceNumber = action.sequenceNumber + 1
|
||||
|
||||
const { isChildLimitAdding } = await assertActionIntegrity({
|
||||
deviceId: deviceEntry.deviceId,
|
||||
deviceId: baseInfo.deviceId,
|
||||
cache,
|
||||
eventHandler,
|
||||
action
|
||||
})
|
||||
|
||||
const parsedSerializedAction = JSON.parse(action.encodedAction)
|
||||
|
||||
if (action.type === 'appLogic') {
|
||||
if (!isSerializedAppLogicAction(parsedSerializedAction)) {
|
||||
eventHandler.countEvent('applyActionsFromDevice invalidAppLogicAction')
|
||||
|
||||
throw new Error('invalid action: ' + action.encodedAction)
|
||||
}
|
||||
|
||||
eventHandler.countEvent('applyActionsFromDevice action:' + parsedSerializedAction.type)
|
||||
|
||||
const parsedAction = parseAppLogicAction(parsedSerializedAction)
|
||||
|
||||
try {
|
||||
await dispatchAppLogicAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
deviceId: deviceEntry.deviceId,
|
||||
eventHandler
|
||||
})
|
||||
} catch (ex) {
|
||||
eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
|
||||
|
||||
throw ex
|
||||
}
|
||||
await dispatchAppLogicAction({
|
||||
action,
|
||||
cache,
|
||||
deviceId: baseInfo.deviceId,
|
||||
eventHandler
|
||||
})
|
||||
} else if (action.type === 'parent') {
|
||||
if (!isSerializedParentAction(parsedSerializedAction)) {
|
||||
eventHandler.countEvent('applyActionsFromDevice invalidParentAction')
|
||||
|
||||
throw new Error('invalid action' + action.encodedAction)
|
||||
}
|
||||
|
||||
eventHandler.countEvent('applyActionsFromDevice, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
|
||||
|
||||
const parsedAction = parseParentAction(parsedSerializedAction)
|
||||
|
||||
try {
|
||||
if (isChildLimitAdding) {
|
||||
const deviceEntryUnsafe2 = await cache.database.device.findOne({
|
||||
attributes: ['currentUserId'],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
deviceId: deviceEntry.deviceId,
|
||||
currentUserId: action.userId
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe2) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
const deviceUserId = deviceEntryUnsafe2.currentUserId
|
||||
|
||||
if (!deviceUserId) {
|
||||
throw new Error('no device user id set but child add self limit action requested')
|
||||
}
|
||||
|
||||
const deviceUserEntryUnsafe = await cache.database.user.findOne({
|
||||
attributes: ['flags'],
|
||||
where: {
|
||||
familyId: cache.familyId,
|
||||
userId: deviceUserId,
|
||||
type: 'child'
|
||||
},
|
||||
transaction: cache.transaction
|
||||
})
|
||||
|
||||
if (!deviceUserEntryUnsafe) {
|
||||
throw new Error('no child user found for child limit adding action')
|
||||
}
|
||||
|
||||
if ((parseInt(deviceUserEntryUnsafe.flags, 10) & UserFlags.ALLOW_SELF_LIMIT_ADD) !== UserFlags.ALLOW_SELF_LIMIT_ADD) {
|
||||
throw new Error('child add limit action found but not allowed')
|
||||
}
|
||||
|
||||
await dispatchParentAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
parentUserId: action.userId,
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
fromChildSelfLimitAddChildUserId: deviceUserId
|
||||
})
|
||||
} else {
|
||||
await dispatchParentAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
parentUserId: action.userId,
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
fromChildSelfLimitAddChildUserId: null
|
||||
})
|
||||
}
|
||||
} catch (ex) {
|
||||
eventHandler.countEvent('applyActionsFromDeviceWithError, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
|
||||
|
||||
throw ex
|
||||
}
|
||||
await dispatchParentAction({
|
||||
action,
|
||||
cache,
|
||||
deviceId: baseInfo.deviceId,
|
||||
eventHandler,
|
||||
isChildLimitAdding
|
||||
})
|
||||
} else if (action.type === 'child') {
|
||||
if (!isSerializedChildAction(parsedSerializedAction)) {
|
||||
eventHandler.countEvent('applyActionsFromDevice invalidChildAction')
|
||||
|
||||
throw new Error('invalid action: ' + action.encodedAction)
|
||||
}
|
||||
|
||||
eventHandler.countEvent('applyActionsFromDevice action:' + parsedSerializedAction.type)
|
||||
|
||||
const parsedAction = parseChildAction(parsedSerializedAction)
|
||||
|
||||
try {
|
||||
await dispatchChildAction({
|
||||
action: parsedAction,
|
||||
cache,
|
||||
childUserId: action.userId,
|
||||
deviceId: deviceEntry.deviceId
|
||||
})
|
||||
} catch (ex) {
|
||||
eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
|
||||
|
||||
throw ex
|
||||
}
|
||||
await dispatchChildAction({
|
||||
action,
|
||||
cache,
|
||||
childUserId: action.userId,
|
||||
deviceId: baseInfo.deviceId,
|
||||
eventHandler
|
||||
})
|
||||
} else {
|
||||
throw new Error('illegal state')
|
||||
throw new IllegalStateException({ staticMessage: 'not possible action.type value' })
|
||||
}
|
||||
})
|
||||
} catch (ex) {
|
||||
eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction')
|
||||
if (ex instanceof ApplyActionException) {
|
||||
eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction:' + ex.staticMessage)
|
||||
} else {
|
||||
eventHandler.countEvent('applyActionsFromDevice errorDispatchingAction:other')
|
||||
}
|
||||
|
||||
cache.requireFullSync()
|
||||
}
|
||||
}
|
||||
|
||||
// save new next sequence number
|
||||
if (nextSequenceNumber !== deviceEntry.nextSequenceNumber) {
|
||||
if (nextSequenceNumber !== baseInfo.nextSequenceNumber) {
|
||||
eventHandler.countEvent('applyActionsFromDevice updateSequenceNumber')
|
||||
|
||||
await database.device.update({
|
||||
nextSequenceNumber
|
||||
}, {
|
||||
where: {
|
||||
familyId: deviceEntry.familyId,
|
||||
deviceId: deviceEntry.deviceId
|
||||
familyId: baseInfo.familyId,
|
||||
deviceId: baseInfo.deviceId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
@@ -262,8 +132,8 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
|
||||
await cache.saveModifiedVersionNumbers()
|
||||
|
||||
await notifyClientsAboutChangesDelayed({
|
||||
familyId: deviceEntry.familyId,
|
||||
sourceDeviceId: deviceEntry.deviceId,
|
||||
familyId: baseInfo.familyId,
|
||||
sourceDeviceId: baseInfo.deviceId,
|
||||
isImportant: cache.areChangesImportant,
|
||||
websocket,
|
||||
database,
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
|
||||
import { createHash } from 'crypto'
|
||||
import { ClientPushChangesRequestAction } from '../../../api/schema'
|
||||
import { EventHandler } from '../../../monitoring/eventhandler'
|
||||
import { Cache } from './cache'
|
||||
import {
|
||||
InvalidChildActionIntegrityValue, InvalidParentActionIntegrityValue, ParentDeviceActionWithoutParentDeviceException
|
||||
} from './exception/integrity'
|
||||
|
||||
export async function assertActionIntegrity ({ action, cache, eventHandler, deviceId }: {
|
||||
export async function assertActionIntegrity ({ action, cache, deviceId }: {
|
||||
action: ClientPushChangesRequestAction
|
||||
cache: Cache
|
||||
eventHandler: EventHandler
|
||||
deviceId: string
|
||||
}): Promise<{ isChildLimitAdding: boolean }> {
|
||||
let isChildLimitAdding = false
|
||||
@@ -42,7 +43,7 @@ export async function assertActionIntegrity ({ action, cache, eventHandler, devi
|
||||
})
|
||||
|
||||
if (!deviceEntryUnsafe) {
|
||||
throw new Error('user is not signed in at this device')
|
||||
throw new ParentDeviceActionWithoutParentDeviceException()
|
||||
}
|
||||
|
||||
// this ensures that the parent exists
|
||||
@@ -61,9 +62,7 @@ export async function assertActionIntegrity ({ action, cache, eventHandler, devi
|
||||
const expectedIntegrityValue = createHash('sha512').update(integrityData).digest('hex')
|
||||
|
||||
if (action.integrity !== expectedIntegrityValue) {
|
||||
eventHandler.countEvent('applyActionsFromDevice parentActionInvalidIntegrityValue')
|
||||
|
||||
throw new Error('invalid integrity value')
|
||||
throw new InvalidParentActionIntegrityValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,9 +78,7 @@ export async function assertActionIntegrity ({ action, cache, eventHandler, devi
|
||||
const expectedIntegrityValue = createHash('sha512').update(integrityData).digest('hex')
|
||||
|
||||
if (action.integrity !== expectedIntegrityValue) {
|
||||
eventHandler.countEvent('applyActionsFromDevice childActionInvalidIntegrityValue')
|
||||
|
||||
throw new Error('invalid integrity value')
|
||||
throw new InvalidChildActionIntegrityValue()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { InvalidActionParameterException } from '../../../action/meta/exception'
|
||||
import { ClientPushChangesRequestAction } from '../../../api/schema'
|
||||
import { BadEncodedActionJsonException, InvalidActionParamterException } from './exception/invalidaction'
|
||||
|
||||
export function parseEncodedAction (action: ClientPushChangesRequestAction): object {
|
||||
try {
|
||||
const result = JSON.parse(action.encodedAction)
|
||||
|
||||
if (typeof result !== 'object') throw new BadEncodedActionJsonException()
|
||||
|
||||
return result
|
||||
} catch (ex) {
|
||||
if (ex instanceof SyntaxError) {
|
||||
throw new BadEncodedActionJsonException()
|
||||
} else if (ex instanceof InvalidActionParameterException) {
|
||||
throw new InvalidActionParamterException(ex)
|
||||
} else throw ex
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { difference, filter, intersection } from 'lodash'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { config } from '../../config'
|
||||
import { Database } from '../../database'
|
||||
import { StaticMessageException } from '../../exception'
|
||||
import { getStatusMessage } from '../../function/statusmessage'
|
||||
import { ClientDataStatus } from '../../object/clientdatastatus'
|
||||
import {
|
||||
@@ -48,7 +49,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
})
|
||||
|
||||
if (!familyEntryUnsafe) {
|
||||
throw new Error('illegal state')
|
||||
throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find family entry' })
|
||||
}
|
||||
|
||||
const familyEntry = {
|
||||
@@ -210,7 +211,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
const entry = serverInstalledAppsVersions.find((item) => item.deviceId === deviceId)
|
||||
|
||||
if (!entry) {
|
||||
throw new Error('illegal state')
|
||||
throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find device entry' })
|
||||
}
|
||||
|
||||
return entry.installedAppsVersion
|
||||
@@ -333,7 +334,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
const clientEntry = clientStatus.categories[categoryId]
|
||||
|
||||
if ((!serverEntry) || (!clientEntry)) {
|
||||
throw new Error('illegal state')
|
||||
throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category overview item again' })
|
||||
}
|
||||
|
||||
if (serverEntry.baseVersion !== clientEntry.base) {
|
||||
@@ -460,7 +461,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('illegal state')
|
||||
throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
|
||||
}
|
||||
|
||||
return categoryEntry.assignedAppsVersion
|
||||
@@ -509,7 +510,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('illegal state')
|
||||
throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
|
||||
}
|
||||
|
||||
return categoryEntry.timeLimitRulesVersion
|
||||
@@ -586,7 +587,7 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
|
||||
|
||||
if (!categoryEntry) {
|
||||
throw new Error('illegal state')
|
||||
throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
|
||||
}
|
||||
|
||||
return categoryEntry.usedTimesVersion
|
||||
@@ -614,3 +615,9 @@ export const generateServerDataStatus = async ({ database, clientStatus, familyI
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export class GetServerDataStatusIllegalStateException extends StaticMessageException {
|
||||
constructor ({ staticMessage }: { staticMessage: string }) {
|
||||
super({ staticMessage: 'GetServerDataStatusIllegalStateException: ' + staticMessage })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user