From 0af0ff961e0e766ffdd23508c5616545bba17436 Mon Sep 17 00:00:00 2001 From: Jonas L Date: Mon, 15 Apr 2019 00:00:00 +0000 Subject: [PATCH] Add new actions for activity level blocking --- src/action/index.ts | 2 + src/action/serialization/applogicaction.ts | 4 + src/action/serialization/parentaction.ts | 4 + src/action/updateappactivities.ts | 61 ++++++++++++ .../updateenableactivitylevelblocking.ts | 49 ++++++++++ src/api/validator.ts | 97 +++++++++++++++++++ .../dispatch-app-logic-action/index.ts | 4 + .../updateappactivities.ts | 72 ++++++++++++++ .../dispatch-parent-action/index.ts | 4 + .../updateenableactivitylevelblocking.ts | 41 ++++++++ src/model/appactivity.ts | 70 +++++++++++++ 11 files changed, 408 insertions(+) create mode 100644 src/action/updateappactivities.ts create mode 100644 src/action/updateenableactivitylevelblocking.ts create mode 100644 src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts create mode 100644 src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts create mode 100644 src/model/appactivity.ts diff --git a/src/action/index.ts b/src/action/index.ts index 0d00ef7..9ca07ea 100644 --- a/src/action/index.ts +++ b/src/action/index.ts @@ -49,12 +49,14 @@ export { SetUserDisableLimitsUntilAction } from './setuserdisablelimitsuntil' export { SetUserTimezoneAction } from './setusertimezone' export { SignOutAtDeviceAction } from './signoutatdevice' export { TriedDisablingDeviceAdminAction } from './trieddisablingdeviceadmin' +export { UpdateAppActivitiesAction } from './updateappactivities' export { UpdateCategoryBlockAllNotificationsAction } from './updatecategoryblockallnotifications' export { UpdateCategoryBlockedTimesAction } from './updatecategoryblockedtimes' export { UpdateCategoryTemporarilyBlockedAction } from './updatecategorytemporarilyblocked' export { UpdateCategoryTitleAction } from './updatecategorytitle' export { UpdateDeviceNameAction } from './updatedevicename' export { UpdateDeviceStatusAction } from './updatedevicestatus' +export { UpdateEnableActivityLevelBlockingAction } from './updateenableactivitylevelblocking' export { UpdateNetworkTimeVerificationAction } from './updatenetworktimeverification' export { UpdateParentNotificationFlagsAction } from './updateparentnotificationflags' export { UpdateTimelimitRuleAction } from './updatetimelimitrule' diff --git a/src/action/serialization/applogicaction.ts b/src/action/serialization/applogicaction.ts index d015318..0a514af 100644 --- a/src/action/serialization/applogicaction.ts +++ b/src/action/serialization/applogicaction.ts @@ -21,6 +21,7 @@ import { AppLogicAction } from '../basetypes' import { RemoveInstalledAppsAction, SerializedRemoveInstalledAppsAction } from '../removeinstalledapps' import { SerializedSignOutAtDeviceAction, SignOutAtDeviceAction } from '../signoutatdevice' import { SerialiezdTriedDisablingDeviceAdminAction, TriedDisablingDeviceAdminAction } from '../trieddisablingdeviceadmin' +import { SerializedUpdateAppActivitiesAction, UpdateAppActivitiesAction } from '../updateappactivities' import { SerializedUpdateDeviceStatusAction, UpdateDeviceStatusAction } from '../updatedevicestatus' export type SerializedAppLogicAction = @@ -29,6 +30,7 @@ export type SerializedAppLogicAction = SerializedRemoveInstalledAppsAction | SerializedSignOutAtDeviceAction | SerialiezdTriedDisablingDeviceAdminAction | + SerializedUpdateAppActivitiesAction | SerializedUpdateDeviceStatusAction export const parseAppLogicAction = (serialized: SerializedAppLogicAction): AppLogicAction => { @@ -42,6 +44,8 @@ export const parseAppLogicAction = (serialized: SerializedAppLogicAction): AppLo return SignOutAtDeviceAction.parse(serialized) } else if (serialized.type === 'TRIED_DISABLING_DEVICE_ADMIN') { return new TriedDisablingDeviceAdminAction() + } else if (serialized.type === 'UPDATE_APP_ACTIVITIES') { + return UpdateAppActivitiesAction.parse(serialized) } else if (serialized.type === 'UPDATE_DEVICE_STATUS') { return UpdateDeviceStatusAction.parse(serialized) } else { diff --git a/src/action/serialization/parentaction.ts b/src/action/serialization/parentaction.ts index 228e5e2..a3ddd84 100644 --- a/src/action/serialization/parentaction.ts +++ b/src/action/serialization/parentaction.ts @@ -46,6 +46,7 @@ import { SerializedUpdateCategoryBlockedTimesAction, UpdateCategoryBlockedTimesA import { SerializedUpdateCategoryTemporarilyBlockedAction, UpdateCategoryTemporarilyBlockedAction } from '../updatecategorytemporarilyblocked' import { SerializedUpdateCategoryTitleAction, UpdateCategoryTitleAction } from '../updatecategorytitle' import { SerializedUpdateDeviceNameAction, UpdateDeviceNameAction } from '../updatedevicename' +import { SerializedUpdateEnableActivityLevelBlockingAction, UpdateEnableActivityLevelBlockingAction } from '../updateenableactivitylevelblocking' import { SerialiizedUpdateNetworkTimeVerificationAction, UpdateNetworkTimeVerificationAction } from '../updatenetworktimeverification' import { SerializedUpdateParentNotificationFlagsAction, UpdateParentNotificationFlagsAction } from '../updateparentnotificationflags' import { SerializedUpdateTimelimitRuleAction, UpdateTimelimitRuleAction } from '../updatetimelimitrule' @@ -81,6 +82,7 @@ export type SerializedParentAction = SerializedUpdateCategoryTemporarilyBlockedAction | SerializedUpdateCategoryTitleAction | SerializedUpdateDeviceNameAction | + SerializedUpdateEnableActivityLevelBlockingAction | SerialiizedUpdateNetworkTimeVerificationAction | SerializedUpdateParentNotificationFlagsAction | SerializedUpdateTimelimitRuleAction @@ -146,6 +148,8 @@ export const parseParentAction = (action: SerializedParentAction): ParentAction return UpdateCategoryTemporarilyBlockedAction.parse(action) } else if (action.type === 'UPDATE_DEVICE_NAME') { return UpdateDeviceNameAction.parse(action) + } else if (action.type === 'UPDATE_ENABLE_ACTIVITY_LEVEL_BLOCKING') { + return UpdateEnableActivityLevelBlockingAction.parse(action) } else if (action.type === 'UPDATE_NETWORK_TIME_VERIFICATION') { return UpdateNetworkTimeVerificationAction.parse(action) } else if (action.type === 'UPDATE_PARENT_NOTIFICATION_FLAGS') { diff --git a/src/action/updateappactivities.ts b/src/action/updateappactivities.ts new file mode 100644 index 0000000..39813ba --- /dev/null +++ b/src/action/updateappactivities.ts @@ -0,0 +1,61 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { AppActivityItem, RemovedAppActivityItem, SerializedAppActivityItem, SerializedRemovedAppActivityItem } from '../model/appactivity' +import { assertNonEmptyListWithoutDuplicates } from '../util/list' +import { AppLogicAction } from './basetypes' + +export class UpdateAppActivitiesAction extends AppLogicAction { + readonly removed: Array + readonly updatedOrAdded: Array + + constructor ({ removed, updatedOrAdded }: { + removed: Array + updatedOrAdded: Array + }) { + super() + + assertNonEmptyListWithoutDuplicates(removed.map((item) => item.packageName + ':' + item.activityName)) + assertNonEmptyListWithoutDuplicates(updatedOrAdded.map((item) => item.packageName + ':' + item.activityName)) + + if (removed.length === 0 && updatedOrAdded.length === 0) { + throw new Error('UpdateAppActivitiesAction is empty') + } + + this.removed = removed + this.updatedOrAdded = updatedOrAdded + } + + serialize = (): SerializedUpdateAppActivitiesAction => ({ + type: 'UPDATE_APP_ACTIVITIES', + removed: this.removed.map((item) => item.serialize()), + updatedOrAdded: this.updatedOrAdded.map((item) => item.serialize()) + }) + + static parse = ({ removed, updatedOrAdded }: SerializedUpdateAppActivitiesAction) => ( + new UpdateAppActivitiesAction({ + removed: removed.map((item) => RemovedAppActivityItem.parse(item)), + updatedOrAdded: updatedOrAdded.map((item) => AppActivityItem.parse(item)) + }) + ) +} + +export interface SerializedUpdateAppActivitiesAction { + type: 'UPDATE_APP_ACTIVITIES' + removed: Array + updatedOrAdded: Array +} diff --git a/src/action/updateenableactivitylevelblocking.ts b/src/action/updateenableactivitylevelblocking.ts new file mode 100644 index 0000000..3879499 --- /dev/null +++ b/src/action/updateenableactivitylevelblocking.ts @@ -0,0 +1,49 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { assertIdWithinFamily } from '../util/token' +import { ParentAction } from './basetypes' + +export class UpdateEnableActivityLevelBlockingAction extends ParentAction { + readonly deviceId: string + readonly enable: boolean + + constructor ({ deviceId, enable }: {deviceId: string, enable: boolean}) { + super() + + assertIdWithinFamily(deviceId) + + this.deviceId = deviceId + this.enable = enable + } + + serialize = (): SerializedUpdateEnableActivityLevelBlockingAction => ({ + type: 'UPDATE_ENABLE_ACTIVITY_LEVEL_BLOCKING', + deviceId: this.deviceId, + enable: this.enable + }) + + static parse = ({ deviceId, enable }: SerializedUpdateEnableActivityLevelBlockingAction) => ( + new UpdateEnableActivityLevelBlockingAction({ deviceId, enable }) + ) +} + +export interface SerializedUpdateEnableActivityLevelBlockingAction { + type: 'UPDATE_ENABLE_ACTIVITY_LEVEL_BLOCKING' + deviceId: string + enable: boolean +} diff --git a/src/api/validator.ts b/src/api/validator.ts index 351da7d..a4992d7 100644 --- a/src/api/validator.ts +++ b/src/api/validator.ts @@ -861,6 +861,29 @@ const definitions = { "type" ] }, + "SerializedUpdateEnableActivityLevelBlockingAction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "UPDATE_ENABLE_ACTIVITY_LEVEL_BLOCKING" + ] + }, + "deviceId": { + "type": "string" + }, + "enable": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "deviceId", + "enable", + "type" + ] + }, "SerialiizedUpdateNetworkTimeVerificationAction": { "type": "object", "properties": { @@ -1084,6 +1107,74 @@ const definitions = { "type" ] }, + "SerializedUpdateAppActivitiesAction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "UPDATE_APP_ACTIVITIES" + ] + }, + "removed": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "minItems": 2, + "additionalItems": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + } + }, + "updatedOrAdded": { + "type": "array", + "items": { + "$ref": "#/definitions/SerializedAppActivityItem" + } + } + }, + "additionalProperties": false, + "required": [ + "removed", + "type", + "updatedOrAdded" + ] + }, + "SerializedAppActivityItem": { + "type": "object", + "properties": { + "p": { + "type": "string" + }, + "c": { + "type": "string" + }, + "t": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "c", + "p", + "t" + ] + }, "SerializedUpdateDeviceStatusAction": { "type": "object", "properties": { @@ -1479,6 +1570,9 @@ export const isSerializedParentAction: (value: object) => value is SerializedPar { "$ref": "#/definitions/SerializedUpdateDeviceNameAction" }, + { + "$ref": "#/definitions/SerializedUpdateEnableActivityLevelBlockingAction" + }, { "$ref": "#/definitions/SerialiizedUpdateNetworkTimeVerificationAction" }, @@ -1509,6 +1603,9 @@ export const isSerializedAppLogicAction: (value: object) => value is SerializedA { "$ref": "#/definitions/SerialiezdTriedDisablingDeviceAdminAction" }, + { + "$ref": "#/definitions/SerializedUpdateAppActivitiesAction" + }, { "$ref": "#/definitions/SerializedUpdateDeviceStatusAction" } diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts index 168cb47..1e7c64a 100644 --- a/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts +++ b/src/function/sync/apply-actions/dispatch-app-logic-action/index.ts @@ -22,6 +22,7 @@ import { RemoveInstalledAppsAction, SignOutAtDeviceAction, TriedDisablingDeviceAdminAction, + UpdateAppActivitiesAction, UpdateDeviceStatusAction } from '../../../../action' import { Cache } from '../cache' @@ -30,6 +31,7 @@ import { dispatchAddUsedTime } from './addusedtime' import { dispatchRemoveInstalledApps } from './removeinstalledapps' import { dispatchSignOutAtDevice } from './signoutatdevice' import { dispatchTriedDisablingDeviceAdmin } from './trieddisablingdeviceadmin' +import { dispatchUpdateAppActivities } from './updateappactivities' import { dispatchUpdateDeviceStatus } from './updatedevicestatus' export const dispatchAppLogicAction = async ({ action, deviceId, cache }: { @@ -47,6 +49,8 @@ export const dispatchAppLogicAction = async ({ action, deviceId, cache }: { await dispatchSignOutAtDevice({ deviceId, action, cache }) } else if (action instanceof UpdateDeviceStatusAction) { await dispatchUpdateDeviceStatus({ deviceId, action, cache }) + } else if (action instanceof UpdateAppActivitiesAction) { + await dispatchUpdateAppActivities({ deviceId, action, cache }) } else if (action instanceof TriedDisablingDeviceAdminAction) { await dispatchTriedDisablingDeviceAdmin({ deviceId, action, cache }) } else { diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts new file mode 100644 index 0000000..6c18635 --- /dev/null +++ b/src/function/sync/apply-actions/dispatch-app-logic-action/updateappactivities.ts @@ -0,0 +1,72 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as Sequelize from 'sequelize' +import { UpdateAppActivitiesAction } from '../../../../action' +import { AppActivityAttributes } from '../../../../database/appactivity' +import { Cache } from '../cache' + +export async function dispatchUpdateAppActivities ({ deviceId, action, cache }: { + deviceId: string + action: UpdateAppActivitiesAction + cache: Cache +}) { + if (action.updatedOrAdded.length > 0) { + await cache.database.appActivity.destroy({ + where: { + familyId: cache.familyId, + deviceId, + [Sequelize.Op.or]: ( + action.updatedOrAdded.map((item) => ({ + packageName: item.packageName, + activityName: item.activityName + })) + ) + }, + transaction: cache.transaction + }) + + await cache.database.appActivity.bulkCreate( + action.updatedOrAdded.map((item): AppActivityAttributes => ({ + familyId: cache.familyId, + deviceId, + packageName: item.packageName, + activityName: item.activityName, + title: item.title + })), + { transaction: cache.transaction } + ) + } + + if (action.removed.length > 0) { + await cache.database.appActivity.destroy({ + where: { + familyId: cache.familyId, + deviceId, + [Sequelize.Op.or]: ( + action.removed.map((item) => ({ + packageName: item.packageName, + activityName: item.activityName + })) + ) + }, + transaction: cache.transaction + }) + } + + cache.devicesWithModifiedInstalledApps.push(deviceId) +} diff --git a/src/function/sync/apply-actions/dispatch-parent-action/index.ts b/src/function/sync/apply-actions/dispatch-parent-action/index.ts index c0869da..47e5ef8 100644 --- a/src/function/sync/apply-actions/dispatch-parent-action/index.ts +++ b/src/function/sync/apply-actions/dispatch-parent-action/index.ts @@ -47,6 +47,7 @@ import { UpdateCategoryTemporarilyBlockedAction, UpdateCategoryTitleAction, UpdateDeviceNameAction, + UpdateEnableActivityLevelBlockingAction, UpdateNetworkTimeVerificationAction, UpdateParentNotificationFlagsAction, UpdateTimelimitRuleAction @@ -82,6 +83,7 @@ import { dispatchUpdateCategoryBlockedTimes } from './updatecategoryblockedtimes import { dispatchUpdateCategoryTemporarilyBlocked } from './updatecategorytemporarilyblocked' import { dispatchUpdateCategoryTitle } from './updatecategorytitle' import { dispatchUpdateDeviceName } from './updatedevicename' +import { dispatchUpdateEnableActivityLevelBlocking } from './updateenableactivitylevelblocking' import { dispatchUpdateNetworkTimeVerification } from './updatenetworktimeverification' import { dispatchUpdateParentNotificationFlags } from './updateparentnotificationflags' import { dispatchUpdateTimelimitRule } from './updatetimelimitrule' @@ -144,6 +146,8 @@ export const dispatchParentAction = async ({ action, cache, parentUserId, source await dispatchDeleteTimeLimitRule({ action, cache }) } else if (action instanceof UpdateDeviceNameAction) { await dispatchUpdateDeviceName({ action, cache }) + } else if (action instanceof UpdateEnableActivityLevelBlockingAction) { + await dispatchUpdateEnableActivityLevelBlocking({ action, cache }) } else if (action instanceof UpdateNetworkTimeVerificationAction) { await dispatchUpdateNetworkTimeVerification({ action, cache }) } else if (action instanceof UpdateParentNotificationFlagsAction) { diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts b/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts new file mode 100644 index 0000000..dccc22b --- /dev/null +++ b/src/function/sync/apply-actions/dispatch-parent-action/updateenableactivitylevelblocking.ts @@ -0,0 +1,41 @@ +/* + * server component for the TimeLimit App + * Copyright (C) 2019 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { UpdateEnableActivityLevelBlockingAction } from '../../../../action' +import { Cache } from '../cache' + +export async function dispatchUpdateEnableActivityLevelBlocking ({ action, cache }: { + action: UpdateEnableActivityLevelBlockingAction + cache: Cache +}) { + const [affectedRows] = await cache.database.device.update({ + activityLevelBlocking: action.enable + }, { + transaction: cache.transaction, + where: { + familyId: cache.familyId, + deviceId: action.deviceId + } + }) + + if (affectedRows === 0) { + throw new Error('did not find device to update activity level blocking') + } + + cache.invalidiateDeviceList = true + cache.areChangesImportant = true +} diff --git a/src/model/appactivity.ts b/src/model/appactivity.ts new file mode 100644 index 0000000..ce5a47f --- /dev/null +++ b/src/model/appactivity.ts @@ -0,0 +1,70 @@ +export interface SerializedAppActivityItem { + p: string + c: string + t: string +} + +export class AppActivityItem { + readonly packageName: string + readonly activityName: string + readonly title: string + + constructor ({ packageName, activityName, title }: { + packageName: string + activityName: string + title: string + }) { + if ((!packageName) || (!activityName)) { + throw new Error('incomplete app activity') + } + + this.packageName = packageName + this.activityName = activityName + this.title = title + } + + static parse = ({ p, c, t }: SerializedAppActivityItem) => ( + new AppActivityItem({ + packageName: p, + activityName: c, + title: t + }) + ) + + serialize = (): SerializedAppActivityItem => ({ + p: this.packageName, + c: this.activityName, + t: this.title + }) +} + +export type SerializedRemovedAppActivityItem = [string, string] + +export class RemovedAppActivityItem { + readonly packageName: string + readonly activityName: string + + constructor ({ packageName, activityName }: { + packageName: string + activityName: string + }) { + if ((!packageName) || (!activityName)) { + throw new Error('incomplete app activity') + } + + this.packageName = packageName + this.activityName = activityName + } + + static parse = (data: SerializedRemovedAppActivityItem) => ( + new RemovedAppActivityItem({ + packageName: data[0], + activityName: data[1] + }) + ) + + serialize = (): SerializedRemovedAppActivityItem => ([ + this.packageName, + this.activityName + ]) +}