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
+ ])
+}