diff --git a/src/function/sync/get-server-data-status.ts b/src/function/sync/get-server-data-status.ts
deleted file mode 100644
index 44cd6b6..0000000
--- a/src/function/sync/get-server-data-status.ts
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * server component for the TimeLimit App
- * Copyright (C) 2019 - 2020 Jonas Lochmann
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-import { 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 {
- ServerDataStatus, ServerInstalledAppsData, ServerUpdatedCategoryAssignedApps,
- ServerUpdatedCategoryBaseData, ServerUpdatedCategoryUsedTimes,
- ServerUpdatedTimeLimitRules
-} from '../../object/serverdatastatus'
-import { MinuteOfDay } from '../../util/minuteofday'
-
-export const generateServerDataStatus = async ({ database, clientStatus, familyId, transaction }: {
- database: Database,
- clientStatus: ClientDataStatus,
- familyId: string
- transaction: Sequelize.Transaction
-}): Promise => {
- const familyEntryUnsafe = await database.family.findOne({
- where: {
- familyId
- },
- attributes: [
- 'deviceListVersion',
- 'userListVersion',
- 'hasFullVersion',
- 'fullVersionUntil'
- ],
- transaction
- })
-
- if (!familyEntryUnsafe) {
- throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find family entry' })
- }
-
- const familyEntry = {
- deviceListVersion: familyEntryUnsafe.deviceListVersion,
- userListVersion: familyEntryUnsafe.userListVersion,
- hasFullVersion: familyEntryUnsafe.hasFullVersion,
- fullVersionUntil: familyEntryUnsafe.fullVersionUntil
- }
-
- let result: ServerDataStatus = {
- fullVersion: config.alwaysPro ? 1 : (
- familyEntry.hasFullVersion ? parseInt(familyEntry.fullVersionUntil, 10) : 0
- ),
- message: await getStatusMessage({ database, transaction }) || undefined
- }
-
- if (familyEntry.deviceListVersion !== clientStatus.devices) {
- const devices = (await database.device.findAll({
- where: {
- familyId
- },
- transaction
- }))
-
- result.devices = {
- version: familyEntry.deviceListVersion,
- data: devices.map((item) => ({
- deviceId: item.deviceId,
- name: item.name,
- model: item.model,
- addedAt: parseInt(item.addedAt, 10),
- currentUserId: item.currentUserId,
- networkTime: item.networkTime,
- cProtectionLevel: item.currentProtectionLevel,
- hProtectionLevel: item.highestProtectionLevel,
- cUsageStats: item.currentUsageStatsPermission,
- hUsageStats: item.highestUsageStatsPermission,
- cNotificationAccess: item.currentNotificationAccessPermission,
- hNotificationAccess: item.highestNotificationAccessPermission,
- cAppVersion: item.currentAppVersion,
- hAppVersion: item.highestAppVersion,
- tDisablingAdmin: item.triedDisablingDeviceAdmin,
- reboot: item.didReboot,
- hadManipulation: item.hadManipulation,
- hadManipulationFlags: item.hadManipulationFlags,
- reportUninstall: item.didDeviceReportUninstall,
- isUserKeptSignedIn: item.isUserKeptSignedIn,
- showDeviceConnected: item.showDeviceConnected,
- defUser: item.defaultUserId,
- defUserTimeout: item.defaultUserTimeout,
- rebootIsManipulation: item.considerRebootManipulation,
- cOverlay: item.currentOverlayPermission,
- hOverlay: item.highestOverlayPermission,
- asEnabled: item.asEnabled,
- wasAsEnabled: item.wasAsEnabled,
- activityLevelBlocking: item.activityLevelBlocking,
- qOrLater: item.isQorLater
- }))
- }
- }
-
- if (familyEntry.userListVersion !== clientStatus.users) {
- const users = (await database.user.findAll({
- where: {
- familyId
- },
- attributes: [
- 'userId',
- 'name',
- 'passwordHash',
- 'secondPasswordSalt',
- 'type',
- 'timeZone',
- 'disableTimelimitsUntil',
- 'mail',
- 'currentDevice',
- 'categoryForNotAssignedApps',
- 'relaxPrimaryDeviceRule',
- 'mailNotificationFlags',
- 'blockedTimes',
- 'flags'
- ],
- transaction
- })).map((item) => ({
- userId: item.userId,
- name: item.name,
- passwordHash: item.passwordHash,
- secondPasswordSalt: item.secondPasswordSalt,
- type: item.type,
- timeZone: item.timeZone,
- disableTimelimitsUntil: item.disableTimelimitsUntil,
- mail: item.mail,
- currentDevice: item.currentDevice,
- categoryForNotAssignedApps: item.categoryForNotAssignedApps,
- relaxPrimaryDeviceRule: item.relaxPrimaryDeviceRule,
- mailNotificationFlags: item.mailNotificationFlags,
- blockedTimes: item.blockedTimes,
- flags: item.flags
- }))
-
- const limitLoginCategories = (await database.userLimitLoginCategory.findAll({
- where: {
- familyId
- },
- attributes: [
- 'userId',
- 'categoryId'
- ],
- transaction
- })).map((item) => ({
- userId: item.userId,
- categoryId: item.categoryId
- }))
-
- const getLimitLoginCategory = (userId: string) => {
- const item = limitLoginCategories.find((item) => item.userId === userId)
-
- if (item) {
- return item.categoryId
- } else {
- return undefined
- }
- }
-
- result.users = {
- version: familyEntry.userListVersion,
- data: users.map((item) => ({
- id: item.userId,
- name: item.name,
- password: item.passwordHash,
- secondPasswordSalt: item.secondPasswordSalt,
- type: item.type,
- timeZone: item.timeZone,
- disableLimitsUntil: parseInt(item.disableTimelimitsUntil, 10),
- mail: item.mail,
- currentDevice: item.currentDevice,
- categoryForNotAssignedApps: item.categoryForNotAssignedApps,
- relaxPrimaryDevice: item.relaxPrimaryDeviceRule,
- mailNotificationFlags: item.mailNotificationFlags,
- blockedTimes: item.blockedTimes,
- flags: parseInt(item.flags, 10),
- llc: getLimitLoginCategory(item.userId)
- }))
- }
- }
-
- const serverInstalledAppsVersions = (await database.device.findAll({
- where: {
- familyId
- },
- attributes: ['deviceId', 'installedAppsVersion'],
- transaction
- })).map((item) => ({
- deviceId: item.deviceId,
- installedAppsVersion: item.installedAppsVersion
- }))
-
- const getServerInstalledAppsVersionByDeviceId = (deviceId: string) => {
- const entry = serverInstalledAppsVersions.find((item) => item.deviceId === deviceId)
-
- if (!entry) {
- throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find device entry' })
- }
-
- return entry.installedAppsVersion
- }
-
- const serverDeviceIds = serverInstalledAppsVersions.map((item) => item.deviceId)
- const clientDeviceIds = Object.keys(clientStatus.apps)
- const addedDeviceIds = difference(serverDeviceIds, clientDeviceIds)
- const deviceIdsWhereInstalledAppsHaveChanged = filter(Object.keys(clientStatus.apps), (deviceId) => {
- const installedAppsVersion = clientStatus.apps[deviceId]
-
- const serverEntry = serverInstalledAppsVersions.find((item) => item.deviceId === deviceId)
-
- return !!serverEntry && serverEntry.installedAppsVersion !== installedAppsVersion
- })
- const idsOfDevicesWhereInstalledAppsMustBeSynced = [...addedDeviceIds, ...deviceIdsWhereInstalledAppsHaveChanged]
-
- if (idsOfDevicesWhereInstalledAppsMustBeSynced.length > 0) {
- const [appsToSync, activitiesToSync] = await Promise.all([
- database.app.findAll({
- where: {
- familyId,
- deviceId: {
- [Sequelize.Op.in]: idsOfDevicesWhereInstalledAppsMustBeSynced
- }
- },
- attributes: [
- 'deviceId',
- 'packageName',
- 'title',
- 'isLaunchable',
- 'recommendation'
- ],
- transaction
- }).map((item) => ({
- deviceId: item.deviceId,
- packageName: item.packageName,
- title: item.title,
- isLaunchable: item.isLaunchable,
- recommendation: item.recommendation
- })),
- database.appActivity.findAll({
- where: {
- familyId,
- deviceId: {
- [Sequelize.Op.in]: idsOfDevicesWhereInstalledAppsMustBeSynced
- }
- },
- attributes: [
- 'deviceId',
- 'packageName',
- 'title',
- 'activityName'
- ],
- transaction
- }).map((item) => ({
- deviceId: item.deviceId,
- packageName: item.packageName,
- activityName: item.activityName,
- title: item.title
- }))
- ])
-
- result.apps = idsOfDevicesWhereInstalledAppsMustBeSynced.map((deviceId): ServerInstalledAppsData => ({
- deviceId,
- apps: appsToSync.filter((item) => item.deviceId === deviceId).map((item) => ({
- packageName: item.packageName,
- title: item.title,
- isLaunchable: item.isLaunchable,
- recommendation: item.recommendation
- })),
- activities: activitiesToSync.filter((item) => item.deviceId === deviceId).map((item) => ({
- p: item.packageName,
- c: item.activityName,
- t: item.title
- })),
- version: getServerInstalledAppsVersionByDeviceId(deviceId)
- }))
- }
-
- const serverCategoriesVersions = (await database.category.findAll({
- where: {
- familyId
- },
- attributes: [
- 'categoryId',
- 'baseVersion',
- 'assignedAppsVersion',
- 'timeLimitRulesVersion',
- 'usedTimesVersion'
- ],
- transaction
- })).map((item) => ({
- categoryId: item.categoryId,
- baseVersion: item.baseVersion,
- assignedAppsVersion: item.assignedAppsVersion,
- timeLimitRulesVersion: item.timeLimitRulesVersion,
- usedTimesVersion: item.usedTimesVersion
- }))
-
- const serverCategoryIds = serverCategoriesVersions.map((item) => item.categoryId)
- const clientCategoryIds = Object.keys(clientStatus.categories)
-
- const removedCategoryIds = difference(clientCategoryIds, serverCategoryIds)
-
- if (removedCategoryIds.length > 0) {
- result.rmCategories = removedCategoryIds
- }
-
- const addedCategoryIds = difference(serverCategoryIds, clientCategoryIds)
- const categoryIdsOfClientAndServer = intersection(serverCategoryIds, clientCategoryIds)
-
- const categoryIdsToSyncBaseData = [...addedCategoryIds]
- const categoryIdsToSyncAssignedApps = [...addedCategoryIds]
- const categoryIdsToSyncRules = [...addedCategoryIds]
- const categoryIdsToSyncUsedTimes = [...addedCategoryIds]
-
- categoryIdsOfClientAndServer.forEach((categoryId) => {
- const serverEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
- const clientEntry = clientStatus.categories[categoryId]
-
- if ((!serverEntry) || (!clientEntry)) {
- throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category overview item again' })
- }
-
- if (serverEntry.baseVersion !== clientEntry.base) {
- categoryIdsToSyncBaseData.push(categoryId)
- }
-
- if (serverEntry.assignedAppsVersion !== clientEntry.apps) {
- categoryIdsToSyncAssignedApps.push(categoryId)
- }
-
- if (serverEntry.timeLimitRulesVersion !== clientEntry.rules) {
- categoryIdsToSyncRules.push(categoryId)
- }
-
- if (serverEntry.usedTimesVersion !== clientEntry.usedTime) {
- categoryIdsToSyncUsedTimes.push(categoryId)
- }
- })
-
- if (categoryIdsToSyncBaseData.length > 0) {
- const dataForSyncing = (await database.category.findAll({
- where: {
- familyId,
- categoryId: {
- [Sequelize.Op.in]: categoryIdsToSyncBaseData
- }
- },
- attributes: [
- 'categoryId',
- 'childId',
- 'title',
- 'blockedMinutesInWeek',
- 'extraTimeInMillis',
- 'extraTimeDay',
- 'temporarilyBlocked',
- 'baseVersion',
- 'parentCategoryId',
- 'blockAllNotifications',
- 'timeWarningFlags',
- 'minBatteryCharging',
- 'minBatteryMobile',
- 'temporarilyBlockedEndTime',
- 'sort'
- ],
- transaction
- })).map((item) => ({
- categoryId: item.categoryId,
- childId: item.childId,
- title: item.title,
- blockedMinutesInWeek: item.blockedMinutesInWeek,
- extraTimeInMillis: item.extraTimeInMillis,
- extraTimeDay: item.extraTimeDay,
- temporarilyBlocked: item.temporarilyBlocked,
- baseVersion: item.baseVersion,
- parentCategoryId: item.parentCategoryId,
- blockAllNotifications: item.blockAllNotifications,
- timeWarningFlags: item.timeWarningFlags,
- minBatteryCharging: item.minBatteryCharging,
- minBatteryMobile: item.minBatteryMobile,
- temporarilyBlockedEndTime: item.temporarilyBlockedEndTime,
- sort: item.sort
- }))
-
- const networkIdsForSyncing = (await database.categoryNetworkId.findAll({
- where: {
- familyId,
- categoryId: {
- [Sequelize.Op.in]: categoryIdsToSyncBaseData
- }
- },
- attributes: [
- 'categoryId',
- 'networkItemId',
- 'hashedNetworkId'
- ],
- transaction
- })).map((item) => ({
- categoryId: item.categoryId,
- networkItemId: item.networkItemId,
- hashedNetworkId: item.hashedNetworkId
- }))
-
- result.categoryBase = dataForSyncing.map((item): ServerUpdatedCategoryBaseData => ({
- categoryId: item.categoryId,
- childId: item.childId,
- title: item.title,
- blockedTimes: item.blockedMinutesInWeek,
- extraTime: item.extraTimeInMillis,
- extraTimeDay: item.extraTimeDay,
- tempBlocked: item.temporarilyBlocked,
- version: item.baseVersion,
- parentCategoryId: item.parentCategoryId,
- blockAllNotifications: item.blockAllNotifications,
- timeWarnings: item.timeWarningFlags,
- mblMobile: item.minBatteryMobile,
- mblCharging: item.minBatteryCharging,
- tempBlockTime: item.temporarilyBlockedEndTime,
- sort: item.sort,
- networks: networkIdsForSyncing
- .filter((network) => network.categoryId === item.categoryId)
- .map((network) => ({
- itemId: network.networkItemId,
- hashedNetworkId: network.hashedNetworkId
- }))
- }))
- }
-
- if (categoryIdsToSyncAssignedApps.length > 0) {
- const dataForSyncing = (await database.categoryApp.findAll({
- where: {
- familyId,
- categoryId: {
- [Sequelize.Op.in]: categoryIdsToSyncAssignedApps
- }
- },
- attributes: ['categoryId', 'packageName'],
- transaction
- })).map((item) => ({
- categoryId: item.categoryId,
- packageName: item.packageName
- }))
-
- const getCategoryAssingedAppsVersion = (categoryId: string) => {
- const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
-
- if (!categoryEntry) {
- throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
- }
-
- return categoryEntry.assignedAppsVersion
- }
-
- result.categoryApp = categoryIdsToSyncAssignedApps.map((categoryId): ServerUpdatedCategoryAssignedApps => ({
- categoryId,
- apps: dataForSyncing.filter((item) => item.categoryId === categoryId).map((item) => item.packageName),
- version: getCategoryAssingedAppsVersion(categoryId)
- }))
- }
-
- if (categoryIdsToSyncRules.length > 0) {
- const dataForSyncing = (await database.timelimitRule.findAll({
- where: {
- familyId,
- categoryId: {
- [Sequelize.Op.in]: categoryIdsToSyncRules
- }
- },
- attributes: [
- 'ruleId',
- 'categoryId',
- 'applyToExtraTimeUsage',
- 'maximumTimeInMillis',
- 'dayMaskAsBitmask',
- 'startMinuteOfDay',
- 'endMinuteOfDay',
- 'sessionDurationMilliseconds',
- 'sessionPauseMilliseconds'
- ],
- transaction
- })).map((item) => ({
- ruleId: item.ruleId,
- categoryId: item.categoryId,
- applyToExtraTimeUsage: item.applyToExtraTimeUsage,
- maximumTimeInMillis: item.maximumTimeInMillis,
- dayMaskAsBitmask: item.dayMaskAsBitmask,
- startMinuteOfDay: item.startMinuteOfDay,
- endMinuteOfDay: item.endMinuteOfDay,
- sessionDurationMilliseconds: item.sessionDurationMilliseconds,
- sessionPauseMilliseconds: item.sessionPauseMilliseconds
- }))
-
- const getCategoryRulesVersion = (categoryId: string) => {
- const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
-
- if (!categoryEntry) {
- throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
- }
-
- return categoryEntry.timeLimitRulesVersion
- }
-
- result.rules = categoryIdsToSyncRules.map((categoryId): ServerUpdatedTimeLimitRules => ({
- categoryId,
- rules: dataForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
- id: item.ruleId,
- extraTime: item.applyToExtraTimeUsage,
- dayMask: item.dayMaskAsBitmask,
- maxTime: item.maximumTimeInMillis,
- start: item.startMinuteOfDay,
- end: item.endMinuteOfDay,
- session: item.sessionDurationMilliseconds,
- pause: item.sessionPauseMilliseconds
- })),
- version: getCategoryRulesVersion(categoryId)
- }))
- }
-
- if (categoryIdsToSyncUsedTimes.length > 0) {
- const usedTimesForSyncing = (await database.usedTime.findAll({
- where: {
- familyId,
- categoryId: {
- [Sequelize.Op.in]: categoryIdsToSyncUsedTimes
- },
- ...(clientStatus.clientLevel === undefined || clientStatus.clientLevel < 2) ? {
- startMinuteOfDay: MinuteOfDay.MIN,
- endMinuteOfDay: MinuteOfDay.MAX
- } : {}
- },
- attributes: [
- 'categoryId', 'dayOfEpoch', 'usedTime', 'startMinuteOfDay', 'endMinuteOfDay'
- ],
- transaction
- })).map((item) => ({
- categoryId: item.categoryId,
- dayOfEpoch: item.dayOfEpoch,
- usedTime: item.usedTime,
- startMinuteOfDay: item.startMinuteOfDay,
- endMinuteOfDay: item.endMinuteOfDay
- }))
-
- const sessionDurationsForSyncing = (await database.sessionDuration.findAll({
- where: {
- familyId,
- categoryId: {
- [Sequelize.Op.in]: categoryIdsToSyncUsedTimes
- }
- },
- attributes: [
- 'categoryId',
- 'maxSessionDuration',
- 'sessionPauseDuration',
- 'startMinuteOfDay',
- 'endMinuteOfDay',
- 'lastUsage',
- 'lastSessionDuration'
- ],
- transaction
- })).map((item) => ({
- categoryId: item.categoryId,
- maxSessionDuration: item.maxSessionDuration,
- sessionPauseDuration: item.sessionPauseDuration,
- startMinuteOfDay: item.startMinuteOfDay,
- endMinuteOfDay: item.endMinuteOfDay,
- lastUsage: item.lastUsage,
- lastSessionDuration: item.lastSessionDuration
- }))
-
- const getCategoryUsedTimesVersion = (categoryId: string) => {
- const categoryEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
-
- if (!categoryEntry) {
- throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
- }
-
- return categoryEntry.usedTimesVersion
- }
-
- result.usedTimes = categoryIdsToSyncUsedTimes.map((categoryId): ServerUpdatedCategoryUsedTimes => ({
- categoryId,
- times: usedTimesForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
- day: item.dayOfEpoch,
- time: item.usedTime,
- start: item.startMinuteOfDay,
- end: item.endMinuteOfDay
- })),
- sessionDurations: sessionDurationsForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
- md: item.maxSessionDuration,
- spd: item.sessionPauseDuration,
- sm: item.startMinuteOfDay,
- em: item.endMinuteOfDay,
- l: parseInt(item.lastUsage, 10),
- d: item.lastSessionDuration
- })),
- version: getCategoryUsedTimesVersion(categoryId)
- }))
- }
-
- return result
-}
-
-export class GetServerDataStatusIllegalStateException extends StaticMessageException {
- constructor ({ staticMessage }: { staticMessage: string }) {
- super({ staticMessage: 'GetServerDataStatusIllegalStateException: ' + staticMessage })
- }
-}
diff --git a/src/function/sync/get-server-data-status/app-list.ts b/src/function/sync/get-server-data-status/app-list.ts
new file mode 100644
index 0000000..797f666
--- /dev/null
+++ b/src/function/sync/get-server-data-status/app-list.ts
@@ -0,0 +1,127 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { difference, filter } from 'lodash'
+import * as Sequelize from 'sequelize'
+import { Database } from '../../../database'
+import { ClientDataStatusApps } from '../../../object/clientdatastatus'
+import { ServerInstalledAppsData } from '../../../object/serverdatastatus'
+import { GetServerDataStatusIllegalStateException } from './exception'
+import { FamilyEntry } from './family-entry'
+
+export async function getAppList ({ database, transaction, familyEntry, appsStatus }: {
+ database: Database
+ transaction: Sequelize.Transaction
+ familyEntry: FamilyEntry
+ appsStatus: ClientDataStatusApps
+}): Promise | null> {
+ const serverInstalledAppsVersions = (await database.device.findAll({
+ where: {
+ familyId: familyEntry.familyId
+ },
+ attributes: ['deviceId', 'installedAppsVersion'],
+ transaction
+ })).map((item) => ({
+ deviceId: item.deviceId,
+ installedAppsVersion: item.installedAppsVersion
+ }))
+
+ const getServerInstalledAppsVersionByDeviceId = (deviceId: string) => {
+ const entry = serverInstalledAppsVersions.find((item) => item.deviceId === deviceId)
+
+ if (!entry) {
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find device entry' })
+ }
+
+ return entry.installedAppsVersion
+ }
+
+ const serverDeviceIds = serverInstalledAppsVersions.map((item) => item.deviceId)
+ const clientDeviceIds = Object.keys(appsStatus)
+ const addedDeviceIds = difference(serverDeviceIds, clientDeviceIds)
+ const deviceIdsWhereInstalledAppsHaveChanged = filter(Object.keys(appsStatus), (deviceId) => {
+ const installedAppsVersion = appsStatus[deviceId]
+
+ const serverEntry = serverInstalledAppsVersions.find((item) => item.deviceId === deviceId)
+
+ return !!serverEntry && serverEntry.installedAppsVersion !== installedAppsVersion
+ })
+ const idsOfDevicesWhereInstalledAppsMustBeSynced = [...addedDeviceIds, ...deviceIdsWhereInstalledAppsHaveChanged]
+
+ if (idsOfDevicesWhereInstalledAppsMustBeSynced.length > 0) {
+ const [appsToSync, activitiesToSync] = await Promise.all([
+ database.app.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ deviceId: {
+ [Sequelize.Op.in]: idsOfDevicesWhereInstalledAppsMustBeSynced
+ }
+ },
+ attributes: [
+ 'deviceId',
+ 'packageName',
+ 'title',
+ 'isLaunchable',
+ 'recommendation'
+ ],
+ transaction
+ }).map((item) => ({
+ deviceId: item.deviceId,
+ packageName: item.packageName,
+ title: item.title,
+ isLaunchable: item.isLaunchable,
+ recommendation: item.recommendation
+ })),
+ database.appActivity.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ deviceId: {
+ [Sequelize.Op.in]: idsOfDevicesWhereInstalledAppsMustBeSynced
+ }
+ },
+ attributes: [
+ 'deviceId',
+ 'packageName',
+ 'title',
+ 'activityName'
+ ],
+ transaction
+ }).map((item) => ({
+ deviceId: item.deviceId,
+ packageName: item.packageName,
+ activityName: item.activityName,
+ title: item.title
+ }))
+ ])
+
+ return idsOfDevicesWhereInstalledAppsMustBeSynced.map((deviceId): ServerInstalledAppsData => ({
+ deviceId,
+ apps: appsToSync.filter((item) => item.deviceId === deviceId).map((item) => ({
+ packageName: item.packageName,
+ title: item.title,
+ isLaunchable: item.isLaunchable,
+ recommendation: item.recommendation
+ })),
+ activities: activitiesToSync.filter((item) => item.deviceId === deviceId).map((item) => ({
+ p: item.packageName,
+ c: item.activityName,
+ t: item.title
+ })),
+ version: getServerInstalledAppsVersionByDeviceId(deviceId)
+ }))
+ } else return null // no changes
+}
diff --git a/src/function/sync/get-server-data-status/category/assigned-apps.ts b/src/function/sync/get-server-data-status/category/assigned-apps.ts
new file mode 100644
index 0000000..885472b
--- /dev/null
+++ b/src/function/sync/get-server-data-status/category/assigned-apps.ts
@@ -0,0 +1,56 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database, Transaction } from '../../../../database'
+import { ServerUpdatedCategoryAssignedApps } from '../../../../object/serverdatastatus'
+import { FamilyEntry } from '../family-entry'
+import { ServerCategoryVersions } from './diff'
+
+export async function getCategoryAssignedApps ({
+ database, transaction, categoryIdsToSyncAssignedApps, familyEntry, serverCategoriesVersions
+}: {
+ database: Database
+ transaction: Transaction
+ categoryIdsToSyncAssignedApps: Array
+ familyEntry: FamilyEntry
+ serverCategoriesVersions: ServerCategoryVersions
+}): Promise> {
+ const dataForSyncing = (await database.categoryApp.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: categoryIdsToSyncAssignedApps
+ }
+ },
+ attributes: ['categoryId', 'packageName'],
+ transaction
+ })).map((item) => ({
+ categoryId: item.categoryId,
+ packageName: item.packageName
+ }))
+
+ const getCategoryAssingedAppsVersion = (categoryId: string) => (
+ serverCategoriesVersions.requireByCategoryId(categoryId).assignedAppsVersion
+ )
+
+ return categoryIdsToSyncAssignedApps.map((categoryId): ServerUpdatedCategoryAssignedApps => ({
+ categoryId,
+ apps: dataForSyncing.filter((item) => item.categoryId === categoryId).map((item) => item.packageName),
+ version: getCategoryAssingedAppsVersion(categoryId)
+ }))
+}
diff --git a/src/function/sync/get-server-data-status/category/base-data.ts b/src/function/sync/get-server-data-status/category/base-data.ts
new file mode 100644
index 0000000..26f850d
--- /dev/null
+++ b/src/function/sync/get-server-data-status/category/base-data.ts
@@ -0,0 +1,116 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database, Transaction } from '../../../../database'
+import { ServerUpdatedCategoryBaseData } from '../../../../object/serverdatastatus'
+import { FamilyEntry } from '../family-entry'
+
+export async function getCategoryBaseDatas ({
+ database, transaction, categoryIdsToSyncBaseData, familyEntry
+}: {
+ database: Database
+ transaction: Transaction
+ categoryIdsToSyncBaseData: Array
+ familyEntry: FamilyEntry
+}): Promise> {
+ const dataForSyncing = (await database.category.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: categoryIdsToSyncBaseData
+ }
+ },
+ attributes: [
+ 'categoryId',
+ 'childId',
+ 'title',
+ 'blockedMinutesInWeek',
+ 'extraTimeInMillis',
+ 'extraTimeDay',
+ 'temporarilyBlocked',
+ 'baseVersion',
+ 'parentCategoryId',
+ 'blockAllNotifications',
+ 'timeWarningFlags',
+ 'minBatteryCharging',
+ 'minBatteryMobile',
+ 'temporarilyBlockedEndTime',
+ 'sort'
+ ],
+ transaction
+ })).map((item) => ({
+ categoryId: item.categoryId,
+ childId: item.childId,
+ title: item.title,
+ blockedMinutesInWeek: item.blockedMinutesInWeek,
+ extraTimeInMillis: item.extraTimeInMillis,
+ extraTimeDay: item.extraTimeDay,
+ temporarilyBlocked: item.temporarilyBlocked,
+ baseVersion: item.baseVersion,
+ parentCategoryId: item.parentCategoryId,
+ blockAllNotifications: item.blockAllNotifications,
+ timeWarningFlags: item.timeWarningFlags,
+ minBatteryCharging: item.minBatteryCharging,
+ minBatteryMobile: item.minBatteryMobile,
+ temporarilyBlockedEndTime: item.temporarilyBlockedEndTime,
+ sort: item.sort
+ }))
+
+ const networkIdsForSyncing = (await database.categoryNetworkId.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: categoryIdsToSyncBaseData
+ }
+ },
+ attributes: [
+ 'categoryId',
+ 'networkItemId',
+ 'hashedNetworkId'
+ ],
+ transaction
+ })).map((item) => ({
+ categoryId: item.categoryId,
+ networkItemId: item.networkItemId,
+ hashedNetworkId: item.hashedNetworkId
+ }))
+
+ return dataForSyncing.map((item): ServerUpdatedCategoryBaseData => ({
+ categoryId: item.categoryId,
+ childId: item.childId,
+ title: item.title,
+ blockedTimes: item.blockedMinutesInWeek,
+ extraTime: item.extraTimeInMillis,
+ extraTimeDay: item.extraTimeDay,
+ tempBlocked: item.temporarilyBlocked,
+ version: item.baseVersion,
+ parentCategoryId: item.parentCategoryId,
+ blockAllNotifications: item.blockAllNotifications,
+ timeWarnings: item.timeWarningFlags,
+ mblMobile: item.minBatteryMobile,
+ mblCharging: item.minBatteryCharging,
+ tempBlockTime: item.temporarilyBlockedEndTime,
+ sort: item.sort,
+ networks: networkIdsForSyncing
+ .filter((network) => network.categoryId === item.categoryId)
+ .map((network) => ({
+ itemId: network.networkItemId,
+ hashedNetworkId: network.hashedNetworkId
+ }))
+ }))
+}
diff --git a/src/function/sync/get-server-data-status/category/diff.ts b/src/function/sync/get-server-data-status/category/diff.ts
new file mode 100644
index 0000000..b7ca6bb
--- /dev/null
+++ b/src/function/sync/get-server-data-status/category/diff.ts
@@ -0,0 +1,134 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { difference, intersection } from 'lodash'
+import * as Sequelize from 'sequelize'
+import { Database } from '../../../../database'
+import { ClientDataStatusCategories } from '../../../../object/clientdatastatus'
+import { GetServerDataStatusIllegalStateException } from '../exception'
+import { FamilyEntry } from '../family-entry'
+
+export async function getCategoryDataToSync ({ database, transaction, familyEntry, categoriesStatus }: {
+ database: Database
+ transaction: Sequelize.Transaction
+ familyEntry: FamilyEntry
+ categoriesStatus: ClientDataStatusCategories
+}): Promise {
+ const serverCategoriesVersions: Array = (await database.category.findAll({
+ where: {
+ familyId: familyEntry.familyId
+ },
+ attributes: [
+ 'categoryId',
+ 'baseVersion',
+ 'assignedAppsVersion',
+ 'timeLimitRulesVersion',
+ 'usedTimesVersion'
+ ],
+ transaction
+ })).map((item) => ({
+ categoryId: item.categoryId,
+ baseVersion: item.baseVersion,
+ assignedAppsVersion: item.assignedAppsVersion,
+ timeLimitRulesVersion: item.timeLimitRulesVersion,
+ usedTimesVersion: item.usedTimesVersion
+ }))
+
+ const serverCategoryIds = serverCategoriesVersions.map((item) => item.categoryId)
+ const clientCategoryIds = Object.keys(categoriesStatus)
+
+ const removedCategoryIds = difference(clientCategoryIds, serverCategoryIds)
+
+ const addedCategoryIds = difference(serverCategoryIds, clientCategoryIds)
+ const categoryIdsOfClientAndServer = intersection(serverCategoryIds, clientCategoryIds)
+
+ const categoryIdsToSyncBaseData = [...addedCategoryIds]
+ const categoryIdsToSyncAssignedApps = [...addedCategoryIds]
+ const categoryIdsToSyncRules = [...addedCategoryIds]
+ const categoryIdsToSyncUsedTimes = [...addedCategoryIds]
+
+ categoryIdsOfClientAndServer.forEach((categoryId) => {
+ const serverEntry = serverCategoriesVersions.find((item) => item.categoryId === categoryId)
+ const clientEntry = categoriesStatus[categoryId]
+
+ if ((!serverEntry) || (!clientEntry)) {
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category overview item again' })
+ }
+
+ if (serverEntry.baseVersion !== clientEntry.base) {
+ categoryIdsToSyncBaseData.push(categoryId)
+ }
+
+ if (serverEntry.assignedAppsVersion !== clientEntry.apps) {
+ categoryIdsToSyncAssignedApps.push(categoryId)
+ }
+
+ if (serverEntry.timeLimitRulesVersion !== clientEntry.rules) {
+ categoryIdsToSyncRules.push(categoryId)
+ }
+
+ if (serverEntry.usedTimesVersion !== clientEntry.usedTime) {
+ categoryIdsToSyncUsedTimes.push(categoryId)
+ }
+ })
+
+ const serverCategoriesVersionsMap = new Map()
+
+ serverCategoriesVersions.forEach((item) => serverCategoriesVersionsMap.set(item.categoryId, item))
+
+ return {
+ removedCategoryIds,
+ categoryIdsToSyncBaseData,
+ categoryIdsToSyncAssignedApps,
+ categoryIdsToSyncRules,
+ categoryIdsToSyncUsedTimes,
+ serverCategoriesVersions: {
+ list: serverCategoriesVersions,
+ requireByCategoryId: (categoryId) => {
+ const item = serverCategoriesVersionsMap.get(categoryId)
+
+ if (!item) {
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find category entry' })
+ }
+
+ return item
+ }
+ }
+ }
+}
+
+export interface GetCategoryDataToSyncResult {
+ removedCategoryIds: Array
+ categoryIdsToSyncBaseData: Array
+ categoryIdsToSyncAssignedApps: Array
+ categoryIdsToSyncRules: Array
+ categoryIdsToSyncUsedTimes: Array
+ serverCategoriesVersions: ServerCategoryVersions
+}
+
+export interface ServerCategoryVersions {
+ list: Array
+ requireByCategoryId: (categoryId: string) => ServerCategoryVersion
+}
+
+export interface ServerCategoryVersion {
+ categoryId: string
+ baseVersion: string
+ assignedAppsVersion: string
+ timeLimitRulesVersion: string
+ usedTimesVersion: string
+}
diff --git a/src/function/sync/get-server-data-status/category/index.ts b/src/function/sync/get-server-data-status/category/index.ts
new file mode 100644
index 0000000..991fc29
--- /dev/null
+++ b/src/function/sync/get-server-data-status/category/index.ts
@@ -0,0 +1,22 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+export { getCategoryDataToSync } from './diff'
+export { getCategoryBaseDatas } from './base-data'
+export { getRules } from './rules'
+export { getUsedTimes } from './used-times'
+export { getCategoryAssignedApps } from './assigned-apps'
diff --git a/src/function/sync/get-server-data-status/category/rules.ts b/src/function/sync/get-server-data-status/category/rules.ts
new file mode 100644
index 0000000..393554a
--- /dev/null
+++ b/src/function/sync/get-server-data-status/category/rules.ts
@@ -0,0 +1,80 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database, Transaction } from '../../../../database'
+import { ServerUpdatedTimeLimitRules } from '../../../../object/serverdatastatus'
+import { FamilyEntry } from '../family-entry'
+import { ServerCategoryVersions } from './diff'
+
+export async function getRules ({ database, transaction, categoryIdsToSyncRules, familyEntry, serverCategoriesVersions }: {
+ database: Database
+ transaction: Transaction
+ categoryIdsToSyncRules: Array
+ familyEntry: FamilyEntry
+ serverCategoriesVersions: ServerCategoryVersions
+}): Promise> {
+ const dataForSyncing = (await database.timelimitRule.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: categoryIdsToSyncRules
+ }
+ },
+ attributes: [
+ 'ruleId',
+ 'categoryId',
+ 'applyToExtraTimeUsage',
+ 'maximumTimeInMillis',
+ 'dayMaskAsBitmask',
+ 'startMinuteOfDay',
+ 'endMinuteOfDay',
+ 'sessionDurationMilliseconds',
+ 'sessionPauseMilliseconds'
+ ],
+ transaction
+ })).map((item) => ({
+ ruleId: item.ruleId,
+ categoryId: item.categoryId,
+ applyToExtraTimeUsage: item.applyToExtraTimeUsage,
+ maximumTimeInMillis: item.maximumTimeInMillis,
+ dayMaskAsBitmask: item.dayMaskAsBitmask,
+ startMinuteOfDay: item.startMinuteOfDay,
+ endMinuteOfDay: item.endMinuteOfDay,
+ sessionDurationMilliseconds: item.sessionDurationMilliseconds,
+ sessionPauseMilliseconds: item.sessionPauseMilliseconds
+ }))
+
+ const getCategoryRulesVersion = (categoryId: string) => (
+ serverCategoriesVersions.requireByCategoryId(categoryId).timeLimitRulesVersion
+ )
+
+ return categoryIdsToSyncRules.map((categoryId): ServerUpdatedTimeLimitRules => ({
+ categoryId,
+ rules: dataForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
+ id: item.ruleId,
+ extraTime: item.applyToExtraTimeUsage,
+ dayMask: item.dayMaskAsBitmask,
+ maxTime: item.maximumTimeInMillis,
+ start: item.startMinuteOfDay,
+ end: item.endMinuteOfDay,
+ session: item.sessionDurationMilliseconds,
+ pause: item.sessionPauseMilliseconds
+ })),
+ version: getCategoryRulesVersion(categoryId)
+ }))
+}
diff --git a/src/function/sync/get-server-data-status/category/used-times.ts b/src/function/sync/get-server-data-status/category/used-times.ts
new file mode 100644
index 0000000..24e482c
--- /dev/null
+++ b/src/function/sync/get-server-data-status/category/used-times.ts
@@ -0,0 +1,108 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database, Transaction } from '../../../../database'
+import { ServerUpdatedCategoryUsedTimes } from '../../../../object/serverdatastatus'
+import { MinuteOfDay } from '../../../../util/minuteofday'
+import { FamilyEntry } from '../family-entry'
+import { ServerCategoryVersions } from './diff'
+
+export async function getUsedTimes ({
+ database, transaction, categoryIdsToSyncUsedTimes, familyEntry,
+ serverCategoriesVersions, clientLevel
+}: {
+ database: Database
+ transaction: Transaction
+ categoryIdsToSyncUsedTimes: Array
+ familyEntry: FamilyEntry
+ serverCategoriesVersions: ServerCategoryVersions
+ clientLevel: number | null
+}): Promise> {
+ const usedTimesForSyncing = (await database.usedTime.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: categoryIdsToSyncUsedTimes
+ },
+ ...(clientLevel === null || clientLevel < 2) ? {
+ startMinuteOfDay: MinuteOfDay.MIN,
+ endMinuteOfDay: MinuteOfDay.MAX
+ } : {}
+ },
+ attributes: [
+ 'categoryId', 'dayOfEpoch', 'usedTime', 'startMinuteOfDay', 'endMinuteOfDay'
+ ],
+ transaction
+ })).map((item) => ({
+ categoryId: item.categoryId,
+ dayOfEpoch: item.dayOfEpoch,
+ usedTime: item.usedTime,
+ startMinuteOfDay: item.startMinuteOfDay,
+ endMinuteOfDay: item.endMinuteOfDay
+ }))
+
+ const sessionDurationsForSyncing = (await database.sessionDuration.findAll({
+ where: {
+ familyId: familyEntry.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: categoryIdsToSyncUsedTimes
+ }
+ },
+ attributes: [
+ 'categoryId',
+ 'maxSessionDuration',
+ 'sessionPauseDuration',
+ 'startMinuteOfDay',
+ 'endMinuteOfDay',
+ 'lastUsage',
+ 'lastSessionDuration'
+ ],
+ transaction
+ })).map((item) => ({
+ categoryId: item.categoryId,
+ maxSessionDuration: item.maxSessionDuration,
+ sessionPauseDuration: item.sessionPauseDuration,
+ startMinuteOfDay: item.startMinuteOfDay,
+ endMinuteOfDay: item.endMinuteOfDay,
+ lastUsage: item.lastUsage,
+ lastSessionDuration: item.lastSessionDuration
+ }))
+
+ const getCategoryUsedTimesVersion = (categoryId: string) => (
+ serverCategoriesVersions.requireByCategoryId(categoryId).usedTimesVersion
+ )
+
+ return categoryIdsToSyncUsedTimes.map((categoryId): ServerUpdatedCategoryUsedTimes => ({
+ categoryId,
+ times: usedTimesForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
+ day: item.dayOfEpoch,
+ time: item.usedTime,
+ start: item.startMinuteOfDay,
+ end: item.endMinuteOfDay
+ })),
+ sessionDurations: sessionDurationsForSyncing.filter((item) => item.categoryId === categoryId).map((item) => ({
+ md: item.maxSessionDuration,
+ spd: item.sessionPauseDuration,
+ sm: item.startMinuteOfDay,
+ em: item.endMinuteOfDay,
+ l: parseInt(item.lastUsage, 10),
+ d: item.lastSessionDuration
+ })),
+ version: getCategoryUsedTimesVersion(categoryId)
+ }))
+}
diff --git a/src/function/sync/get-server-data-status/device-list.ts b/src/function/sync/get-server-data-status/device-list.ts
new file mode 100644
index 0000000..442d136
--- /dev/null
+++ b/src/function/sync/get-server-data-status/device-list.ts
@@ -0,0 +1,70 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database } from '../../../database'
+import { ServerDeviceList } from '../../../object/serverdatastatus'
+import { FamilyEntry } from './family-entry'
+
+export async function getDeviceList ({ database, transaction, familyEntry }: {
+ database: Database
+ transaction: Sequelize.Transaction
+ familyEntry: FamilyEntry
+}): Promise {
+ const devices = (await database.device.findAll({
+ where: {
+ familyId: familyEntry.familyId
+ },
+ transaction
+ }))
+
+ return {
+ version: familyEntry.deviceListVersion,
+ data: devices.map((item) => ({
+ deviceId: item.deviceId,
+ name: item.name,
+ model: item.model,
+ addedAt: parseInt(item.addedAt, 10),
+ currentUserId: item.currentUserId,
+ networkTime: item.networkTime,
+ cProtectionLevel: item.currentProtectionLevel,
+ hProtectionLevel: item.highestProtectionLevel,
+ cUsageStats: item.currentUsageStatsPermission,
+ hUsageStats: item.highestUsageStatsPermission,
+ cNotificationAccess: item.currentNotificationAccessPermission,
+ hNotificationAccess: item.highestNotificationAccessPermission,
+ cAppVersion: item.currentAppVersion,
+ hAppVersion: item.highestAppVersion,
+ tDisablingAdmin: item.triedDisablingDeviceAdmin,
+ reboot: item.didReboot,
+ hadManipulation: item.hadManipulation,
+ hadManipulationFlags: item.hadManipulationFlags,
+ reportUninstall: item.didDeviceReportUninstall,
+ isUserKeptSignedIn: item.isUserKeptSignedIn,
+ showDeviceConnected: item.showDeviceConnected,
+ defUser: item.defaultUserId,
+ defUserTimeout: item.defaultUserTimeout,
+ rebootIsManipulation: item.considerRebootManipulation,
+ cOverlay: item.currentOverlayPermission,
+ hOverlay: item.highestOverlayPermission,
+ asEnabled: item.asEnabled,
+ wasAsEnabled: item.wasAsEnabled,
+ activityLevelBlocking: item.activityLevelBlocking,
+ qOrLater: item.isQorLater
+ }))
+ }
+}
diff --git a/src/function/sync/get-server-data-status/exception.ts b/src/function/sync/get-server-data-status/exception.ts
new file mode 100644
index 0000000..78fe153
--- /dev/null
+++ b/src/function/sync/get-server-data-status/exception.ts
@@ -0,0 +1,24 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { StaticMessageException } from '../../../exception'
+
+export class GetServerDataStatusIllegalStateException extends StaticMessageException {
+ constructor ({ staticMessage }: { staticMessage: string }) {
+ super({ staticMessage: 'GetServerDataStatusIllegalStateException: ' + staticMessage })
+ }
+}
diff --git a/src/function/sync/get-server-data-status/family-entry.ts b/src/function/sync/get-server-data-status/family-entry.ts
new file mode 100644
index 0000000..021653b
--- /dev/null
+++ b/src/function/sync/get-server-data-status/family-entry.ts
@@ -0,0 +1,59 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database } from '../../../database'
+import { GetServerDataStatusIllegalStateException } from './exception'
+
+export interface FamilyEntry {
+ familyId: string
+ deviceListVersion: string
+ userListVersion: string
+ hasFullVersion: boolean
+ fullVersionUntil: string
+}
+
+export async function getFamilyEntry ({ database, familyId, transaction }: {
+ database: Database
+ familyId: string
+ transaction: Sequelize.Transaction
+}): Promise {
+ const familyEntryUnsafe = await database.family.findOne({
+ where: {
+ familyId
+ },
+ attributes: [
+ 'deviceListVersion',
+ 'userListVersion',
+ 'hasFullVersion',
+ 'fullVersionUntil'
+ ],
+ transaction
+ })
+
+ if (!familyEntryUnsafe) {
+ throw new GetServerDataStatusIllegalStateException({ staticMessage: 'could not find family entry' })
+ }
+
+ return {
+ familyId,
+ deviceListVersion: familyEntryUnsafe.deviceListVersion,
+ userListVersion: familyEntryUnsafe.userListVersion,
+ hasFullVersion: familyEntryUnsafe.hasFullVersion,
+ fullVersionUntil: familyEntryUnsafe.fullVersionUntil
+ }
+}
diff --git a/src/function/sync/get-server-data-status/index.ts b/src/function/sync/get-server-data-status/index.ts
new file mode 100644
index 0000000..f7726a6
--- /dev/null
+++ b/src/function/sync/get-server-data-status/index.ts
@@ -0,0 +1,96 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { config } from '../../../config'
+import { Database } from '../../../database'
+import { getStatusMessage } from '../../../function/statusmessage'
+import { ClientDataStatus } from '../../../object/clientdatastatus'
+import { ServerDataStatus } from '../../../object/serverdatastatus'
+import { getAppList } from './app-list'
+import {
+ getCategoryAssignedApps, getCategoryBaseDatas, getCategoryDataToSync, getRules, getUsedTimes
+} from './category'
+import { getDeviceList } from './device-list'
+import { getFamilyEntry } from './family-entry'
+import { getUserList } from './user-list'
+
+export const generateServerDataStatus = async ({ database, clientStatus, familyId, transaction }: {
+ database: Database
+ clientStatus: ClientDataStatus
+ familyId: string
+ transaction: Sequelize.Transaction
+}): Promise => {
+ const familyEntry = await getFamilyEntry({ database, familyId, transaction })
+
+ let result: ServerDataStatus = {
+ fullVersion: config.alwaysPro ? 1 : (
+ familyEntry.hasFullVersion ? parseInt(familyEntry.fullVersionUntil, 10) : 0
+ ),
+ message: await getStatusMessage({ database, transaction }) || undefined
+ }
+
+ if (familyEntry.deviceListVersion !== clientStatus.devices) {
+ result.devices = await getDeviceList({ database, transaction, familyEntry })
+ }
+
+ if (familyEntry.userListVersion !== clientStatus.users) {
+ result.users = await getUserList({ database, transaction, familyEntry })
+ }
+
+ result.apps = await getAppList({ database, transaction, familyEntry, appsStatus: clientStatus.apps }) || undefined
+
+ const categoryDataToSync = await getCategoryDataToSync({ database, transaction, familyEntry, categoriesStatus: clientStatus.categories })
+
+ if (categoryDataToSync.removedCategoryIds.length > 0) {
+ result.rmCategories = categoryDataToSync.removedCategoryIds
+ }
+
+ if (categoryDataToSync.categoryIdsToSyncBaseData.length > 0) {
+ result.categoryBase = await getCategoryBaseDatas({
+ database, transaction, familyEntry,
+ categoryIdsToSyncBaseData: categoryDataToSync.categoryIdsToSyncBaseData
+ })
+ }
+
+ if (categoryDataToSync.categoryIdsToSyncAssignedApps.length > 0) {
+ result.categoryApp = await getCategoryAssignedApps({
+ database, transaction, familyEntry,
+ serverCategoriesVersions: categoryDataToSync.serverCategoriesVersions,
+ categoryIdsToSyncAssignedApps: categoryDataToSync.categoryIdsToSyncAssignedApps
+ })
+ }
+
+ if (categoryDataToSync.categoryIdsToSyncRules.length > 0) {
+ result.rules = await getRules({
+ database, transaction, familyEntry,
+ serverCategoriesVersions: categoryDataToSync.serverCategoriesVersions,
+ categoryIdsToSyncRules: categoryDataToSync.categoryIdsToSyncRules
+ })
+ }
+
+ if (categoryDataToSync.categoryIdsToSyncUsedTimes.length > 0) {
+ result.usedTimes = await getUsedTimes({
+ database, transaction, familyEntry,
+ serverCategoriesVersions: categoryDataToSync.serverCategoriesVersions,
+ categoryIdsToSyncUsedTimes: categoryDataToSync.categoryIdsToSyncUsedTimes,
+ clientLevel: clientStatus.clientLevel || null
+ })
+ }
+
+ return result
+}
diff --git a/src/function/sync/get-server-data-status/user-list.ts b/src/function/sync/get-server-data-status/user-list.ts
new file mode 100644
index 0000000..f15426c
--- /dev/null
+++ b/src/function/sync/get-server-data-status/user-list.ts
@@ -0,0 +1,110 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as Sequelize from 'sequelize'
+import { Database } from '../../../database'
+import { ServerUserList } from '../../../object/serverdatastatus'
+import { FamilyEntry } from './family-entry'
+
+export async function getUserList ({ database, transaction, familyEntry }: {
+ database: Database
+ transaction: Sequelize.Transaction
+ familyEntry: FamilyEntry
+}): Promise {
+ const users = (await database.user.findAll({
+ where: {
+ familyId: familyEntry.familyId
+ },
+ attributes: [
+ 'userId',
+ 'name',
+ 'passwordHash',
+ 'secondPasswordSalt',
+ 'type',
+ 'timeZone',
+ 'disableTimelimitsUntil',
+ 'mail',
+ 'currentDevice',
+ 'categoryForNotAssignedApps',
+ 'relaxPrimaryDeviceRule',
+ 'mailNotificationFlags',
+ 'blockedTimes',
+ 'flags'
+ ],
+ transaction
+ })).map((item) => ({
+ userId: item.userId,
+ name: item.name,
+ passwordHash: item.passwordHash,
+ secondPasswordSalt: item.secondPasswordSalt,
+ type: item.type,
+ timeZone: item.timeZone,
+ disableTimelimitsUntil: item.disableTimelimitsUntil,
+ mail: item.mail,
+ currentDevice: item.currentDevice,
+ categoryForNotAssignedApps: item.categoryForNotAssignedApps,
+ relaxPrimaryDeviceRule: item.relaxPrimaryDeviceRule,
+ mailNotificationFlags: item.mailNotificationFlags,
+ blockedTimes: item.blockedTimes,
+ flags: item.flags
+ }))
+
+ const limitLoginCategories = (await database.userLimitLoginCategory.findAll({
+ where: {
+ familyId: familyEntry.familyId
+ },
+ attributes: [
+ 'userId',
+ 'categoryId'
+ ],
+ transaction
+ })).map((item) => ({
+ userId: item.userId,
+ categoryId: item.categoryId
+ }))
+
+ const getLimitLoginCategory = (userId: string) => {
+ const item = limitLoginCategories.find((item) => item.userId === userId)
+
+ if (item) {
+ return item.categoryId
+ } else {
+ return undefined
+ }
+ }
+
+ return {
+ version: familyEntry.userListVersion,
+ data: users.map((item) => ({
+ id: item.userId,
+ name: item.name,
+ password: item.passwordHash,
+ secondPasswordSalt: item.secondPasswordSalt,
+ type: item.type,
+ timeZone: item.timeZone,
+ disableLimitsUntil: parseInt(item.disableTimelimitsUntil, 10),
+ mail: item.mail,
+ currentDevice: item.currentDevice,
+ categoryForNotAssignedApps: item.categoryForNotAssignedApps,
+ relaxPrimaryDevice: item.relaxPrimaryDeviceRule,
+ mailNotificationFlags: item.mailNotificationFlags,
+ blockedTimes: item.blockedTimes,
+ flags: parseInt(item.flags, 10),
+ llc: getLimitLoginCategory(item.userId)
+ }))
+ }
+}
diff --git a/src/object/clientdatastatus.ts b/src/object/clientdatastatus.ts
index 32c152e..c9bc6fd 100644
--- a/src/object/clientdatastatus.ts
+++ b/src/object/clientdatastatus.ts
@@ -17,12 +17,15 @@
export interface ClientDataStatus {
devices: string // deviceListVersion
- apps: {[key: string]: string} // installedAppsVersionsByDeviceId
- categories: {[key: string]: CategoryDataStatus}
+ apps: ClientDataStatusApps
+ categories: ClientDataStatusCategories
users: string // userListVersion
clientLevel?: number
}
+export type ClientDataStatusApps = {[key: string]: string} // installedAppsVersionsByDeviceId
+export type ClientDataStatusCategories = {[key: string]: CategoryDataStatus}
+
export interface CategoryDataStatus {
base: string // baseVersion
apps: string // assignedAppsVersion