diff --git a/pom.xml b/pom.xml
index 18c01ed6f..f038fe136 100644
--- a/pom.xml
+++ b/pom.xml
@@ -232,13 +232,6 @@
spring-boot-starter-logging
-
-
- jakarta.mail
- jakarta.mail-api
- 2.1.3
-
-
tech.tablesaw
diff --git a/src/main/java/com/open/spring/mvc/comment/CommentApiController.java b/src/main/java/com/open/spring/mvc/comment/CommentApiController.java
index ffc38f9f2..56ccec6ed 100644
--- a/src/main/java/com/open/spring/mvc/comment/CommentApiController.java
+++ b/src/main/java/com/open/spring/mvc/comment/CommentApiController.java
@@ -1,6 +1,10 @@
package com.open.spring.mvc.comment;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -11,12 +15,27 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.CrossOrigin;
+
+import com.open.spring.mvc.slack.CalendarIssueService;
+import com.open.spring.mvc.slack.EmailNotificationService;
+
@RestController
@RequestMapping("/api/Comment")
+@CrossOrigin(origins = { "http://127.0.0.1:4500", "https://pages.opencodingsociety.com" }, allowCredentials = "true")
public class CommentApiController {
+ @Autowired
private final CommentJPA CommentJPA;
+ @Autowired
+ private CalendarIssueService calendarIssueService;
+
+ @Autowired
+ private EmailNotificationService emailNotificationService;
+
// Constructor injection for CommentJPA
public CommentApiController(CommentJPA CommentJPA) {
this.CommentJPA = CommentJPA;
@@ -86,4 +105,103 @@ public ResponseEntity> getCommentsByAuthor(@RequestParam String au
return new ResponseEntity<>(comments, HttpStatus.OK); // Return 200 with the list of comments
}
+
+ @GetMapping("/issue/{issueId}")
+ public ResponseEntity> getCommentsByIssue(@PathVariable Long issueId,
+ @AuthenticationPrincipal UserDetails userDetails) {
+ if (userDetails == null) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ }
+
+ List comments = CommentJPA.findByAssignmentOrderByTimestampDesc(issueAssignmentKey(issueId));
+ return new ResponseEntity<>(comments, HttpStatus.OK);
+ }
+
+ @PostMapping("/issue/{issueId}")
+ public ResponseEntity> createIssueComment(@PathVariable Long issueId,
+ @RequestBody Map payload,
+ @AuthenticationPrincipal UserDetails userDetails) {
+ if (userDetails == null) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+ .body(Map.of("message", "Authentication required"));
+ }
+
+ String text = payload.get("text") == null ? "" : String.valueOf(payload.get("text")).trim();
+ if (text.isEmpty()) {
+ return ResponseEntity.badRequest().body(Map.of("message", "Comment text is required"));
+ }
+
+ return calendarIssueService.getIssueById(issueId, userDetails.getUsername(), hasPrivilegedRole(userDetails))
+ .>map(issue -> {
+ String authorUid = userDetails.getUsername();
+ Comment savedComment = CommentJPA.save(new Comment(issueAssignmentKey(issueId), text, authorUid));
+ emailNotificationService.notifyOnIssueComment(issue, savedComment);
+ emailNotificationService.notifyAllStarredIssueFollowers(issue, savedComment);
+
+ Map response = new HashMap<>();
+ response.put("comment", savedComment);
+ response.put("commentCount", CommentJPA.countByAssignment(issueAssignmentKey(issueId)));
+ return ResponseEntity.status(HttpStatus.CREATED).body(response);
+ })
+ .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .body(Map.of("message", "Issue not found")));
+ }
+
+ @PostMapping("/issue/{issueId}/star")
+ public ResponseEntity> toggleIssueStar(@PathVariable Long issueId,
+ @AuthenticationPrincipal UserDetails userDetails) {
+ if (userDetails == null) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+ .body(Map.of("message", "Authentication required"));
+ }
+
+ return calendarIssueService.getIssueById(issueId, userDetails.getUsername(), hasPrivilegedRole(userDetails))
+ .>map(issue -> {
+ String starKey = issueStarAssignmentKey(issueId);
+ String authorUid = userDetails.getUsername();
+ boolean starred = false;
+
+ // Find existing star comments for this assignment with text "star"
+ List existingStars = CommentJPA.findByAssignmentAndText(starKey, "star");
+ Comment toRemove = null;
+ if (existingStars != null) {
+ for (Comment c : existingStars) {
+ if (authorUid.equals(c.getAuthor())) {
+ toRemove = c;
+ break;
+ }
+ }
+ }
+
+ if (toRemove != null) {
+ CommentJPA.delete(toRemove);
+ starred = false;
+ } else {
+ CommentJPA.save(new Comment(starKey, "star", authorUid));
+ starred = true;
+ }
+
+ Map response = new HashMap<>();
+ response.put("starred", starred);
+ response.put("starCount", CommentJPA.countByAssignment(starKey));
+ response.put("issueId", issueId);
+ return ResponseEntity.ok(response);
+ })
+ .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .body(Map.of("message", "Issue not found")));
+ }
+
+ private String issueAssignmentKey(Long issueId) {
+ return "issue-" + issueId;
+ }
+
+ private String issueStarAssignmentKey(Long issueId) {
+ return issueAssignmentKey(issueId) + "::star";
+ }
+
+ private boolean hasPrivilegedRole(UserDetails userDetails) {
+ return userDetails.getAuthorities().stream()
+ .map(authority -> authority.getAuthority())
+ .anyMatch(role -> "ROLE_ADMIN".equals(role) || "ROLE_TEACHER".equals(role));
+ }
}
diff --git a/src/main/java/com/open/spring/mvc/comment/CommentJPA.java b/src/main/java/com/open/spring/mvc/comment/CommentJPA.java
index 67600c76e..570ef9a80 100644
--- a/src/main/java/com/open/spring/mvc/comment/CommentJPA.java
+++ b/src/main/java/com/open/spring/mvc/comment/CommentJPA.java
@@ -8,5 +8,15 @@ public interface CommentJPA extends JpaRepository {
List findByAssignment(String assignment);
+ List findByAssignmentOrderByTimestampDesc(String assignment);
+
List findAllByOrderByTimestampDesc();
+
+ long countByAssignment(String assignment);
+
+ boolean existsByAssignmentAndAuthor(String assignment, String author);
+
+ void deleteByAssignmentAndAuthor(String assignment, String author);
+
+ List findByAssignmentAndText(String assignment, String text);
}
diff --git a/src/main/java/com/open/spring/mvc/person/Email/Email.java b/src/main/java/com/open/spring/mvc/person/Email/Email.java
index a4c6d78f0..922f2e930 100644
--- a/src/main/java/com/open/spring/mvc/person/Email/Email.java
+++ b/src/main/java/com/open/spring/mvc/person/Email/Email.java
@@ -3,138 +3,296 @@
// Java program to send email
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Locale;
import java.util.Properties;
-import jakarta.mail.Authenticator;
-import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
-import jakarta.mail.PasswordAuthentication;
-import jakarta.mail.Session;
-import jakarta.mail.Transport;
-import jakarta.mail.internet.InternetAddress;
-import jakarta.mail.internet.MimeBodyPart;
-import jakarta.mail.internet.MimeMessage;
-import jakarta.mail.internet.MimeMultipart;
-
+import org.json.JSONObject;
//dot env for email username/password
import io.github.cdimascio.dotenv.Dotenv;
public class Email
{
-
- public static void sendEmail(String recipient, String subject, Multipart multipart){
- // email ID of Recipient.
-
- // email ID of Sender.
- String sender = "sender@gmail.com";
-
- // Getting system properties
- Properties properties = System.getProperties();
-
- // Setting up mail server
- properties.put("mail.smtp.auth", "true");
- properties.put("mail.smtp.starttls.enable", "true");
- properties.put("mail.smtp.host", "smtp.gmail.com");
- properties.put("mail.smtp.port", 587);
- properties.put("mail.smtp.ssl.protocols", "TLSv1.2");
-
- // creating session object to get properties
-
- final Dotenv dotenv = Dotenv.load();
- final String emailUsername = dotenv.get("EMAIL_USERNAME");
- final String emailPassword = dotenv.get("EMAIL_PASSWORD");
- Session session = Session.getDefaultInstance(properties,new Authenticator() {
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(emailUsername,emailPassword); // email and password, see this for app passwords https://support.google.com/accounts/answer/185833?visit_id=638748419667916449-2613033234&p=InvalidSecondFactor&rd=1
- }
- });
-
- try
- {
- // MimeMessage object.
- MimeMessage message = new MimeMessage(session);
-
- // Set From Field: adding senders email to from field.
- message.setFrom(new InternetAddress(sender));
-
- // Set To Field: adding recipient's email to from field.
- message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient));
-
- // Set Subject: subject of the email
- message.setSubject(subject);
-
- // SetContent: content (Multipart) of the email
- message.setContent(multipart);
-
- // Send email.
- Transport.send(message);
- System.out.println("Mail successfully sent");
- }
- catch (MessagingException mex)
- {
- mex.printStackTrace();
- }
+ private static final Properties APPLICATION_PROPERTIES = loadApplicationProperties();
+ private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(10))
+ .build();
+
+ private static Properties loadApplicationProperties() {
+ Properties props = new Properties();
+ try (InputStream input = Email.class.getClassLoader().getResourceAsStream("application.properties")) {
+ if (input != null) {
+ props.load(input);
+ }
+ } catch (IOException e) {
+ // Fall back to env/system properties if classpath properties cannot be loaded.
+ }
+ return props;
}
- public static void sendEmail(String recipient, String subject, String content){
+ private static String resolveCredential(String key, String applicationKey) {
+ String value = System.getProperty(key);
+ if (value != null && !value.isBlank()) {
+ return value;
+ }
- try{
- MimeMultipart emailContent = new MimeMultipart();
- MimeBodyPart body1 = new MimeBodyPart();
- body1.setContent(""+content+"
","text/html");
+ value = System.getenv(key);
+ if (value != null && !value.isBlank()) {
+ return value;
+ }
- emailContent.addBodyPart(body1);
+ try {
+ final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
+ value = dotenv.get(key);
+ if (value != null && !value.isBlank()) {
+ return value;
+ }
+ } catch (Exception e) {
+ // Ignore and fall back to packaged properties.
+ }
- sendEmail(recipient, subject, emailContent);
+ value = APPLICATION_PROPERTIES.getProperty(key);
+ if (value != null && !value.isBlank()) {
+ return value;
}
- catch (MessagingException mex)
- {
- mex.printStackTrace();
- }
+
+ if (applicationKey != null && !applicationKey.isBlank()) {
+ value = APPLICATION_PROPERTIES.getProperty(applicationKey);
+ if (value != null && !value.isBlank()) {
+ return value;
+ }
+ }
+
+ return null;
}
- public static void sendPasswordResetEmail(String recipient,String code){
+ private static String resolveEmailProvider() {
+ String provider = resolveCredential("EMAIL_PROVIDER", "email.provider");
+ return provider == null || provider.isBlank() ? "formsubmit" : provider.trim().toLowerCase(Locale.ROOT);
+ }
- try{
- MimeMultipart emailContent = new MimeMultipart();
+ private static String sanitizeText(String input) {
+ if (input == null || input.isBlank()) {
+ return "";
+ }
+
+ return input.replace("\r\n", "\n")
+ .replace("\r", "\n")
+ .replaceAll("(?i)
", "\n")
+ .replaceAll("(?i)
", "\n\n")
+ .replaceAll("(?i)", "\n")
+ .replaceAll("<[^>]+>", "")
+ .replace(" ", " ")
+ .trim();
+ }
- MimeBodyPart body1 = new MimeBodyPart();
- body1.setContent("To reset your password use the following code:
","text/html");
- MimeBodyPart body2 = new MimeBodyPart();
- body2.setContent(""+code+"","text/html");
+ private static String multipartToText(Multipart multipart) throws MessagingException, IOException {
+ if (multipart == null) {
+ return "";
+ }
- emailContent.addBodyPart(body1);
- emailContent.addBodyPart(body2);
+ StringBuilder body = new StringBuilder();
+ for (int i = 0; i < multipart.getCount(); i++) {
+ Object content = multipart.getBodyPart(i).getContent();
+ if (content == null) {
+ continue;
+ }
+ if (body.length() > 0) {
+ body.append("\n");
+ }
+ body.append(sanitizeText(content.toString()));
+ }
+ return body.toString();
+ }
- sendEmail(recipient, "Password Reset", emailContent);
+ private static void addField(StringBuilder builder, String key, String value) {
+ if (value == null) {
+ return;
}
- catch (MessagingException mex)
- {
- mex.printStackTrace();
- }
+ if (builder.length() > 0) {
+ builder.append('&');
+ }
+ builder.append(URLEncoder.encode(key, StandardCharsets.UTF_8));
+ builder.append('=');
+ builder.append(URLEncoder.encode(value, StandardCharsets.UTF_8));
}
- public static void sendVerificationEmail(String recipient,String code){
+ private static String resolveFormSubmitEndpoint(String recipient) {
+ String configured = resolveCredential("FORM_SUBMIT_ENDPOINT", "formsubmit.endpoint");
+ if (configured == null || configured.isBlank()) {
+ configured = "https://formsubmit.co/ajax/{recipient}";
+ } else {
+ // Expand simple placeholder of form ${VAR:default} if present in properties
+ if (configured.startsWith("${") && configured.endsWith("}")) {
+ int colon = configured.indexOf(':', 2);
+ if (colon > 2) {
+ String varName = configured.substring(2, colon);
+ String defaultVal = configured.substring(colon + 1, configured.length() - 1);
+ String resolved = resolveCredential(varName, null);
+ if (resolved != null && !resolved.isBlank()) {
+ configured = resolved;
+ } else {
+ configured = defaultVal;
+ }
+ }
+ }
+ }
- try{
- MimeMultipart emailContent = new MimeMultipart();
+ String encodedRecipient = URLEncoder.encode(recipient, StandardCharsets.UTF_8);
+ if (configured.contains("{recipient}")) {
+ return configured.replace("{recipient}", encodedRecipient);
+ }
- MimeBodyPart body1 = new MimeBodyPart();
- body1.setContent("Thank you for signing up for DNHS Computer Science. Use the following code to verify your email:
","text/html");
- MimeBodyPart body2 = new MimeBodyPart();
- body2.setContent(""+code+"","text/html");
+ if (configured.endsWith("/")) {
+ return configured + encodedRecipient;
+ }
- emailContent.addBodyPart(body1);
- emailContent.addBodyPart(body2);
+ return configured + "/" + encodedRecipient;
+ }
- sendEmail(recipient, "Email Verification", emailContent);
+ private static void sendViaFormSubmit(String recipient, String subject, String body) throws IOException, InterruptedException {
+ if (recipient == null || recipient.isBlank()) {
+ throw new IllegalArgumentException("Recipient is required for FormSubmit delivery.");
}
- catch (MessagingException mex)
- {
- mex.printStackTrace();
- }
+
+ String endpoint = resolveFormSubmitEndpoint(recipient);
+ String sender = resolveCredential("EMAIL_USERNAME", "spring.mail.username");
+ String replyTo = resolveCredential("EMAIL_REPLY_TO", "email.replyTo");
+
+ StringBuilder formBody = new StringBuilder();
+ addField(formBody, "name", "Open Coding Society");
+ addField(formBody, "_subject", subject);
+ addField(formBody, "message", sanitizeText(body));
+ addField(formBody, "_captcha", "false");
+ addField(formBody, "_template", "table");
+ if (sender != null && !sender.isBlank()) {
+ addField(formBody, "email", sender);
+ addField(formBody, "_replyto", sender);
+ }
+ if (replyTo != null && !replyTo.isBlank()) {
+ addField(formBody, "_replyto", replyTo);
+ }
+
+ String origin = resolveCredential("FORM_SUBMIT_ORIGIN", "formsubmit.origin");
+ if (origin == null || origin.isBlank()) {
+ origin = "https://pages.opencodingsociety.com";
+ }
+ String referer = resolveCredential("FORM_SUBMIT_REFERER", "formsubmit.referer");
+ if (referer == null || referer.isBlank()) {
+ referer = origin + "/";
+ }
+
+ HttpRequest request = HttpRequest.newBuilder(URI.create(endpoint))
+ .timeout(Duration.ofSeconds(15))
+ .header("Accept", "application/json")
+ .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
+ .header("User-Agent", "open-coding-society-email-service")
+ .header("Origin", origin)
+ .header("Referer", referer)
+ .POST(HttpRequest.BodyPublishers.ofString(formBody.toString()))
+ .build();
+
+ System.out.println("[Email] FormSubmit POST -> " + endpoint + " (Origin: " + origin + ", Referer: " + referer + ")");
+ System.out.println("[Email] Form body: " + formBody.toString());
+
+ HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
+ System.out.println("[Email] FormSubmit response HTTP " + response.statusCode());
+ String responseBody = response.body();
+ System.out.println("[Email] FormSubmit response body: " + (responseBody == null ? "" : responseBody));
+
+ if (response.statusCode() < 200 || response.statusCode() >= 300) {
+ throw new IllegalStateException("FormSubmit returned HTTP " + response.statusCode() + ": " + response.body());
+ }
+
+ try {
+ JSONObject json = new JSONObject(responseBody == null ? "{}" : responseBody);
+ String success = json.optString("success", "").trim().toLowerCase(Locale.ROOT);
+ if ("false".equals(success)) {
+ String message = json.optString("message", "FormSubmit rejected the request.");
+ throw new IllegalStateException("FormSubmit rejected delivery: " + message);
+ }
+ } catch (org.json.JSONException parseError) {
+ // Accept non-JSON success responses from provider/CDN edge behavior.
+ }
+ }
+
+ private static void sendViaSmtp(String recipient, String subject, String body) {
+ String sender = resolveCredential("EMAIL_USERNAME", "spring.mail.username");
+ String password = resolveCredential("EMAIL_PASSWORD", "spring.mail.password");
+ String smtpHost = resolveCredential("EMAIL_SMTP_HOST", "spring.mail.host");
+ String smtpPort = resolveCredential("EMAIL_SMTP_PORT", "spring.mail.port");
+
+ if (sender == null || password == null) {
+ throw new IllegalStateException("Email credentials are not configured. Set EMAIL_USERNAME and EMAIL_PASSWORD or spring.mail.username/password.");
+ }
+
+ java.util.Properties properties = System.getProperties();
+ properties.put("mail.smtp.auth", "true");
+ properties.put("mail.smtp.starttls.enable", "true");
+ properties.put("mail.smtp.host", smtpHost != null && !smtpHost.isBlank() ? smtpHost : "smtp.gmail.com");
+ properties.put("mail.smtp.port", smtpPort != null && !smtpPort.isBlank() ? smtpPort : "587");
+ properties.put("mail.smtp.ssl.protocols", "TLSv1.2");
+
+ jakarta.mail.Session session = jakarta.mail.Session.getDefaultInstance(properties, new jakarta.mail.Authenticator() {
+ @Override
+ protected jakarta.mail.PasswordAuthentication getPasswordAuthentication() {
+ return new jakarta.mail.PasswordAuthentication(sender, password);
+ }
+ });
+
+ try {
+ jakarta.mail.internet.MimeMessage message = new jakarta.mail.internet.MimeMessage(session);
+ message.setFrom(new jakarta.mail.internet.InternetAddress(sender));
+ message.addRecipient(jakarta.mail.Message.RecipientType.TO, new jakarta.mail.internet.InternetAddress(recipient));
+ message.setSubject(subject);
+ message.setContent(body, "text/plain; charset=UTF-8");
+ jakarta.mail.Transport.send(message);
+ System.out.println("Mail successfully sent");
+ } catch (MessagingException mex) {
+ throw new IllegalStateException("SMTP delivery failed", mex);
+ }
+ }
+
+ public static void sendEmail(String recipient, String subject, Multipart multipart){
+ try {
+ sendEmail(recipient, subject, multipartToText(multipart));
+ } catch (MessagingException | IOException e) {
+ throw new IllegalStateException("Unable to prepare email content", e);
+ }
+ }
+
+ public static void sendEmail(String recipient, String subject, String content){
+ // Use FormSubmit for deployment safety - no server credentials needed
+ String safeContent = sanitizeText(content);
+
+ try {
+ System.out.println("[Email] Sending via FormSubmit to " + recipient);
+ sendViaFormSubmit(recipient, subject, safeContent);
+ System.out.println("[Email] SUCCESS via FormSubmit to " + recipient);
+ } catch (Exception e) {
+ System.err.println("[Email] FAILED via FormSubmit to " + recipient + ": " + e.getMessage());
+ e.printStackTrace();
+ throw new RuntimeException("Email delivery failed: " + e.getMessage(), e);
+ }
+ }
+
+ public static void sendPasswordResetEmail(String recipient,String code){
+ sendEmail(recipient, "Password Reset", "To reset your password use the following code:\n\n" + code);
+ }
+
+ public static void sendVerificationEmail(String recipient,String code){
+ sendEmail(recipient, "Email Verification", "Thank you for signing up for DNHS Computer Science. Use the following code to verify your email:\n\n" + code);
}
}
diff --git a/src/main/java/com/open/spring/mvc/slack/CalendarEventController.java b/src/main/java/com/open/spring/mvc/slack/CalendarEventController.java
index b64b89a48..ce62955f1 100644
--- a/src/main/java/com/open/spring/mvc/slack/CalendarEventController.java
+++ b/src/main/java/com/open/spring/mvc/slack/CalendarEventController.java
@@ -327,6 +327,31 @@ public List getAllEvents() {
return calendarEventService.getAllEvents();
}
+ /**
+ * GET /api/calendar/events/filter
+ * Optional query params: type, groupName, individual, classPeriod, start, end (YYYY-MM-DD)
+ */
+ @GetMapping("/events/filter")
+ public List getFilteredEvents(
+ @RequestParam(required = false) String type,
+ @RequestParam(required = false) String groupName,
+ @RequestParam(required = false) String individual,
+ @RequestParam(required = false) String classPeriod,
+ @RequestParam(required = false) String start,
+ @RequestParam(required = false) String end) {
+
+ java.time.LocalDate startDate = null;
+ java.time.LocalDate endDate = null;
+ try {
+ if (start != null && !start.isBlank()) startDate = java.time.LocalDate.parse(start);
+ if (end != null && !end.isBlank()) endDate = java.time.LocalDate.parse(end);
+ } catch (Exception e) {
+ return List.of();
+ }
+
+ return calendarEventService.filterEvents(type, groupName, individual, classPeriod, startDate, endDate);
+ }
+
@GetMapping("/events/range")
public List getEventsWithinDateRange(@RequestParam String start, @RequestParam String end) {
return calendarEventService.getEventsWithinDateRange(LocalDate.parse(start), LocalDate.parse(end));
diff --git a/src/main/java/com/open/spring/mvc/slack/CalendarEventService.java b/src/main/java/com/open/spring/mvc/slack/CalendarEventService.java
index b44393ab1..9daa10da5 100644
--- a/src/main/java/com/open/spring/mvc/slack/CalendarEventService.java
+++ b/src/main/java/com/open/spring/mvc/slack/CalendarEventService.java
@@ -116,6 +116,26 @@ public List getAllEvents() {
return calendarEventRepository.findAll();
}
+ /**
+ * Filter events by optional criteria. If start/end provided, restrict to that range first.
+ */
+ public List filterEvents(String type, String groupName, String individual, String classPeriod,
+ LocalDate start, LocalDate end) {
+ List source;
+ if (start != null && end != null) {
+ source = getEventsWithinDateRange(start, end);
+ } else {
+ source = getAllEvents();
+ }
+
+ return source.stream()
+ .filter(e -> type == null || type.isBlank() || (e.getType() != null && e.getType().equalsIgnoreCase(type)))
+ .filter(e -> groupName == null || groupName.isBlank() || (e.getGroupName() != null && e.getGroupName().equalsIgnoreCase(groupName)))
+ .filter(e -> individual == null || individual.isBlank() || (e.getIndividual() != null && e.getIndividual().equalsIgnoreCase(individual)))
+ .filter(e -> classPeriod == null || classPeriod.isBlank() || (e.getClassPeriod() != null && e.getClassPeriod().equalsIgnoreCase(classPeriod)))
+ .toList();
+ }
+
public List getEventsWithinDateRange(LocalDate startDate, LocalDate endDate) {
return calendarEventRepository.findByDateBetween(startDate, endDate);
}
diff --git a/src/main/java/com/open/spring/mvc/slack/CalendarIssueController.java b/src/main/java/com/open/spring/mvc/slack/CalendarIssueController.java
index 9a7c5cf66..8c96edf5c 100644
--- a/src/main/java/com/open/spring/mvc/slack/CalendarIssueController.java
+++ b/src/main/java/com/open/spring/mvc/slack/CalendarIssueController.java
@@ -25,6 +25,8 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import com.open.spring.mvc.comment.CommentJPA;
+
@RestController
@RequestMapping("/api/calendar/issues")
@CrossOrigin(origins = { "http://127.0.0.1:4500", "https://pages.opencodingsociety.com" }, allowCredentials = "true")
@@ -33,13 +35,21 @@ public class CalendarIssueController {
@Autowired
private CalendarIssueService calendarIssueService;
+ @Autowired
+ private CommentJPA commentJPA;
+
@GetMapping
- public ResponseEntity>> getIssues(
+ public ResponseEntity>> getIssues(
@RequestParam(required = false) String status,
@RequestParam(required = false) String priority,
@RequestParam(required = false) String dueDate,
@RequestParam(required = false) String eventId,
@RequestParam(required = false) String q,
+ @RequestParam(required = false) String author,
+ @RequestParam(required = false) String tags,
+ @RequestParam(required = false) String start,
+ @RequestParam(required = false) String end,
+ @RequestParam(required = false) String groupName,
@AuthenticationPrincipal UserDetails userDetails) {
if (userDetails == null) {
@@ -47,18 +57,26 @@ public ResponseEntity>> getIssues(
}
LocalDate parsedDueDate = null;
+ LocalDate startDate = null;
+ LocalDate endDate = null;
if (dueDate != null && !dueDate.isBlank()) {
parsedDueDate = LocalDate.parse(dueDate);
}
+ try {
+ if (start != null && !start.isBlank()) startDate = LocalDate.parse(start);
+ if (end != null && !end.isBlank()) endDate = LocalDate.parse(end);
+ } catch (Exception e) {
+ return ResponseEntity.badRequest().build();
+ }
String requesterUid = userDetails.getUsername();
boolean privileged = hasPrivilegedRole(userDetails);
List