From 346a860b0aae7097445aa5aa22f37a88d9d7955e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 7 Jan 2024 12:13:47 -0800 Subject: User and device expiration emails --- src/main/java/org/traccar/config/Keys.java | 38 ++++-- .../java/org/traccar/schedule/ScheduleManager.java | 3 +- .../java/org/traccar/schedule/TaskExpirations.java | 130 +++++++++++++++++++++ 3 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/traccar/schedule/TaskExpirations.java (limited to 'src') diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 3059c4f4b..3528dafa0 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -489,14 +489,6 @@ public final class Keys { "database.throttleUnknown", List.of(KeyType.CONFIG)); - /** - * By default, server syncs with the database if it encounters and unknown device. This flag allows to disable that - * behavior to improve performance in some cases. - */ - public static final ConfigKey DATABASE_IGNORE_UNKNOWN = new BooleanConfigKey( - "database.ignoreUnknown", - List.of(KeyType.CONFIG)); - /** * Automatically register unknown devices in the database. */ @@ -664,7 +656,7 @@ public final class Keys { /** * OpenID Connect Authorization URL. * This can usually be found in the documentation of your identity provider or by using the well-known - * configuration endpoint, eg. https://auth.example.com//.well-known/openid-configuration + * configuration endpoint, e.g. https://auth.example.com//.well-known/openid-configuration * Required to enable SSO if openid.issuerUrl is not set. */ public static final ConfigKey OPENID_AUTH_URL = new StringConfigKey( @@ -1225,6 +1217,34 @@ public final class Keys { "notificator.telegram.sendLocation", List.of(KeyType.CONFIG)); + /** + * Enable user expiration email notification. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_USER = new BooleanConfigKey( + "notification.expiration.user", + List.of(KeyType.CONFIG)); + + /** + * User expiration reminder. Value in milliseconds. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_USER_REMINDER = new LongConfigKey( + "notification.expiration.user.reminder", + List.of(KeyType.CONFIG)); + + /** + * Enable device expiration email notification. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_DEVICE = new BooleanConfigKey( + "notification.expiration.device", + List.of(KeyType.CONFIG)); + + /** + * Device expiration reminder. Value in milliseconds. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_DEVICE_REMINDER = new LongConfigKey( + "notification.expiration.device.reminder", + List.of(KeyType.CONFIG)); + /** * Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports. * By default there is no limit. diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java index 38e8f281c..3756d955b 100644 --- a/src/main/java/org/traccar/schedule/ScheduleManager.java +++ b/src/main/java/org/traccar/schedule/ScheduleManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ public class ScheduleManager implements LifecycleObject { public void start() { executor = Executors.newSingleThreadScheduledExecutor(); var tasks = List.of( + TaskExpirations.class, TaskDeleteTemporary.class, TaskReports.class, TaskDeviceInactivityCheck.class, diff --git a/src/main/java/org/traccar/schedule/TaskExpirations.java b/src/main/java/org/traccar/schedule/TaskExpirations.java new file mode 100644 index 000000000..94f855c5f --- /dev/null +++ b/src/main/java/org/traccar/schedule/TaskExpirations.java @@ -0,0 +1,130 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.schedule; + +import jakarta.inject.Inject; +import jakarta.mail.MessagingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.mail.MailManager; +import org.traccar.model.Device; +import org.traccar.model.Disableable; +import org.traccar.model.Server; +import org.traccar.model.User; +import org.traccar.notification.TextTemplateFormatter; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class TaskExpirations implements ScheduleTask { + + private static final Logger LOGGER = LoggerFactory.getLogger(TaskExpirations.class); + + private static final long CHECK_PERIOD_HOURS = 1; + + private final Config config; + private final Storage storage; + private final TextTemplateFormatter textTemplateFormatter; + private final MailManager mailManager; + + @Inject + public TaskExpirations( + Config config, Storage storage, TextTemplateFormatter textTemplateFormatter, MailManager mailManager) { + this.config = config; + this.storage = storage; + this.textTemplateFormatter = textTemplateFormatter; + this.mailManager = mailManager; + } + + @Override + public void schedule(ScheduledExecutorService executor) { + executor.scheduleAtFixedRate(this, CHECK_PERIOD_HOURS, CHECK_PERIOD_HOURS, TimeUnit.HOURS); + } + + private boolean checkTimeTrigger(Disableable disableable, long currentTime, long offsetTime) { + if (disableable.getExpirationTime() != null) { + long previousTime = currentTime - TimeUnit.HOURS.toMillis(CHECK_PERIOD_HOURS); + long expirationTime = disableable.getExpirationTime().getTime() + offsetTime; + return previousTime < expirationTime && currentTime >= expirationTime; + } + return false; + } + + private void sendUserExpiration( + Server server, User user, String template) throws MessagingException { + var velocityContext = textTemplateFormatter.prepareContext(server, user); + velocityContext.put("expiration", user.getExpirationTime()); + var fullMessage = textTemplateFormatter.formatMessage(velocityContext, template, "full"); + mailManager.sendMessage(user, true, fullMessage.getSubject(), fullMessage.getBody()); + } + + private void sendDeviceExpiration( + Server server, Device device, String template) throws MessagingException, StorageException { + var users = storage.getObjects(User.class, new Request( + new Columns.All(), new Condition.Permission(User.class, Device.class, device.getId()))); + for (User user : users) { + var velocityContext = textTemplateFormatter.prepareContext(server, user); + velocityContext.put("expiration", device.getExpirationTime()); + velocityContext.put("device", device); + var fullMessage = textTemplateFormatter.formatMessage(velocityContext, template, "full"); + mailManager.sendMessage(user, true, fullMessage.getSubject(), fullMessage.getBody()); + } + } + + @Override + public void run() { + try { + + long currentTime = System.currentTimeMillis(); + Server server = storage.getObject(Server.class, new Request(new Columns.All())); + + if (config.getBoolean(Keys.NOTIFICATION_EXPIRATION_USER)) { + long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_USER_REMINDER); + var users = storage.getObjects(User.class, new Request(new Columns.All())); + for (User user : users) { + if (checkTimeTrigger(user, currentTime, 0)) { + sendUserExpiration(server, user, "userExpiration"); + } else if (reminder > 0 && checkTimeTrigger(user, currentTime, -reminder)) { + sendUserExpiration(server, user, "userExpirationReminder"); + } + } + } + + if (config.getBoolean(Keys.NOTIFICATION_EXPIRATION_DEVICE)) { + long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_USER_REMINDER); + var devices = storage.getObjects(Device.class, new Request(new Columns.All())); + for (Device device : devices) { + if (checkTimeTrigger(device, currentTime, 0)) { + sendDeviceExpiration(server, device, "deviceExpiration"); + } else if (reminder > 0 && checkTimeTrigger(device, currentTime, -reminder)) { + sendDeviceExpiration(server, device, "deviceExpirationReminder"); + } + } + } + + } catch (StorageException | MessagingException e) { + LOGGER.warn("Failed to check expirations", e); + } + } + +} -- cgit v1.2.3