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