Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
</plugin>
</plugins>
</build>


<dependencies>
<dependency>
<groupId>net.java.dev.jna</groupId>
Expand Down Expand Up @@ -206,11 +208,38 @@
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.mockito</groupId>-->
<!-- <artifactId>mockito-core</artifactId>-->
<!-- <version>4.11.0</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.0</version>
<scope>compile</scope>
<version>4.11.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<!-- Byte Buddy for mockito-inline 4.11.0 -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.11</version>
<scope>test</scope>
</dependency>
<!-- Byte Buddy agent for static mocking -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.14.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand All @@ -236,11 +265,7 @@
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.4</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
Expand Down Expand Up @@ -317,5 +342,16 @@
<artifactId>braintree-java</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.9.3</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.33</version> <!-- Use a stable version -->
</dependency>
</dependencies>
</project>
21 changes: 21 additions & 0 deletions src/main/Activity/Activity.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@

public class Activity implements Comparable<Activity> {
private ObjectId id;
private LocalDateTime notifiedAt;


// add notification
@BsonProperty(value = "notified")
private boolean notified = false;

@BsonProperty(value = "occurredAt")
private LocalDateTime occurredAt;
Expand Down Expand Up @@ -64,6 +70,13 @@ public String getUsername() {
public ObjectId getId() {
return id;
}
public LocalDateTime getNotifiedAt() {
return notifiedAt;
}

public void setNotifiedAt(LocalDateTime notifiedAt) {
this.notifiedAt = notifiedAt;
}

public Activity setUsername(String username) {
this.username = username;
Expand All @@ -74,6 +87,14 @@ public void setId(ObjectId id) {
this.id = id;
}

public boolean isNotified() {
return notified;
}

public void setNotified(boolean notified) {
this.notified = notified;
}

// default sort is by occurred at, and then by username
private Comparator<Activity> getComparator() {
return Comparator.comparing(Activity::getOccurredAt)
Expand Down
3 changes: 3 additions & 0 deletions src/main/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ public static void main(String[] args) {
AppConfig.appFactory(DeploymentLevel.STAGING);
}
}



42 changes: 39 additions & 3 deletions src/main/Config/AppConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import Billing.BillingController;
import Database.Activity.ActivityDao;
import Database.Activity.ActivityDaoFactory;
import Database.Activity.ActivityDaoImpl;
import Database.File.FileDao;
import Database.File.FileDaoFactory;
import Database.Form.FormDao;
Expand All @@ -24,6 +25,7 @@
import Issue.IssueController;
import Mail.FileBackfillController;
import Mail.MailController;
import Mail.ScheduledEmailDispatcher;
import OptionalUserInformation.OptionalUserInformationController;
import Organization.Organization;
import Organization.OrganizationController;
Expand All @@ -40,10 +42,21 @@
import com.mongodb.client.MongoDatabase;
import io.javalin.Javalin;
import io.javalin.http.HttpResponseException;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import lombok.SneakyThrows;
import org.bson.types.ObjectId;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class AppConfig {
public static Long ASYNC_TIME_OUT = 10L;
Expand All @@ -52,17 +65,28 @@ public class AppConfig {

@SneakyThrows
public static Javalin appFactory(DeploymentLevel deploymentLevel) {
System.setProperty("logback.configurationFile", "../Logger/Resources/logback.xml");
System.setProperty("logback.configurationFile", "../Logger/Resources/logback.xml");
MongoConfig.getMongoClient();
ActivityDao activityDao = ActivityDaoFactory.create(deploymentLevel);
ScheduledEmailDispatcher dispatcher = new ScheduledEmailDispatcher(activityDao);
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(
dispatcher::dispatchDailyReminders,
computeInitialDelayTo8AM(),
TimeUnit.DAYS.toSeconds(1),
TimeUnit.SECONDS
);
// create app
Javalin app = AppConfig.createJavalinApp(deploymentLevel);
MongoConfig.getMongoClient();
// Continue loading other DAOs
UserDao userDao = UserDaoFactory.create(deploymentLevel);
OptionalUserInformationDao optionalUserInformationDao =
OptionalUserInformationDaoFactory.create(deploymentLevel);
TokenDao tokenDao = TokenDaoFactory.create(deploymentLevel);
OrgDao orgDao = OrgDaoFactory.create(deploymentLevel);
FormDao formDao = FormDaoFactory.create(deploymentLevel);
FileDao fileDao = FileDaoFactory.create(deploymentLevel);
ActivityDao activityDao = ActivityDaoFactory.create(deploymentLevel);
//ActivityDao activityDao = ActivityDaoFactory.create(deploymentLevel);
MailDao mailDao = MailDaoFactory.create(deploymentLevel);
MongoDatabase db = MongoConfig.getDatabase(deploymentLevel);
setApplicationHeaders(app);
Expand Down Expand Up @@ -283,6 +307,18 @@ public static Javalin appFactory(DeploymentLevel deploymentLevel) {
app.post("/submit-mail", mailController.saveMail);
return app;
}
private static long computeInitialDelayTo8AM() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextRun = now.withHour(8).withMinute(0).withSecond(0).withNano(0);

if (now.isAfter(nextRun)) {
// if now the time is over 8:00 am, schedule to tomorrow
nextRun = nextRun.plusDays(1);
}

Duration delay = Duration.between(now, nextRun);
return delay.getSeconds(); // return as second
}

public static void setApplicationHeaders(Javalin app) {
app.before(
Expand Down
7 changes: 7 additions & 0 deletions src/main/Database/Activity/ActivityDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ public interface ActivityDao extends Dao<Activity> {

List<Activity> getAllFromUserBetweenInclusive(
String username, LocalDateTime startTime, LocalDateTime endTime);
// New: Find up to N unnotified activities (for email reminders)
List<Activity> findUnnotified(int limit);

// New: Update an activity (to mark as notified)
void update(Activity activity);
List<Activity> getUnnotifiedActivities();
}

25 changes: 25 additions & 0 deletions src/main/Database/Activity/ActivityDaoImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import Activity.Activity;
import Config.DeploymentLevel;
import Config.MongoConfig;
import Mail.EmailNotifier;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.ArrayList;
Expand All @@ -16,6 +19,7 @@

import static com.mongodb.client.model.Filters.eq;

@Repository
public class ActivityDaoImpl implements ActivityDao {
private final MongoCollection<Activity> activityCollection;

Expand Down Expand Up @@ -57,6 +61,15 @@ public List<Activity> getAll() {
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
}
//
// @Override
public List<Activity> getUnnotifiedActivities() {
return activityCollection.find(eq("notified", false))
.into(new ArrayList<>()).stream()
.sorted(Comparator.reverseOrder()) // optional
.collect(Collectors.toList());
}


@Override
public int size() {
Expand All @@ -80,6 +93,18 @@ public void update(Activity activity) {

@Override
public void save(Activity activity) {

activityCollection.insertOne(activity);
// Trigger email notifications
EmailNotifier.handle(activity);;
}
@Override
public List<Activity> findUnnotified(int limit) {
return activityCollection.find(eq("notified", false))
.limit(limit)
.into(new ArrayList<>()).stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
}

}
10 changes: 10 additions & 0 deletions src/main/Database/Activity/ActivityDaoTestImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,14 @@ public void update(Activity activity) {
public void save(Activity activity) {
activityMap.put(activity.getId(), activity);
}

@Override
public List<Activity> findUnnotified(int limit) {
return new ArrayList<>();
}
@Override
public List<Activity> getUnnotifiedActivities() {
return Collections.emptyList();
}

}
45 changes: 45 additions & 0 deletions src/main/Mail/EmailNotifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package Mail;

import Activity.Activity;
import Mail.Services.SendgridService;

import java.util.List;

public class EmailNotifier {

public static void handle(Activity activity) {
List<String> types = activity.getType();
if (types == null || types.isEmpty()) return;

String type = types.get(types.size() - 1); // get the most specific activity type
// System.out.println("Handling activity type: " + type + " for user: " + activity.getUsername());
switch (type) {
case "CreateClientActivity":
SendgridService.handleCreateClientActivity(activity.getUsername(), "PA"); // placeholder
break;
case "UploadFileActivity":
SendgridService.handleUploadFileActivity(activity.getUsername(), "DocumentType"); // placeholder
break;
case "StartApplicationActivity":
SendgridService.handleMailApplicationActivity(activity.getUsername());
break;
case "MailApplicationActivity":
SendgridService.handleSubmitApplicationActivity(activity.getUsername(), "NonprofitName"); // placeholder
break;
// case "SubmitApplicationActivity":
// SendgridService.sendSubmissionConfirmation(user, "ApplicationName");
// break;
// case "RecoverPasswordActivity":
// SendgridService.sendPasswordRecoveryAlert(user);
// break;
// case "ChangePasswordActivity":
// SendgridService.sendPasswordChangeConfirmation(user);
// break;
// case "Change2FAActivity":
// SendgridService.send2FAUpdateNotice(user, activity.getObjectName()); // "on"/"off"
// break;
default:
// Optional: log unknown type
}
}
}
30 changes: 30 additions & 0 deletions src/main/Mail/ScheduledEmailDispatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package Mail;

import Activity.Activity;
import Database.Activity.ActivityDao;

import java.time.LocalDateTime;
import java.util.List;

public class ScheduledEmailDispatcher {
private final ActivityDao activityDao;

public ScheduledEmailDispatcher(ActivityDao activityDao) {
this.activityDao = activityDao;
}

public void dispatchDailyReminders() {
System.out.println("Running scheduled reminder task: " + LocalDateTime.now());

List<Activity> unnotified = activityDao.getUnnotifiedActivities();
for (Activity activity : unnotified) {
//
EmailNotifier.handle(activity);

activity.setNotified(true);
activity.setNotifiedAt(LocalDateTime.now());
activityDao.update(activity);
}
}
}

Loading