From 206f0a1f99171a054ac8de2a52ee6b6989131eaf Mon Sep 17 00:00:00 2001 From: Noah Hammoud Date: Wed, 22 Nov 2023 17:50:26 -0500 Subject: [PATCH 1/3] Added initial upload page Co-authored-by: ConnorMarcus --- .../controllers/ProjectController.java | 5 ++ .../static/css/StudentReportUploadStyles.css | 80 +++++++++++++++++++ .../resources/templates/studentNavBar.html | 1 + .../templates/studentReportUpload.html | 23 ++++++ 4 files changed, 109 insertions(+) create mode 100644 src/main/resources/static/css/StudentReportUploadStyles.css create mode 100644 src/main/resources/templates/studentReportUpload.html diff --git a/src/main/java/sysc4806/project/controllers/ProjectController.java b/src/main/java/sysc4806/project/controllers/ProjectController.java index 0924a8a..e3904ac 100644 --- a/src/main/java/sysc4806/project/controllers/ProjectController.java +++ b/src/main/java/sysc4806/project/controllers/ProjectController.java @@ -114,6 +114,11 @@ public String removeStudentFromProject(@PathVariable Long projectId, @PathVariab return "error"; } + @GetMapping("/studentReportUpload") + public String uploadReport() { + return "studentReportUpload"; + } + /** * Disassociates a project from students and professor * @param project The project to disassociate diff --git a/src/main/resources/static/css/StudentReportUploadStyles.css b/src/main/resources/static/css/StudentReportUploadStyles.css new file mode 100644 index 0000000..a45b3ab --- /dev/null +++ b/src/main/resources/static/css/StudentReportUploadStyles.css @@ -0,0 +1,80 @@ +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; +} + +.container { + height: 100vh; + width: 100%; + align-items: center; + display: flex; + justify-content: center; + background-color: #fcfcfc; +} + +.card { + border-radius: 10px; + box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.3); + width: 600px; + height: 260px; + background-color: #ffffff; + padding: 10px 30px 40px; +} + +.card h3 { + font-size: 22px; + font-weight: 600; + +} + +.drop_box { + margin: 10px 0; + padding: 30px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + border: 3px dotted #a3a3a3; + border-radius: 5px; +} + +.drop_box h4 { + font-size: 16px; + font-weight: 400; + color: #2e2e2e; +} + +.drop_box p { + margin-top: 10px; + margin-bottom: 20px; + font-size: 12px; + color: #a3a3a3; +} + +.btn { + text-decoration: none; + background-color: #005af0; + color: #ffffff; + padding: 10px 20px; + border: none; + outline: none; + transition: 0.3s; +} + +.btn:hover{ + text-decoration: none; + background-color: #ffffff; + color: #005af0; + padding: 10px 20px; + border: none; + outline: 1px solid #010101; +} +.form input { + margin: 10px 0; + width: 100%; + background-color: #e2e2e2; + border: none; + outline: none; + padding: 12px 20px; + border-radius: 4px; +} diff --git a/src/main/resources/templates/studentNavBar.html b/src/main/resources/templates/studentNavBar.html index 7a56163..ef475c0 100644 --- a/src/main/resources/templates/studentNavBar.html +++ b/src/main/resources/templates/studentNavBar.html @@ -12,6 +12,7 @@ diff --git a/src/main/resources/templates/studentReportUpload.html b/src/main/resources/templates/studentReportUpload.html new file mode 100644 index 0000000..a7d78c9 --- /dev/null +++ b/src/main/resources/templates/studentReportUpload.html @@ -0,0 +1,23 @@ + + + + + Upload Report + + + +
+
+

Upload Report File

+
+
+

Select File here

+
+

Files Supported: PDF, TEXT, DOC, DOCX

+ + +
+
+
+ + \ No newline at end of file From 5a0574960564f8fdaf1bcfb3f28deea32d83f937 Mon Sep 17 00:00:00 2001 From: Connor Marcus <73544123+ConnorMarcus@users.noreply.github.com> Date: Wed, 22 Nov 2023 20:55:30 -0500 Subject: [PATCH 2/3] Added file upload logic Co-Authored-By: Noah Hammoud --- .../controllers/DownloadController.java | 51 ++++++++++++ .../controllers/ProjectController.java | 14 ++-- .../project/controllers/ReportController.java | 65 +++++++++++++++ .../java/sysc4806/project/models/Project.java | 42 +++++++++- .../sysc4806/project/models/ReportFile.java | 79 +++++++++++++++++++ .../repositories/ReportFileRepository.java | 8 ++ .../project/services/ReportService.java | 53 +++++++++++++ .../static/css/StudentReportUploadStyles.css | 38 +++++---- .../resources/templates/studentNavBar.html | 1 - .../templates/studentReportUpload.html | 28 ++++--- 10 files changed, 343 insertions(+), 36 deletions(-) create mode 100644 src/main/java/sysc4806/project/controllers/DownloadController.java create mode 100644 src/main/java/sysc4806/project/controllers/ReportController.java create mode 100644 src/main/java/sysc4806/project/models/ReportFile.java create mode 100644 src/main/java/sysc4806/project/repositories/ReportFileRepository.java create mode 100644 src/main/java/sysc4806/project/services/ReportService.java diff --git a/src/main/java/sysc4806/project/controllers/DownloadController.java b/src/main/java/sysc4806/project/controllers/DownloadController.java new file mode 100644 index 0000000..67c102b --- /dev/null +++ b/src/main/java/sysc4806/project/controllers/DownloadController.java @@ -0,0 +1,51 @@ +package sysc4806.project.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import sysc4806.project.models.ApplicationUser; +import sysc4806.project.models.ReportFile; +import sysc4806.project.models.Student; +import sysc4806.project.repositories.ApplicationUserRepository; +import sysc4806.project.services.ApplicationUserService; + +import java.io.IOException; +import java.nio.file.Files; + +import static sysc4806.project.util.AuthenticationHelper.STUDENT_ROLE; + +@RestController +public class DownloadController { + @Autowired + private ApplicationUserService userService; + + @RequestMapping(path = "/downloadReport", method = RequestMethod.GET) + @Secured(STUDENT_ROLE) + public ResponseEntity download() throws IOException { + Student user = (Student) userService.getCurrentUser(); + if (user.getProject() != null && user.getProject().getReport() != null) { + ReportFile reportFile = user.getProject().getReport(); + ByteArrayResource resource = new ByteArrayResource(reportFile.getFileData()); + + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + reportFile.getFileName()); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + + return ResponseEntity.ok() + .headers(headers) + .contentLength(reportFile.getFileData().length) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); + + } + + return ResponseEntity.badRequest().build(); + } +} diff --git a/src/main/java/sysc4806/project/controllers/ProjectController.java b/src/main/java/sysc4806/project/controllers/ProjectController.java index e3904ac..27f7bb4 100644 --- a/src/main/java/sysc4806/project/controllers/ProjectController.java +++ b/src/main/java/sysc4806/project/controllers/ProjectController.java @@ -67,7 +67,8 @@ public String viewProjectsPage(Model model) throws Exception { return "viewProjects"; } - @DeleteMapping( "/deleteProject/{projectId}") + @DeleteMapping(path = "/deleteProject/{projectId}") + @Secured(PROFESSOR_ROLE) public String deleteProject(@PathVariable Long projectId) { log.info(String.format("DELETE project request received for id: %s", projectId)); Project project = projectRepository.findById(projectId).orElse(null); @@ -82,7 +83,8 @@ public String deleteProject(@PathVariable Long projectId) { } } - @PatchMapping("/project/{projectId}/addStudent/{userId}") + @PatchMapping(path = "/project/{projectId}/addStudent/{userId}") + @Secured(STUDENT_ROLE) public String addStudentToProject(@PathVariable Long projectId, @PathVariable Long userId) { log.info("add student to project PATCH request received"); Project project = projectRepository.findById(projectId).orElse(null); @@ -98,7 +100,8 @@ public String addStudentToProject(@PathVariable Long projectId, @PathVariable Lo return "error"; } - @PatchMapping("/project/{projectId}/removeStudent/{userId}") + @PatchMapping(path = "/project/{projectId}/removeStudent/{userId}") + @Secured(STUDENT_ROLE) public String removeStudentFromProject(@PathVariable Long projectId, @PathVariable Long userId) { log.info("remove student from project PATCH request received"); Project project = projectRepository.findById(projectId).orElse(null); @@ -114,11 +117,6 @@ public String removeStudentFromProject(@PathVariable Long projectId, @PathVariab return "error"; } - @GetMapping("/studentReportUpload") - public String uploadReport() { - return "studentReportUpload"; - } - /** * Disassociates a project from students and professor * @param project The project to disassociate diff --git a/src/main/java/sysc4806/project/controllers/ReportController.java b/src/main/java/sysc4806/project/controllers/ReportController.java new file mode 100644 index 0000000..5ae6e41 --- /dev/null +++ b/src/main/java/sysc4806/project/controllers/ReportController.java @@ -0,0 +1,65 @@ +package sysc4806.project.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.parameters.P; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import sysc4806.project.models.ApplicationUser; +import sysc4806.project.models.Project; +import sysc4806.project.models.ReportFile; +import sysc4806.project.models.Student; +import sysc4806.project.services.ApplicationUserService; +import sysc4806.project.services.ReportService; + +import static sysc4806.project.util.AuthenticationHelper.PROFESSOR_ROLE; +import static sysc4806.project.util.AuthenticationHelper.STUDENT_ROLE; + +@Controller +public class ReportController { + @Autowired + private ReportService reportService; + + @Autowired + private ApplicationUserService userService; + + @GetMapping(path = "/studentReportUpload") + @Secured(STUDENT_ROLE) + public String uploadReportPage(Model model) { + Student user = (Student) userService.getCurrentUser(); + if (user.getProject() != null) { + ReportFile report = user.getProject().getReport(); + model.addAttribute("inProject", true); + model.addAttribute("reportName", report == null ? "" : report.getFileName()); + } + else { + model.addAttribute("inProject", false); + } + + return "studentReportUpload"; + } + + @PostMapping(path = "/studentReportUpload") + @Secured(STUDENT_ROLE) + public String uploadReport(@RequestParam("file") MultipartFile file, Model model) { + Student user = (Student) userService.getCurrentUser(); + if (user.getProject() != null) { + try { + reportService.saveReport(user.getProject(), file); + } + catch (Exception e) { + model.addAttribute("uploadError", "There was an error uploading the file!"); + } + model.addAttribute("inProject", true); + model.addAttribute("reportName", file.getOriginalFilename()); + return "studentReportUpload"; + } + + return "redirect:/home"; // Redirect to home page if project is null + } + +} diff --git a/src/main/java/sysc4806/project/models/Project.java b/src/main/java/sysc4806/project/models/Project.java index 1523117..b4cdefc 100644 --- a/src/main/java/sysc4806/project/models/Project.java +++ b/src/main/java/sysc4806/project/models/Project.java @@ -1,8 +1,8 @@ package sysc4806.project.models; import jakarta.persistence.*; -import java.util.ArrayList; -import java.util.List; + +import java.util.*; /** * Project model @@ -27,6 +27,11 @@ public class Project { @ManyToOne private Professor professor; + @OneToOne + private ReportFile report; + + private static Calendar deadline = new GregorianCalendar(2023, Calendar.DECEMBER, 8); + public Project() { this.students = new ArrayList<>(); this.restrictions = new ArrayList<>(); @@ -159,4 +164,37 @@ public void addStudent(Student student) { public void removeStudent(Student student) { students.remove(student); } + + + /** + * Getter for the report + * @return byte array representing the report + */ + public ReportFile getReport() { + return report; + } + + /** + * Setter for the report + * @param report the report thats being set + */ + public void setReport(ReportFile report) { + this.report = report; + } + + /** + * Setter for the deadline + * @return The deadline of the project + */ + public static Calendar getDeadline() { + return deadline; + } + + /** + * Getter for the deadline + * @param deadline The deadline of the project + */ + public static void setDeadline(Calendar deadline) { + Project.deadline = deadline; + } } diff --git a/src/main/java/sysc4806/project/models/ReportFile.java b/src/main/java/sysc4806/project/models/ReportFile.java new file mode 100644 index 0000000..a46690e --- /dev/null +++ b/src/main/java/sysc4806/project/models/ReportFile.java @@ -0,0 +1,79 @@ +package sysc4806.project.models; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; + + +@Entity +public class ReportFile { + + private String fileName; + + @Lob + private byte[] fileData; + + @Id + @GeneratedValue + private Long id; + + /** + * Constructor for ReportFile + * @param fileName String filename + * @param fileData byte[] data of file + */ + public ReportFile(String fileName, byte[] fileData) { + this.fileName = fileName; + this.fileData = fileData; + } + + /** + * Default constructor + */ + public ReportFile() {} + + /** + * Gets the filename + * @return String file name + */ + public String getFileName() { + return fileName; + } + + /** + * Sets the filename + * @param fileName String filename to set + */ + public void setFileName(String fileName) { + this.fileName = fileName; + } + + /** + * Gets the filedata + * @return byte[] the file data + */ + public byte[] getFileData() { + return fileData; + } + + /** + * Sets the filedata + * @param fileData byte[] filedata to set + */ + public void setFileData(byte[] fileData) { + this.fileData = fileData; + } + + /** + * Sets the id + * @param id + */ + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } +} diff --git a/src/main/java/sysc4806/project/repositories/ReportFileRepository.java b/src/main/java/sysc4806/project/repositories/ReportFileRepository.java new file mode 100644 index 0000000..0e95601 --- /dev/null +++ b/src/main/java/sysc4806/project/repositories/ReportFileRepository.java @@ -0,0 +1,8 @@ +package sysc4806.project.repositories; + +import org.springframework.data.repository.CrudRepository; +import sysc4806.project.models.ReportFile; + +public interface ReportFileRepository extends CrudRepository { + public ReportFile findReportFileById(Long id); +} diff --git a/src/main/java/sysc4806/project/services/ReportService.java b/src/main/java/sysc4806/project/services/ReportService.java new file mode 100644 index 0000000..dd87a05 --- /dev/null +++ b/src/main/java/sysc4806/project/services/ReportService.java @@ -0,0 +1,53 @@ +package sysc4806.project.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import sysc4806.project.models.Project; +import sysc4806.project.models.ReportFile; +import sysc4806.project.repositories.ProjectRepository; +import sysc4806.project.repositories.ReportFileRepository; +import java.io.IOException; +import java.io.InputStream; + +@Service +public class ReportService { + @Autowired + private ProjectRepository projectRepository; + @Autowired + private ReportFileRepository reportFileRepository; + private final Logger log = LoggerFactory.getLogger(ReportService.class); + + public void saveReport(Project project, MultipartFile file) throws Exception { + try { + try (InputStream inputStream = file.getInputStream()) { + if (project != null) { + ReportFile reportFile = new ReportFile(file.getOriginalFilename(), inputStream.readAllBytes()); + reportFileRepository.save(reportFile); + project.setReport(reportFile); + projectRepository.save(project); + } + else { + throw new Exception("Project cannot be null!"); + } + } + } + catch (IOException e) { + log.error(e.getMessage()); + throw new IOException("Failed to store file.", e); + } + catch (Exception e) { + log.error(e.getMessage()); + throw new Exception("Project cannot be null.", e); + } + } + + public ReportFile getReport(Project project) { + if (project != null) { + return project.getReport(); + } + return null; + } +} diff --git a/src/main/resources/static/css/StudentReportUploadStyles.css b/src/main/resources/static/css/StudentReportUploadStyles.css index a45b3ab..5bda718 100644 --- a/src/main/resources/static/css/StudentReportUploadStyles.css +++ b/src/main/resources/static/css/StudentReportUploadStyles.css @@ -1,9 +1,9 @@ body { - font-family: Arial, sans-serif; background-color: #f5f5f5; + font-family: Arial, sans-serif; } -.container { +.main-content .container { height: 100vh; width: 100%; align-items: center; @@ -12,22 +12,21 @@ body { background-color: #fcfcfc; } -.card { +.main-content .card { border-radius: 10px; box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.3); width: 600px; - height: 260px; + height: 300px; background-color: #ffffff; padding: 10px 30px 40px; } -.card h3 { +.main-content .card h3 { font-size: 22px; font-weight: 600; - } -.drop_box { +.main-content .drop_box { margin: 10px 0; padding: 30px; display: flex; @@ -38,22 +37,22 @@ body { border-radius: 5px; } -.drop_box h4 { +.main-content .drop_box h4 { font-size: 16px; font-weight: 400; color: #2e2e2e; } -.drop_box p { +.main-content .drop_box p { margin-top: 10px; margin-bottom: 20px; font-size: 12px; color: #a3a3a3; } -.btn { +.main-content .btn { text-decoration: none; - background-color: #005af0; + background-color: #007bff; color: #ffffff; padding: 10px 20px; border: none; @@ -61,15 +60,16 @@ body { transition: 0.3s; } -.btn:hover{ +.main-content .btn:hover { text-decoration: none; background-color: #ffffff; - color: #005af0; + color: #007bff; padding: 10px 20px; border: none; outline: 1px solid #010101; } -.form input { + +.main-content .form input { margin: 10px 0; width: 100%; background-color: #e2e2e2; @@ -78,3 +78,13 @@ body { padding: 12px 20px; border-radius: 4px; } + +.main-content .submit-button-container { + display: flex; + justify-content: center; + margin-top: 20px; +} + +.main-content .btn.submit-button { + margin-top: 10px; +} diff --git a/src/main/resources/templates/studentNavBar.html b/src/main/resources/templates/studentNavBar.html index ef475c0..1d21d5b 100644 --- a/src/main/resources/templates/studentNavBar.html +++ b/src/main/resources/templates/studentNavBar.html @@ -13,7 +13,6 @@
  • Home
  • View Projects
  • Upload Report
  • -
  • Log Out
  • diff --git a/src/main/resources/templates/studentReportUpload.html b/src/main/resources/templates/studentReportUpload.html index a7d78c9..7c1792c 100644 --- a/src/main/resources/templates/studentReportUpload.html +++ b/src/main/resources/templates/studentReportUpload.html @@ -6,18 +6,24 @@ -
    -
    -

    Upload Report File

    -
    -
    -

    Select File here

    -
    -

    Files Supported: PDF, TEXT, DOC, DOCX

    - - + +
    +
    +
    +
    +

    Upload Report File

    +
    +
    +

    Select File here

    +
    +

    Files Supported: PDF

    + + +
    -
    + +
    Submitted Report:
    +
    You must join a project before submitting a report!
    \ No newline at end of file From eeae6760cf28a2a41ec0ee5a46acac8a1ae32d14 Mon Sep 17 00:00:00 2001 From: Connor Marcus <73544123+ConnorMarcus@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:48:00 -0500 Subject: [PATCH 3/3] Added Project Deadline and Unit/Integration Tests Co-Authored-By: Noah Hammoud --- .../controllers/DeadlineController.java | 37 ++++++++ .../controllers/DownloadController.java | 6 +- .../controllers/ProjectController.java | 1 - .../project/controllers/ReportController.java | 32 +++++-- .../sysc4806/project/models/ReportFile.java | 6 +- .../project/services/ReportService.java | 2 + .../resources/static/css/DeadlineStyles.css | 54 +++++++++++ .../resources/static/css/NavBarStyles.css | 2 +- .../static/css/StudentReportUploadStyles.css | 21 +++- src/main/resources/templates/deadlines.html | 22 +++++ .../resources/templates/professorNavBar.html | 1 + .../templates/studentReportUpload.html | 5 +- .../controllers/TestDeadlineController.java | 54 +++++++++++ .../controllers/TestDownloadController.java | 47 +++++++++ .../controllers/TestProjectController.java | 2 + .../controllers/TestReportController.java | 95 +++++++++++++++++++ .../sysc4806/project/models/TestProject.java | 18 +++- .../project/models/TestReportFile.java | 48 ++++++++++ .../project/services/TestReportService.java | 76 +++++++++++++++ 19 files changed, 504 insertions(+), 25 deletions(-) create mode 100644 src/main/java/sysc4806/project/controllers/DeadlineController.java create mode 100644 src/main/resources/static/css/DeadlineStyles.css create mode 100644 src/main/resources/templates/deadlines.html create mode 100644 src/test/java/sysc4806/project/controllers/TestDeadlineController.java create mode 100644 src/test/java/sysc4806/project/controllers/TestDownloadController.java create mode 100644 src/test/java/sysc4806/project/controllers/TestReportController.java create mode 100644 src/test/java/sysc4806/project/models/TestReportFile.java create mode 100644 src/test/java/sysc4806/project/services/TestReportService.java diff --git a/src/main/java/sysc4806/project/controllers/DeadlineController.java b/src/main/java/sysc4806/project/controllers/DeadlineController.java new file mode 100644 index 0000000..9564fb2 --- /dev/null +++ b/src/main/java/sysc4806/project/controllers/DeadlineController.java @@ -0,0 +1,37 @@ +package sysc4806.project.controllers; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import sysc4806.project.models.Project; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +import static sysc4806.project.util.AuthenticationHelper.PROFESSOR_ROLE; + +@Controller +public class DeadlineController { + + @GetMapping(path = "/deadlines") + @Secured(PROFESSOR_ROLE) + public String getDeadlinePage(Model model) { + model.addAttribute("reportDeadline", Project.getDeadline().getTime().toString()); + return "deadlines"; + } + + @PostMapping(path = "/deadlines/setReport") + @Secured(PROFESSOR_ROLE) + public String setReportDeadline(@RequestParam("selectedDate") String deadline, Model model) throws ParseException { + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); + cal.setTime(sdf.parse(deadline)); + Project.setDeadline(cal); + model.addAttribute("reportDeadline", Project.getDeadline().getTime().toString()); + return "deadlines"; + } +} diff --git a/src/main/java/sysc4806/project/controllers/DownloadController.java b/src/main/java/sysc4806/project/controllers/DownloadController.java index 67c102b..1241446 100644 --- a/src/main/java/sysc4806/project/controllers/DownloadController.java +++ b/src/main/java/sysc4806/project/controllers/DownloadController.java @@ -10,14 +10,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import sysc4806.project.models.ApplicationUser; import sysc4806.project.models.ReportFile; import sysc4806.project.models.Student; -import sysc4806.project.repositories.ApplicationUserRepository; import sysc4806.project.services.ApplicationUserService; - import java.io.IOException; -import java.nio.file.Files; import static sysc4806.project.util.AuthenticationHelper.STUDENT_ROLE; @@ -28,7 +24,7 @@ public class DownloadController { @RequestMapping(path = "/downloadReport", method = RequestMethod.GET) @Secured(STUDENT_ROLE) - public ResponseEntity download() throws IOException { + public ResponseEntity studentDownloadReport() throws IOException { Student user = (Student) userService.getCurrentUser(); if (user.getProject() != null && user.getProject().getReport() != null) { ReportFile reportFile = user.getProject().getReport(); diff --git a/src/main/java/sysc4806/project/controllers/ProjectController.java b/src/main/java/sysc4806/project/controllers/ProjectController.java index 27f7bb4..143feda 100644 --- a/src/main/java/sysc4806/project/controllers/ProjectController.java +++ b/src/main/java/sysc4806/project/controllers/ProjectController.java @@ -13,7 +13,6 @@ import sysc4806.project.services.ApplicationUserService; import java.util.List; - import static sysc4806.project.util.AuthenticationHelper.*; @Controller diff --git a/src/main/java/sysc4806/project/controllers/ReportController.java b/src/main/java/sysc4806/project/controllers/ReportController.java index 5ae6e41..3e33634 100644 --- a/src/main/java/sysc4806/project/controllers/ReportController.java +++ b/src/main/java/sysc4806/project/controllers/ReportController.java @@ -9,14 +9,15 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; -import sysc4806.project.models.ApplicationUser; import sysc4806.project.models.Project; import sysc4806.project.models.ReportFile; import sysc4806.project.models.Student; import sysc4806.project.services.ApplicationUserService; import sysc4806.project.services.ReportService; +import sysc4806.project.util.AuthenticationHelper; + +import java.util.Date; -import static sysc4806.project.util.AuthenticationHelper.PROFESSOR_ROLE; import static sysc4806.project.util.AuthenticationHelper.STUDENT_ROLE; @Controller @@ -31,6 +32,8 @@ public class ReportController { @Secured(STUDENT_ROLE) public String uploadReportPage(Model model) { Student user = (Student) userService.getCurrentUser(); + model.addAttribute("deadline", Project.getDeadline().getTime().toString()); + model.addAttribute("beforeDeadline", isBeforeDeadline()); if (user.getProject() != null) { ReportFile report = user.getProject().getReport(); model.addAttribute("inProject", true); @@ -45,21 +48,32 @@ public String uploadReportPage(Model model) { @PostMapping(path = "/studentReportUpload") @Secured(STUDENT_ROLE) - public String uploadReport(@RequestParam("file") MultipartFile file, Model model) { + public String uploadReport(@RequestParam("file") MultipartFile file, Model model) throws Exception { Student user = (Student) userService.getCurrentUser(); if (user.getProject() != null) { - try { - reportService.saveReport(user.getProject(), file); - } - catch (Exception e) { - model.addAttribute("uploadError", "There was an error uploading the file!"); + model.addAttribute("deadline", Project.getDeadline()); + boolean beforeDeadline = isBeforeDeadline(); + if (beforeDeadline) { + try { + reportService.saveReport(user.getProject(), file); + } + catch (Exception e) { + model.addAttribute("uploadError", "There was an error uploading the file!"); + } + model.addAttribute("reportName", file.getOriginalFilename()); } model.addAttribute("inProject", true); - model.addAttribute("reportName", file.getOriginalFilename()); + model.addAttribute("beforeDeadline", beforeDeadline); return "studentReportUpload"; } + model.addAttribute("userType", STUDENT_ROLE); + model.addAttribute("userName", user.getName()); return "redirect:/home"; // Redirect to home page if project is null } + protected boolean isBeforeDeadline() { + return new Date().before(Project.getDeadline().getTime()); + } + } diff --git a/src/main/java/sysc4806/project/models/ReportFile.java b/src/main/java/sysc4806/project/models/ReportFile.java index a46690e..df3bc90 100644 --- a/src/main/java/sysc4806/project/models/ReportFile.java +++ b/src/main/java/sysc4806/project/models/ReportFile.java @@ -67,12 +67,16 @@ public void setFileData(byte[] fileData) { /** * Sets the id - * @param id + * @param id Long id to set */ public void setId(Long id) { this.id = id; } + /** + * Gets the id + * @return Long the id + */ public Long getId() { return id; } diff --git a/src/main/java/sysc4806/project/services/ReportService.java b/src/main/java/sysc4806/project/services/ReportService.java index dd87a05..81aa3b5 100644 --- a/src/main/java/sysc4806/project/services/ReportService.java +++ b/src/main/java/sysc4806/project/services/ReportService.java @@ -16,8 +16,10 @@ public class ReportService { @Autowired private ProjectRepository projectRepository; + @Autowired private ReportFileRepository reportFileRepository; + private final Logger log = LoggerFactory.getLogger(ReportService.class); public void saveReport(Project project, MultipartFile file) throws Exception { diff --git a/src/main/resources/static/css/DeadlineStyles.css b/src/main/resources/static/css/DeadlineStyles.css new file mode 100644 index 0000000..6377ade --- /dev/null +++ b/src/main/resources/static/css/DeadlineStyles.css @@ -0,0 +1,54 @@ +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; +} + +.main-content h1 { + text-align: center; + margin-top: 50px; + color: #333; +} + +.main-content form { + max-width: 300px; + margin: 0 auto; + background: #fff; + padding: 20px; + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} + +.main-content input[type="date"] { + width: 100%; + padding: 10px; + margin: 8px 0; + border: 1px solid #ccc; + border-radius: 3px; + box-sizing: border-box; +} + +.main-content input[type="submit"] { + background-color: #007bff; + color: #fff; + padding: 10px 20px; + border: none; + border-radius: 3px; + cursor: pointer; + +} + +.main-content input[type="submit"]:hover { + background-color: #0056b3; +} + +.main-content p { + text-align: center; + font-size: 20px; + margin-top: 30px; +} + +.center { + display: flex; + justify-content: center; + align-content: center; +} \ No newline at end of file diff --git a/src/main/resources/static/css/NavBarStyles.css b/src/main/resources/static/css/NavBarStyles.css index 3883fac..2146d67 100644 --- a/src/main/resources/static/css/NavBarStyles.css +++ b/src/main/resources/static/css/NavBarStyles.css @@ -1,4 +1,4 @@ -body { +.nav-bar body { margin: 0; font-family: Arial, sans-serif; } diff --git a/src/main/resources/static/css/StudentReportUploadStyles.css b/src/main/resources/static/css/StudentReportUploadStyles.css index 5bda718..7762493 100644 --- a/src/main/resources/static/css/StudentReportUploadStyles.css +++ b/src/main/resources/static/css/StudentReportUploadStyles.css @@ -1,5 +1,8 @@ body { background-color: #f5f5f5; +} + +.main-content { font-family: Arial, sans-serif; } @@ -17,7 +20,6 @@ body { box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.3); width: 600px; height: 300px; - background-color: #ffffff; padding: 10px 30px 40px; } @@ -62,7 +64,6 @@ body { .main-content .btn:hover { text-decoration: none; - background-color: #ffffff; color: #007bff; padding: 10px 20px; border: none; @@ -72,7 +73,6 @@ body { .main-content .form input { margin: 10px 0; width: 100%; - background-color: #e2e2e2; border: none; outline: none; padding: 12px 20px; @@ -88,3 +88,18 @@ body { .main-content .btn.submit-button { margin-top: 10px; } + +p { + text-align: center; +} + +.not-in-project { + margin-top: 100px; + font-size: 30px; +} + +.deadline-message { + margin-top: 30px; + font-size: 20px; + color: red; +} diff --git a/src/main/resources/templates/deadlines.html b/src/main/resources/templates/deadlines.html new file mode 100644 index 0000000..28fcb71 --- /dev/null +++ b/src/main/resources/templates/deadlines.html @@ -0,0 +1,22 @@ + + + + + Set deadline + + + + +
    +

    Set Deadlines:

    +
    + + +
    + +
    +
    +

    +
    + + diff --git a/src/main/resources/templates/professorNavBar.html b/src/main/resources/templates/professorNavBar.html index ece68e1..7ed49cd 100644 --- a/src/main/resources/templates/professorNavBar.html +++ b/src/main/resources/templates/professorNavBar.html @@ -13,6 +13,7 @@
  • Home
  • View Projects
  • Create Project
  • +
  • Deadlines
  • Log Out
  • diff --git a/src/main/resources/templates/studentReportUpload.html b/src/main/resources/templates/studentReportUpload.html index 7c1792c..6e3b5ab 100644 --- a/src/main/resources/templates/studentReportUpload.html +++ b/src/main/resources/templates/studentReportUpload.html @@ -9,7 +9,7 @@
    -
    +

    Upload Report File

    @@ -22,8 +22,9 @@

    Select File here

    +

    Submitted Report:
    -
    You must join a project before submitting a report!
    +

    You must join a project before submitting a report!

    \ No newline at end of file diff --git a/src/test/java/sysc4806/project/controllers/TestDeadlineController.java b/src/test/java/sysc4806/project/controllers/TestDeadlineController.java new file mode 100644 index 0000000..3a1aec9 --- /dev/null +++ b/src/test/java/sysc4806/project/controllers/TestDeadlineController.java @@ -0,0 +1,54 @@ +package sysc4806.project.controllers; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.core.parameters.P; +import org.springframework.test.web.servlet.MockMvc; +import sysc4806.project.WithMockProfessor; +import sysc4806.project.models.Project; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static sysc4806.project.util.AuthenticationHelper.STUDENT_ROLE; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class TestDeadlineController { + @Autowired + private MockMvc mockMvc; + + @Test + @WithMockProfessor + public void testGetDeadlinePage() throws Exception { + String deadline = Project.getDeadline().getTime().toString(); + this.mockMvc.perform(get("/deadlines")).andExpect(status().is2xxSuccessful()) + .andExpect(view().name("deadlines")) + .andExpect(model().attribute("reportDeadline", deadline)) + .andDo(print()).andExpect(content().string(containsString(deadline))); + } + + @Test + @WithMockProfessor + public void testSetReportDeadline() throws Exception { + String deadline = "2023-12-12"; + this.mockMvc.perform(post("/deadlines/setReport").param("selectedDate", deadline)) + .andExpect(status().is2xxSuccessful()) + .andExpect(view().name("deadlines")) + .andExpect(model().attribute("reportDeadline", Project.getDeadline().getTime().toString())); + Calendar cal = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); + cal.setTime(sdf.parse(deadline)); + assertEquals(cal, Project.getDeadline()); + } +} diff --git a/src/test/java/sysc4806/project/controllers/TestDownloadController.java b/src/test/java/sysc4806/project/controllers/TestDownloadController.java new file mode 100644 index 0000000..1a32428 --- /dev/null +++ b/src/test/java/sysc4806/project/controllers/TestDownloadController.java @@ -0,0 +1,47 @@ +package sysc4806.project.controllers; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import sysc4806.project.WithMockCustomStudentUser; +import sysc4806.project.WithMockStudent; +import sysc4806.project.models.Project; +import sysc4806.project.models.ReportFile; +import sysc4806.project.services.ApplicationUserService; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class TestDownloadController { + @Autowired + private MockMvc mockMvc; + @Autowired + @InjectMocks + private DownloadController controller; + @MockBean + private ApplicationUserService userService; + + @Test + @WithMockStudent + public void testStudentDownloadReport() throws Exception { + when(userService.getCurrentUser()).thenReturn(WithMockCustomStudentUser.STUDENT); + + // Test download when student not in a project + this.mockMvc.perform(get("/downloadReport")).andExpect(status().isBadRequest()); + + // Test download when student is in a project + Project project = new Project(); + ReportFile report = new ReportFile("filename", "content".getBytes()); + project.setReport(report); + WithMockCustomStudentUser.STUDENT.setProject(project); + this.mockMvc.perform(get("/downloadReport")).andExpect(status().isOk()) + .andExpect(content().bytes(report.getFileData())); + } +} diff --git a/src/test/java/sysc4806/project/controllers/TestProjectController.java b/src/test/java/sysc4806/project/controllers/TestProjectController.java index 884cee9..94bc0bc 100644 --- a/src/test/java/sysc4806/project/controllers/TestProjectController.java +++ b/src/test/java/sysc4806/project/controllers/TestProjectController.java @@ -83,6 +83,7 @@ public void testStudentViewProjectPage() throws Exception { } @Test + @WithMockProfessor public void testDeleteProject() throws Exception { this.mockMvc.perform(delete("/deleteProject/"+100) .with(csrf())) @@ -101,6 +102,7 @@ public void testDeleteProject() throws Exception { } @Test + @WithMockStudent public void testAddStudentToProject() throws Exception { this.mockMvc.perform(patch("/project/{projectId}/addStudent/{studentId}", 100, 25) .with(csrf())) diff --git a/src/test/java/sysc4806/project/controllers/TestReportController.java b/src/test/java/sysc4806/project/controllers/TestReportController.java new file mode 100644 index 0000000..aa919c2 --- /dev/null +++ b/src/test/java/sysc4806/project/controllers/TestReportController.java @@ -0,0 +1,95 @@ +package sysc4806.project.controllers; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import sysc4806.project.WithMockCustomStudentUser; +import sysc4806.project.WithMockStudent; +import sysc4806.project.models.Project; +import sysc4806.project.models.ReportFile; +import sysc4806.project.services.ApplicationUserService; +import sysc4806.project.services.ReportService; + +import java.util.Calendar; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +public class TestReportController { + @Autowired + private MockMvc mockMvc; + @Autowired + @InjectMocks + private ReportController controller; + @MockBean + private ApplicationUserService userService; + @MockBean + private ReportService reportService; + + @Test + @WithMockStudent + public void testUploadReportPage() throws Exception { + when(userService.getCurrentUser()).thenReturn(WithMockCustomStudentUser.STUDENT); + WithMockCustomStudentUser.STUDENT.setProject(new Project()); + String reportFileName = "Test"; + WithMockCustomStudentUser.STUDENT.getProject().setReport(new ReportFile(reportFileName, "test".getBytes())); + this.mockMvc.perform(get("/studentReportUpload")) + .andExpect(status().is2xxSuccessful()) + .andExpect(view().name("studentReportUpload")) + .andExpect(model().attribute("deadline", Project.getDeadline().getTime().toString())) + .andExpect(model().attribute("beforeDeadline", true)) + .andExpect(model().attribute("inProject", true)) + .andExpect(model().attribute("reportName",reportFileName)); + } + + @Test + @WithMockStudent + public void testUploadReportPageNullProject() throws Exception { + when(userService.getCurrentUser()).thenReturn(WithMockCustomStudentUser.STUDENT); + WithMockCustomStudentUser.STUDENT.setProject(null); + this.mockMvc.perform(get("/studentReportUpload")) + .andExpect(status().is2xxSuccessful()) + .andExpect(view().name("studentReportUpload")) + .andExpect(model().attribute("inProject", false)); + } + + @Test + @WithMockStudent + public void testUploadReport() throws Exception { + when(userService.getCurrentUser()).thenReturn(WithMockCustomStudentUser.STUDENT); + String filename = "FILENAME"; + byte[] data = "testData".getBytes(); + MockMultipartFile file = new MockMultipartFile("file", filename, "testContentType", data); + + // Test with non-null project + Project project = new Project(); + ReportFile report = new ReportFile(filename, data); + project.setReport(report); + WithMockCustomStudentUser.STUDENT.setProject(project); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DAY_OF_MONTH, 1); + Project.setDeadline(calendar); + this.mockMvc.perform(multipart("/studentReportUpload").file(file)) + .andExpect(status().is2xxSuccessful()) + .andExpect(view().name("studentReportUpload")) + .andExpect(model().attribute("reportName", filename)) + .andExpect(model().attribute("inProject", true)) + .andExpect(model().attribute("beforeDeadline", true)); + + // Test with null project + WithMockCustomStudentUser.STUDENT.setProject(null); + this.mockMvc.perform(multipart("/studentReportUpload").file(file)) + .andExpect(status().is3xxRedirection()) + .andExpect(view().name("redirect:/home")); + } + + +} diff --git a/src/test/java/sysc4806/project/models/TestProject.java b/src/test/java/sysc4806/project/models/TestProject.java index fb9a6f4..2ee5044 100644 --- a/src/test/java/sysc4806/project/models/TestProject.java +++ b/src/test/java/sysc4806/project/models/TestProject.java @@ -3,9 +3,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -108,4 +106,18 @@ void testRemoveStudent() { project.removeStudent(s); assertEquals(new ArrayList<>(), project.getStudents()); } + + @Test + void testSetReportFile() { + ReportFile file = new ReportFile(); + project.setReport(file); + assertEquals(file, project.getReport()); + } + + @Test + void testSetDeadline() { + Calendar calendar = new GregorianCalendar(2023, Calendar.DECEMBER, 12); + project.setDeadline(calendar); + assertEquals(calendar, project.getDeadline()); + } } \ No newline at end of file diff --git a/src/test/java/sysc4806/project/models/TestReportFile.java b/src/test/java/sysc4806/project/models/TestReportFile.java new file mode 100644 index 0000000..636a719 --- /dev/null +++ b/src/test/java/sysc4806/project/models/TestReportFile.java @@ -0,0 +1,48 @@ +package sysc4806.project.models; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestReportFile { + private static ReportFile reportFile; + static String FILENAME = "FILENAME"; + static byte[] CONTENT = "FILE_CONTENT".getBytes(); + + @BeforeEach + public void setUp() { + reportFile = new ReportFile(FILENAME, CONTENT); + } + + @Test + public void testGetFileName() { + assertEquals(reportFile.getFileName(), FILENAME); + } + + @Test + public void testSetFileName() { + reportFile.setFileName("other filename"); + assertEquals(reportFile.getFileName(), "other filename"); + } + + @Test + public void testGetFileData() { + assertArrayEquals(reportFile.getFileData(), CONTENT); + } + + @Test + public void testSetFileData() { + byte[] newContent = "New content".getBytes(); + reportFile.setFileData(newContent); + assertArrayEquals(reportFile.getFileData(), newContent); + } + + @Test + public void testSetId() { + reportFile.setId(100L); + assertEquals(reportFile.getId(), 100L); + } +} diff --git a/src/test/java/sysc4806/project/services/TestReportService.java b/src/test/java/sysc4806/project/services/TestReportService.java new file mode 100644 index 0000000..cd78a28 --- /dev/null +++ b/src/test/java/sysc4806/project/services/TestReportService.java @@ -0,0 +1,76 @@ +package sysc4806.project.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; +import sysc4806.project.models.Project; +import sysc4806.project.models.ReportFile; +import sysc4806.project.repositories.ApplicationUserRepository; +import sysc4806.project.repositories.ProjectRepository; +import sysc4806.project.repositories.ReportFileRepository; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + + +public class TestReportService { + @Spy + private ProjectRepository projectRepository = mock(ProjectRepository.class); + + @Spy + private ReportFileRepository reportFileRepository = mock(ReportFileRepository.class); + + @InjectMocks + private ReportService reportService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testSaveReport() throws Exception{ + Project project = new Project(); + String filename = "testfile.txt"; + byte[] data = "testData".getBytes(); + MultipartFile file = new MockMultipartFile("test", filename, "testContentType", data); + ReportFile expectedReportFile = new ReportFile(filename, data); + reportService.saveReport(project, file); + assertEquals(expectedReportFile.getFileName(), project.getReport().getFileName()); + assertArrayEquals(expectedReportFile.getFileData(), project.getReport().getFileData()); + } + + @Test + public void testSaveReportNullProject() { + Project project = null; + String filename = "testfile.txt"; + byte[] data = "testData".getBytes(); + MultipartFile file = new MockMultipartFile("test", filename, "testContentType", data); + + assertThrows(Exception.class, () -> { + reportService.saveReport(project, file); + }); + } + + @Test + public void testGetReport() { + Project project = new Project(); + String filename = "testfile.txt"; + byte[] data = "testData".getBytes(); + ReportFile expectedReportFile = new ReportFile(filename, data); + project.setReport(expectedReportFile); + + assertEquals(expectedReportFile.getFileName(), reportService.getReport(project).getFileName()); + assertArrayEquals(expectedReportFile.getFileData(), reportService.getReport(project).getFileData()); + } + + @Test + public void testGetReportNullProject() { + Project project = null; + assertNull(reportService.getReport(project)); + } +}