Skip to content

Commit

Permalink
Merge branch 'feature/user-inactivity'
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Dec 19, 2024
2 parents 70c93b6 + b9ae6e3 commit d601abd
Show file tree
Hide file tree
Showing 18 changed files with 395 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
distribution: 'temurin'
cache: 'maven'
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
Expand Down
94 changes: 91 additions & 3 deletions myconext-server/src/main/java/myconext/cron/InactivityMail.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,96 @@
package myconext.cron;

import myconext.mail.MailBox;
import myconext.model.User;
import myconext.model.UserInactivity;
import myconext.repository.UserRepository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class InactivityMail {

//TODO, keep track of the mails send bu using an enum indicating the level of inactivity
//In the variables for the mail separate the NL variable and EN variable and use
//different variables in the mail templates
private static final Log LOG = LogFactory.getLog(InactivityMail.class);

private final MailBox mailBox;
private final UserRepository userRepository;
private final boolean mailInactivityMails;
private final boolean cronJobResponsible;
private final DateFormat dateFormatUS;
private final DateFormat dateFormatNL;

@Autowired
public InactivityMail(MailBox mailBox,
UserRepository userRepository,
@Value("${cron.node-cron-job-responsible}") boolean cronJobResponsible,
@Value("${feature.mail_inactivity_mails}") boolean mailInactivityMails) {
this.mailBox = mailBox;
this.userRepository = userRepository;
this.cronJobResponsible = cronJobResponsible;
this.mailInactivityMails = mailInactivityMails;
this.dateFormatUS = DateFormat.getDateInstance(DateFormat.LONG, Locale.of("us"));
this.dateFormatNL = DateFormat.getDateInstance(DateFormat.LONG, Locale.of("nl"));
}

@Scheduled(cron = "${cron.inactivity-users-expression}")
@SuppressWarnings("unchecked")
public void mailInactiveUsers() {
if (!mailInactivityMails || !cronJobResponsible) {
return;
}
try {
Stream.of(UserInactivity.values()).forEach(this::doMailInactiveUsers);
this.doDeleteInactiveUsers();
} catch (Exception e) {
//swallow exception as the scheduling stops then
LOG.error("Error in mailInactiveUsers", e);
}
}

private void doMailInactiveUsers(UserInactivity userInactivity) {
long nowInMillis = System.currentTimeMillis();
long oneDayInMillis = 24 * 60 * 60 * 1000L;
long fiveYearsInMillis = 5 * 365 * oneDayInMillis;

long lastLoginBefore = nowInMillis - (oneDayInMillis * userInactivity.getInactivityDays());
List<User> users = userRepository.findByLastLoginBeforeAndUserInactivity(lastLoginBefore, userInactivity.getPreviousUserInactivity());

Map<String, String> localeVariables = new HashMap<>();
//This is the future date when the user will be deleted based on the inactivityDays of the userInactivity
Date date = new Date(nowInMillis + (fiveYearsInMillis - (oneDayInMillis * userInactivity.getInactivityDays())));
localeVariables.put("inactivity_period_en", userInactivity.getInactivityPeriodEn());
localeVariables.put("inactivity_period_nl", userInactivity.getInactivityPeriodNl());
localeVariables.put("deletion_period_en", userInactivity.getDeletionPeriodEn());
localeVariables.put("deletion_period_nl", userInactivity.getDeletionPeriodNl());
localeVariables.put("account_delete_date_en", dateFormatUS.format(date));
localeVariables.put("account_delete_date_nl", dateFormatNL.format(date));
users.forEach(user -> {
mailBox.sendUserInactivityMail(user, localeVariables, true);
user.setUserInactivity(userInactivity);
userRepository.save(user);
});
LOG.info(String.format("Mailed %s users who has been inactive for %s period in for %s ms",
users.size(), userInactivity, System.currentTimeMillis() - nowInMillis));
}

private void doDeleteInactiveUsers() {
long nowInMillis = System.currentTimeMillis();
long oneDayInMillis = 24 * 60 * 60 * 1000L;

long lastLoginBefore = nowInMillis - (oneDayInMillis * 5L * 365);
List<User> users = userRepository.findByLastLoginBeforeAndUserInactivity(lastLoginBefore, UserInactivity.WEEK_1_BEFORE_5_YEARS);
userRepository.deleteAll(users);
LOG.info(String.format("Deleted %s users (%s) who has been inactive for 5 years in for %s ms",
users.size(), users.stream().map(User::getEmail).collect(Collectors.joining(", ")),
System.currentTimeMillis() - nowInMillis));
}
}
9 changes: 9 additions & 0 deletions myconext-server/src/main/java/myconext/mail/MailBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ public void sendInstitutionMailWarning(User user) {
sendMail("institution_mail_warning", title, variables, preferredLanguage(user), user.getEmail(), false);
}

public void sendUserInactivityMail(User user, Map<String, String> localeVariables, boolean firstTwoWarnings) {
String title = this.getTitle(firstTwoWarnings ? "inactivity_warning_years_ahead" : "inactivity_warning_short_term", user);
Map<String, Object> variables = variables(user, title);
variables.put("mySurfConextURL", mySURFconextURL);
variables.putAll(localeVariables);
String templateName = firstTwoWarnings ? "inactivity_warning_years_ahead" : "inactivity_warning_short_term";
sendMail(templateName, title, variables, preferredLanguage(user), user.getEmail(), false);
}

public void sendNudgeAppMail(User user) {
String title = this.getTitle("nudge_eduid_app", user);
Map<String, Object> variables = variables(user, title);
Expand Down
4 changes: 4 additions & 0 deletions myconext-server/src/main/java/myconext/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public class User implements Serializable, UserDetails {
private List<EduID> eduIDS = new ArrayList<>();

private long created;
@Setter
private long lastLogin;

@Setter
Expand All @@ -97,6 +98,9 @@ public class User implements Serializable, UserDetails {
@Setter
private boolean mobileAuthentication;

@Setter
private UserInactivity userInactivity;

public User(CreateInstitutionEduID createInstitutionEduID, Map<String, Object> userInfo) {
this.email = createInstitutionEduID.getEmail();
this.chosenName = (String) userInfo.get("given_name");
Expand Down
58 changes: 58 additions & 0 deletions myconext-server/src/main/java/myconext/model/UserInactivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package myconext.model;

import lombok.Getter;

@Getter
public enum UserInactivity {

// 1 year (= 4 year before the 5-year mark)
YEAR_1_INTERVAL(365L,
null,
"1 year",
"1 jaar",
"4 years",
"4 jaren"),
// 3 year (= 3 year before the 5-year mark)
YEAR_3_INTERVAL(2L * 365,
YEAR_1_INTERVAL,
"2 years",
"2 jaren",
"3 years",
"3 jaren"),
// 1 month before the 5-year mark
MONTH_1_BEFORE_5_YEARS((4L * 365) + (11L * 30),
YEAR_3_INTERVAL,
"almost 5 years",
"bijna 5 jaar",
"1 month",
"1 maand"),
// 1 week before the-5 year mark
WEEK_1_BEFORE_5_YEARS((4L * 365) + (51L * 7),
MONTH_1_BEFORE_5_YEARS,
"almost 5 years",
"bijna 5 jaar",
"1 week",
"1 week");

private final long inactivityDays;
private final UserInactivity previousUserInactivity;
private final String inactivityPeriodEn;
private final String inactivityPeriodNl;
private final String deletionPeriodEn;
private final String deletionPeriodNl;

UserInactivity(long inactivityDays,
UserInactivity previousUserInactivity,
String inactivityPeriodEn,
String inactivityPeriodNl,
String deletionPeriodEn,
String deletionPeriodNl) {
this.inactivityDays = inactivityDays;
this.previousUserInactivity = previousUserInactivity;
this.inactivityPeriodEn = inactivityPeriodEn;
this.inactivityPeriodNl = inactivityPeriodNl;
this.deletionPeriodEn = deletionPeriodEn;
this.deletionPeriodNl = deletionPeriodNl;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import myconext.model.User;
import myconext.model.UserInactivity;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
Expand Down Expand Up @@ -53,6 +54,7 @@ public interface UserRepository extends MongoRepository<User, String> {
@Query("{'email' : {$regex : ?0, $options: 'i'}}")
List<User> findByEmailDomain(String regex);

List<User> findByLastLoginBeforeAndUserInactivity(long lastLoginBefore, UserInactivity userInactivity);

@Query("""
{ $and: [ { $or: [
Expand Down
4 changes: 4 additions & 0 deletions myconext-server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ cron:
nudge-app-mail-expression: "0 30 6 * * ?"
# Number of days after creation of the eduID account which the nudge mail is send
nudge-app-mail-days-after-creation: 14
# Every day at 7:30AM
inactivity-users-expression: "0 30 7 * * ?"

manage:
username: myconext
Expand Down Expand Up @@ -105,6 +107,8 @@ feature:
remote_creation_api: True
# Do we periodically mail users who have used their institution account
mail_institution_mail_usage: True
# Do we periodically mail users who are inactive and might have their account deleted
mail_inactivity_mails: True
# Do we mail users who have not installed the eduID app
nudge_app_mail: True
secure_cookie: false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<div class="main" style="font-family: Helvetica, Arial, sans-serif;line-height: 18px;font-size: 14px;color: #353535;">
<div class="head" style="background-color: #f5f5f5;padding: 30px 0 20px 60px;">
<p class="title" style="font-weight: 600;font-size: 38px;line-height: 38px; margin: 0;margin-bottom: 10px">You
have not used your eidUD account for {{inactivity_period_en}}</p>
</div>
<div class="middle" style="padding: 20px 0 40px 60px;max-width: 600px;">
<p>Hi <strong>{{name}}</strong>,</p>
<p>You have not used your eduID account for {{inactivity_period_en}}. If you remain inactive than your eduID
account will be deleted within {{deletion_period_en}}, on {{account_delete_date_en}}.
Go to your <a href="{{mySurfConextURL}}">eduID account</a> and login to keep your account active.</p>
<br/>
<p>
Kind regards,
eduID.
</p>
</div>
{{> footer_nl.html}}
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi {{name}},

You have not used your eduID account for {{inactivity_period_en}}. If you remain inactive than your eduID
account will be deleted within {{deletion_period_en}}, on {{account_delete_date_en}}.
Go to your {{mySurfConextURL}} and login to keep your account active.

{{> footer_en.txt}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<div class="main" style="font-family: Helvetica, Arial, sans-serif;line-height: 18px;font-size: 14px;color: #353535;">
<div class="head" style="background-color: #f5f5f5;padding: 30px 0 20px 60px;">
<p class="title" style="font-weight: 600;font-size: 38px;line-height: 38px; margin: 0;margin-bottom: 10px">You
have created an eduID with your institutional account</p>
</div>
<div class="middle" style="padding: 20px 0 40px 60px;max-width: 600px;">
<p>Hi <strong>{{name}}</strong>,</p>

<p>Je hebt je eduID account niet gebruikt in {{inactivity_period_nl}}. Als je je eduID account niet gebruikt dan
zullen we deze verwijderen in {{deletion_period_nl}}, op {{account_delete_date_nl}}.
Ga naar <a href="{{mySurfConextURL}}">je eduID account </a> en login om je account actief te houden.</p>
<br/>
<p>
Vriendelijke groeten,
eduID.
</p>
</div>
{{> footer_nl.html}}
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi {{name}},

Je hebt je eduID account niet gebruikt in {{inactivity_period_nl}}. Als je je eduID account niet gebruikt dan zullen we
deze verwijderen in {{deletion_period_nl}}, op {{account_delete_date_nl}}.
Ga naar {{mySurfConextURL}} en login om je account actief te houden.

{{> footer_en.txt}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<div class="main" style="font-family: Helvetica, Arial, sans-serif;line-height: 18px;font-size: 14px;color: #353535;">
<div class="head" style="background-color: #f5f5f5;padding: 30px 0 20px 60px;">
<p class="title" style="font-weight: 600;font-size: 38px;line-height: 38px; margin: 0;margin-bottom: 10px">You
have not used your eidUD account for {{inactivity_period_en}}</p>
</div>
<div class="middle" style="padding: 20px 0 40px 60px;max-width: 600px;">
<p>Hi <strong>{{name}}</strong>,</p>
<p>You have not used your eduID account for {{inactivity_period_en}}. If you remain inactive than your eduID
account will be deleted within {{deletion_period_en}}, on {{account_delete_date_en}}.
Go to your <a href="{{mySurfConextURL}}">eduID account</a> and login to keep your account active.</p>
<br/>
<p>
Kind regards,
eduID.
</p>
</div>
{{> footer_nl.html}}
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi {{name}},

You have not used your eduID account for {{inactivity_period_en}}. If you remain inactive than your eduID
account will be deleted within {{deletion_period_en}}, on {{account_delete_date_en}}.
Go to your {{mySurfConextURL}} and login to keep your account active.

{{> footer_en.txt}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<div class="main" style="font-family: Helvetica, Arial, sans-serif;line-height: 18px;font-size: 14px;color: #353535;">
<div class="head" style="background-color: #f5f5f5;padding: 30px 0 20px 60px;">
<p class="title" style="font-weight: 600;font-size: 38px;line-height: 38px; margin: 0;margin-bottom: 10px">You
have created an eduID with your institutional account</p>
</div>
<div class="middle" style="padding: 20px 0 40px 60px;max-width: 600px;">
<p>Hi <strong>{{name}}</strong>,</p>

<p>Je hebt je eduID account niet gebruikt in {{inactivity_period_nl}}. Als je je eduID account niet gebruikt dan
zullen we deze verwijderen in {{deletion_period_nl}}, op {{account_delete_date_nl}}.
Ga naar <a href="{{mySurfConextURL}}">je eduID account </a> en login om je account actief te houden.</p>
<br/>
<p>
Vriendelijke groeten,
eduID.
</p>
</div>
{{> footer_nl.html}}
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi {{name}},

Je hebt je eduID account niet gebruikt in {{inactivity_period_nl}}. Als je je eduID account niet gebruikt dan zullen we
deze verwijderen in {{deletion_period_nl}}, op {{account_delete_date_nl}}.
Ga naar {{mySurfConextURL}} en login om je account actief te houden.

{{> footer_en.txt}}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
"nudge_eduid_app": {
"en": "Installeer de eduID app",
"nl": "Install the eduID app"
},
"inactivity_warning_years_ahead": {
"en": "Inactivity warning",
"nl": "Inactiviteit waarschuwing"
},
"inactivity_warning_short_term": {
"en": "Inactivity warning",
"nl": "Inactiviteit waarschuwing"
}

}
Loading

0 comments on commit d601abd

Please sign in to comment.