Refactor code to send the status to the client

This commit is contained in:
Jonas Lochmann 2020-09-28 02:00:00 +02:00
parent b17bc42a1e
commit b4fd177afe
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
14 changed files with 1007 additions and 625 deletions

View file

@ -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 })
}
}

View 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
}

View file

@ -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)
}))
}

View 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
}))
}))
}

View 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
}

View 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'

View 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)
}))
}

View 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)
}))
}

View 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
}))
}
}

View 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 })
}
}

View 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
}
}

View 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
}

View 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)
}))
}
}

View file

@ -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