mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2025-10-04 18:29:42 +02:00
Refactor code to send the status to the client
This commit is contained in:
parent
b17bc42a1e
commit
b4fd177afe
14 changed files with 1007 additions and 625 deletions
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<ServerDataStatus> => {
|
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
|
127
src/function/sync/get-server-data-status/app-list.ts
Normal file
127
src/function/sync/get-server-data-status/app-list.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Array<ServerInstalledAppsData> | 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
|
||||||
|
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
familyEntry: FamilyEntry
|
||||||
|
serverCategoriesVersions: ServerCategoryVersions
|
||||||
|
}): Promise<Array<ServerUpdatedCategoryAssignedApps>> {
|
||||||
|
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)
|
||||||
|
}))
|
||||||
|
}
|
116
src/function/sync/get-server-data-status/category/base-data.ts
Normal file
116
src/function/sync/get-server-data-status/category/base-data.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
familyEntry: FamilyEntry
|
||||||
|
}): Promise<Array<ServerUpdatedCategoryBaseData>> {
|
||||||
|
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
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}
|
134
src/function/sync/get-server-data-status/category/diff.ts
Normal file
134
src/function/sync/get-server-data-status/category/diff.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<GetCategoryDataToSyncResult> {
|
||||||
|
const serverCategoriesVersions: Array<ServerCategoryVersion> = (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<string, ServerCategoryVersion>()
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
categoryIdsToSyncBaseData: Array<string>
|
||||||
|
categoryIdsToSyncAssignedApps: Array<string>
|
||||||
|
categoryIdsToSyncRules: Array<string>
|
||||||
|
categoryIdsToSyncUsedTimes: Array<string>
|
||||||
|
serverCategoriesVersions: ServerCategoryVersions
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerCategoryVersions {
|
||||||
|
list: Array<ServerCategoryVersion>
|
||||||
|
requireByCategoryId: (categoryId: string) => ServerCategoryVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerCategoryVersion {
|
||||||
|
categoryId: string
|
||||||
|
baseVersion: string
|
||||||
|
assignedAppsVersion: string
|
||||||
|
timeLimitRulesVersion: string
|
||||||
|
usedTimesVersion: string
|
||||||
|
}
|
22
src/function/sync/get-server-data-status/category/index.ts
Normal file
22
src/function/sync/get-server-data-status/category/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* server component for the TimeLimit App
|
||||||
|
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { getCategoryDataToSync } from './diff'
|
||||||
|
export { getCategoryBaseDatas } from './base-data'
|
||||||
|
export { getRules } from './rules'
|
||||||
|
export { getUsedTimes } from './used-times'
|
||||||
|
export { getCategoryAssignedApps } from './assigned-apps'
|
80
src/function/sync/get-server-data-status/category/rules.ts
Normal file
80
src/function/sync/get-server-data-status/category/rules.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
familyEntry: FamilyEntry
|
||||||
|
serverCategoriesVersions: ServerCategoryVersions
|
||||||
|
}): Promise<Array<ServerUpdatedTimeLimitRules>> {
|
||||||
|
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)
|
||||||
|
}))
|
||||||
|
}
|
108
src/function/sync/get-server-data-status/category/used-times.ts
Normal file
108
src/function/sync/get-server-data-status/category/used-times.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
familyEntry: FamilyEntry
|
||||||
|
serverCategoriesVersions: ServerCategoryVersions
|
||||||
|
clientLevel: number | null
|
||||||
|
}): Promise<Array<ServerUpdatedCategoryUsedTimes>> {
|
||||||
|
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)
|
||||||
|
}))
|
||||||
|
}
|
70
src/function/sync/get-server-data-status/device-list.ts
Normal file
70
src/function/sync/get-server-data-status/device-list.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<ServerDeviceList> {
|
||||||
|
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
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
24
src/function/sync/get-server-data-status/exception.ts
Normal file
24
src/function/sync/get-server-data-status/exception.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* server component for the TimeLimit App
|
||||||
|
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { StaticMessageException } from '../../../exception'
|
||||||
|
|
||||||
|
export class GetServerDataStatusIllegalStateException extends StaticMessageException {
|
||||||
|
constructor ({ staticMessage }: { staticMessage: string }) {
|
||||||
|
super({ staticMessage: 'GetServerDataStatusIllegalStateException: ' + staticMessage })
|
||||||
|
}
|
||||||
|
}
|
59
src/function/sync/get-server-data-status/family-entry.ts
Normal file
59
src/function/sync/get-server-data-status/family-entry.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<FamilyEntry> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
96
src/function/sync/get-server-data-status/index.ts
Normal file
96
src/function/sync/get-server-data-status/index.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<ServerDataStatus> => {
|
||||||
|
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
|
||||||
|
}
|
110
src/function/sync/get-server-data-status/user-list.ts
Normal file
110
src/function/sync/get-server-data-status/user-list.ts
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<ServerUserList> {
|
||||||
|
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)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,15 @@
|
||||||
|
|
||||||
export interface ClientDataStatus {
|
export interface ClientDataStatus {
|
||||||
devices: string // deviceListVersion
|
devices: string // deviceListVersion
|
||||||
apps: {[key: string]: string} // installedAppsVersionsByDeviceId
|
apps: ClientDataStatusApps
|
||||||
categories: {[key: string]: CategoryDataStatus}
|
categories: ClientDataStatusCategories
|
||||||
users: string // userListVersion
|
users: string // userListVersion
|
||||||
clientLevel?: number
|
clientLevel?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ClientDataStatusApps = {[key: string]: string} // installedAppsVersionsByDeviceId
|
||||||
|
export type ClientDataStatusCategories = {[key: string]: CategoryDataStatus}
|
||||||
|
|
||||||
export interface CategoryDataStatus {
|
export interface CategoryDataStatus {
|
||||||
base: string // baseVersion
|
base: string // baseVersion
|
||||||
apps: string // assignedAppsVersion
|
apps: string // assignedAppsVersion
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue