students = course.getList("students", String.class);
for (String student : students) {
Document studentDocument = studentCollection.find(eq("student_id", student)).first();
- if (studentDocument != null) studentCollection.updateOne(eq("student_id", student), push("courses", dao.courseID));
+ if (studentDocument != null)
+ studentCollection.updateOne(eq("student_id", student), push("courses", dao.courseID));
}
}
@@ -94,6 +111,7 @@ public String updateCourse(SecurityContext securityContext, CourseDAO dao) {
String originalCourseID = dao.courseID;
String newCourseID = dao.abbreviation + "-" + dao.courseSection + "-" + dao.crn + "-" + dao.semester + "-" + dao.year;
int originalTeamSize = courseDocument.getInteger("team_size");
+ dao.blockedWords = courseDocument.getList("blocked_words", String.class);
int newTeamSize = dao.teamSize;
dao.courseID = newCourseID;
dao.students = courseDocument.getList("students", String.class);
@@ -104,7 +122,8 @@ public String updateCourse(SecurityContext securityContext, CourseDAO dao) {
eq("course_id", newCourseID),
eq("professor_id", professorID)
)).first();
- if (duplicatedCourseDocument != null) throw new CPRException(Response.Status.CONFLICT, "This course_id already exist.");
+ if (duplicatedCourseDocument != null)
+ throw new CPRException(Response.Status.CONFLICT, "This course_id already exist.");
}
if (originalTeamSize != newTeamSize) {
@@ -124,36 +143,65 @@ public String updateCourse(SecurityContext securityContext, CourseDAO dao) {
return newCourseID;
}
+ /**
+ * Adds a student to the given course, also adds the course to the
+ * student's list of enrolled courses.
+ *
+ * The spin block is to ensure that there is only one request currently working
+ * to add the student to the course. If the spin block is not present, it is possible
+ * that two requests will try to add the student to the course (nearly) at the same time,
+ * which will cause the student to be added twice.
+ *
+ * @param securityContext the content of the application (for professor information)
+ * @param student the student to be added to the course
+ * @param courseID the course to add the student to
+ */
public void addStudent(SecurityContext securityContext, StudentDAO student, String courseID) {
+ while (courseLocks.containsKey(courseID)) ; /* spin block (see explanation above) */
+ courseLocks.put(courseID, true);
String professorID = securityContext.getUserPrincipal().getName().split("@")[0];
String studentId = student.email.split("@")[0];
String studentLastName = student.fullName.split(", ")[0];
String studentFirstName = student.fullName.split(", ")[1];
Document courseDocument = courseCollection.find(and(eq("course_id", courseID), eq("professor_id", professorID))).first();
- if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This course does not exist.");
+ if (courseDocument == null) {
+ courseLocks.remove(courseID);
+ throw new CPRException(Response.Status.NOT_FOUND, "This course does not exist.");
+ }
List students = courseDocument.getList("students", String.class);
- if (students.contains(studentId)) throw new CPRException(Response.Status.CONFLICT, "This student is already in the course.");
+ if (students.contains(studentId)) {
+ courseLocks.remove(courseID);
+ throw new CPRException(Response.Status.CONFLICT, "This student is already in the course.");
+ }
courseCollection.updateOne(eq("course_id", courseID), push("students", studentId));
Document studentDocument = studentCollection.find(eq("student_id", studentId)).first();
- if (studentDocument != null) {
- List courseList = studentDocument.getList("courses", String.class);
- for (String course : courseList) {
- if (course.equals(courseID)) throw new CPRException(Response.Status.CONFLICT, "This student is already in the course.");
- }
- studentCollection.updateOne(eq("student_id", studentId), push("courses", courseID));
- } else {
- List courseList = new ArrayList<>();
- courseList.add(courseID);
- Document newStudent = new Document()
+ boolean studentNotFound = false;
+ if (studentDocument == null) {
+ studentNotFound = true;
+ studentDocument = new Document()
.append("first_name", studentFirstName)
.append("last_name", studentLastName)
.append("student_id", studentId)
- .append("courses", courseList);
- studentCollection.insertOne(newStudent);
+ .append("courses", new ArrayList())
+ .append("team_submissions", new ArrayList())
+ .append("peer_reviews", new ArrayList());
+ }
+
+ List courseList = studentDocument.getList("courses", String.class);
+ boolean isAlreadyEnrolled = courseList.contains(courseID);
+ if (isAlreadyEnrolled) {
+ courseLocks.remove(courseID);
+ throw new CPRException(Response.Status.CONFLICT, "This student is already in the course.");
+ } else {
+ if (studentNotFound) {
+ studentDocument.put("courses", new ArrayList<>(List.of(courseID)));
+ studentCollection.insertOne(studentDocument);
+ } else studentCollection.updateOne(eq("student_id", studentId), push("courses", courseID));
}
+ courseLocks.remove(courseID);
}
public void removeCourse(SecurityContext securityContext, String courseID) {
@@ -168,22 +216,50 @@ public void removeCourse(SecurityContext securityContext, String courseID) {
courseCollection.deleteOne(eq("course_id", courseID));
}
+ /**
+ * Removes a student from the roster of a given course, removes the course from the
+ * student's list of courses, removes the student from the team they are on
+ * within the course (if applicable), "randomly" assigns a new team leader if
+ * the student removed was the leader of a team, and removes the team if the team
+ * is empty after the removal of the student.
+ *
+ * @param securityContext the context of the application (for professor information)
+ * @param studentID the net ID of the student to be removed
+ * @param courseID the ID of the course from which the student is to be removed
+ */
public void removeStudent(SecurityContext securityContext, String studentID, String courseID) {
String professorID = securityContext.getUserPrincipal().getName().split("@")[0];
-
Document studentDocument = studentCollection.find(and(eq("student_id", studentID), eq("courses", courseID))).first();
if (studentDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This student does not exist.");
-
Document courseDocument = courseCollection.find(and(eq("course_id", courseID), eq("professor_id", professorID))).first();
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This course does not exist.");
-
List courses = studentDocument.getList("courses", String.class);
courses.remove(courseID);
studentCollection.updateOne(eq("student_id", studentID), set("courses", courses));
-
List students = courseDocument.getList("students", String.class);
students.remove(studentID);
courseCollection.updateOne(eq("course_id", courseID), set("students", students));
+
+ Document teamDocument = teamCollection.find(and(eq("course_id", courseID), eq("team_members", studentID))).first();
+ if (teamDocument == null) return;
+ int teamSize = teamDocument.getInteger("team_size", -1);
+ boolean isOnlyMember = teamDocument.getInteger("team_size", -1) == 1;
+ boolean isTeamLeader = teamDocument.getString("team_lead").equals(studentID);
+ String teamID = teamDocument.getString("team_id");
+ if (teamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This team does not exist.");
+ List teamMembers = teamDocument.getList("team_members", String.class);
+ if (isOnlyMember) {
+ teamCollection.deleteOne(eq("course_id", courseID));
+ return;
+ }
+ teamMembers.remove(studentID);
+ teamCollection.updateOne(eq("team_id", teamID), set("team_size", teamSize - 1));
+ teamCollection.updateOne(eq("team_id", teamID), set("team_members", teamMembers));
+ if (isTeamLeader) {
+ /* "Randomly" select a new team leader (for now) */
+ String newTeamLeader = teamMembers.get(0);
+ teamCollection.updateOne(eq("team_id", teamID), set("team_leader", newTeamLeader));
+ }
}
public void addStudentsFromCSV(SecurityContext securityContext, FileDAO fileDAO) {
@@ -220,4 +296,21 @@ public void addStudentsFromCSV(SecurityContext securityContext, FileDAO fileDAO)
addStudent(securityContext, student, courseID);
}
}
+
+ public void updateBlockedWordsForCourse(String course_id, String payload) {
+ Document courseDocument = courseCollection.find(eq("course_id", course_id)).first();
+ if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This course does not exist.");
+ Gson gson = new Gson();
+ List blockedWords = gson.fromJson(payload, List.class);
+ courseDocument.put("blocked_words", blockedWords);
+ courseCollection.updateOne(eq("course_id", course_id), set("blocked_words", blockedWords));
+ }
+
+ public String getBlockedWordsForCourse(String course_id) {
+ Document courseDocument = courseCollection.find(eq("course_id", course_id)).first();
+ if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This course does not exist.");
+ Gson gson = new Gson();
+ List blockedWords = courseDocument.getList("blocked_words", String.class);
+ return gson.toJson(blockedWords);
+ }
}
diff --git a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/AdminController.java b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/AdminController.java
new file mode 100644
index 000000000..5d4ac84fc
--- /dev/null
+++ b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/AdminController.java
@@ -0,0 +1,197 @@
+package edu.oswego.cs.resources;
+
+import edu.oswego.cs.database.AdminInterface;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+@Path("admin")
+@DenyAll
+public class AdminController {
+
+ // Delete Admin User by User id and all associated data
+ @RolesAllowed("admin")
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/delete/admin/{user_id}")
+ public Response deleteAdminUser(@Context SecurityContext securityContext, @PathParam("user_id") String userId)
+ throws Exception {
+ new AdminInterface(userId).deleteAdminUser(userId);
+ return Response.status(Response.Status.OK).entity("Admin user deleted.").build();
+ }
+
+ // Delete Student User by User id and all associated data.
+ @RolesAllowed("admin")
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/delete/student/{user_id}")
+ public Response deleteStudentUser(@Context SecurityContext securityContext, @PathParam("user_id") String userId) {
+ new AdminInterface(userId).deleteStudentUser(userId);
+ return Response.status(Response.Status.OK).entity("Student user deleted.").build();
+ }
+
+ // Delete Professor User by User id and all associated data.
+ @RolesAllowed("admin")
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/delete/professor/{user_id}")
+ public Response deleteProfessorUser(@Context SecurityContext securityContext, @PathParam("user_id") String userId) {
+ new AdminInterface(userId).deleteProfessorUser(userId);
+ return Response.status(Response.Status.OK).entity("Professor user deleted.").build();
+ }
+
+ @RolesAllowed("admin")
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/delete/course/{course_id}")
+ public Response deleteCourse(@Context SecurityContext securityContext, @PathParam("course_id") String courseId) {
+ System.out.printf("Deleting course %s", courseId);
+ new AdminInterface(securityContext.getUserPrincipal().getName()).removeCourseAsAdmin(securityContext, courseId);
+ return Response.status(Response.Status.OK).entity("Course deleted.").build();
+ }
+
+ // Add Admin User by User Id, First and Last Name
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/add/admin/{user_id}/{first_name}/{last_name}")
+ public Response addAdminUser(@Context SecurityContext securityContext, @PathParam("user_id") String userId,
+ @PathParam("first_name") String firstName, @PathParam("last_name") String lastName) {
+ new AdminInterface(userId).addAdminUser(firstName, lastName, userId);
+ return Response.status(Response.Status.OK).entity("Admin user added.").build();
+ }
+
+ // Add Student User by User Id, First and Last Name
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/add/student/{user_id}/{first_name}/{last_name}")
+ public Response addStudentUser(@Context SecurityContext securityContext, @PathParam("user_id") String userId,
+ @PathParam("first_name") String firstName, @PathParam("last_name") String lastName) {
+ System.out.println("Adding student user");
+ new AdminInterface(userId).addStudentUser(firstName, lastName, userId);
+ return Response.status(Response.Status.OK).entity("Student user added.").build();
+ }
+
+ // Add Admin User by User Id, First and Last Name
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/add/professor/{user_id}/{first_name}/{last_name}")
+ public Response addProfessorUser(@Context SecurityContext securityContext, @PathParam("user_id") String userId,
+ @PathParam("first_name") String firstName, @PathParam("last_name") String lastName) {
+ new AdminInterface(userId).addProfessorUser(firstName, lastName, userId);
+ return Response.status(Response.Status.OK).entity("Professor user added.").build();
+ }
+
+ // Promote Professor User to Admin User by User Id
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/roles/promote/professorToAdmin/{user_id}")
+ public Response promoteProfessorToAdmin(@Context SecurityContext securityContext,
+ @PathParam("user_id") String userId) {
+ new AdminInterface(userId).promoteProfessorToAdmin(userId);
+ return Response.status(Response.Status.OK).entity("Admin role added to professor user.").build();
+ }
+
+ // Promote Student User to Professor User by User Id
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/roles/promote/studentToProfessor/{user_id}")
+ public Response promoteStudentToProfessor(@Context SecurityContext securityContext,
+ @PathParam("user_id") String userId) {
+ new AdminInterface(userId).promoteStudentToProfessor(userId);
+ return Response.status(Response.Status.OK).entity("Student promoted to professor role.").build();
+ }
+
+ // Promote Student User to Admin User by User Id
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/roles/promote/studentToAdmin/{user_id}")
+ public Response promoteStudentToAdmin(@Context SecurityContext securityContext,
+ @PathParam("user_id") String userId) {
+ new AdminInterface(userId).promoteStudentToAdmin(userId);
+ return Response.status(Response.Status.OK).entity("Student promoted to admin role.").build();
+ }
+
+ // Demote Professor User to Student User by User Id
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/roles/demote/professorToStudent/{user_id}")
+ public Response demoteProfessorToStudent(@Context SecurityContext securityContext,
+ @PathParam("user_id") String userId) {
+ new AdminInterface(userId).demoteProfessorToStudent(userId);
+ return Response.status(Response.Status.OK).entity("Admin role removed from professor user.").build();
+ }
+
+ // Demote Admin User to Professor User by User Id
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/roles/demote/adminToProfessor/{user_id}")
+ public Response demoteAdminToProfessor(@Context SecurityContext securityContext,
+ @PathParam("user_id") String userId) {
+ new AdminInterface(userId).demoteAdminToProfessor(userId);
+ return Response.status(Response.Status.OK).entity("Admin role removed from professor user.").build();
+ }
+
+ // Demote Admin User to Student User by User Id
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/roles/demote/adminToStudent/{user_id}")
+ public Response demoteAdminToStudent(@Context SecurityContext securityContext,
+ @PathParam("user_id") String userId) {
+ new AdminInterface(userId).demoteAdminToStudent(userId);
+ return Response.status(Response.Status.OK).entity("Admin role removed from student user.").build();
+ }
+
+ // Add Blocked Word
+ @RolesAllowed("admin")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/profanity/update")
+ public Response updateBlockedWords(@Context SecurityContext securityContext, @RequestBody String payload) {
+ new AdminInterface().updateBlockedWords(payload);
+ return Response.status(Response.Status.OK).entity("Profanity settings updated.").build();
+ }
+
+ // Get Profanity Settings View
+ @RolesAllowed({"admin","professor"})
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/views/profanity")
+ public Response getProfanitySettingsView(@Context SecurityContext securityContext) {
+ System.out.printf("Getting profanity settings view");
+ return Response.status(Response.Status.OK).entity(new AdminInterface().getBlockedWords()).build();
+ }
+
+ // Get Users View
+ @RolesAllowed("admin")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/views/users")
+ public Response getUsersView(@Context SecurityContext securityContext) {
+ return Response.status(Response.Status.OK).entity(new AdminInterface().getUsersView()).build();
+ }
+
+ // Get Courses view
+ @RolesAllowed("admin")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/views/courses")
+ public Response getCoursesView(@Context SecurityContext securityContext) {
+ return Response.status(Response.Status.OK).entity(new AdminInterface().getCourseView()).build();
+ }
+}
diff --git a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/CourseManagerResource.java b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/CourseManagerResource.java
old mode 100644
new mode 100755
index eb5afed0b..295def22d
--- a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/CourseManagerResource.java
+++ b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/resources/CourseManagerResource.java
@@ -15,6 +15,10 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+
+
+//reset
@Path("professor")
@DenyAll
public class CourseManagerResource {
@@ -59,7 +63,8 @@ public Response addStudent(
@PathParam("courseID") String courseID,
@PathParam("studentInfo") String studentInfo) {
String[] parsedStudentInfo = studentInfo.split("-");
- if (parsedStudentInfo.length < 3) throw new CPRException(Response.Status.BAD_REQUEST, "Add student field was not filled out properly.");
+ if (parsedStudentInfo.length < 3)
+ throw new CPRException(Response.Status.BAD_REQUEST, "Add student field was not filled out properly.");
StudentDAO studentDAO = new StudentDAO(parsedStudentInfo[0], parsedStudentInfo[1], parsedStudentInfo[2]);
new CourseInterface().addStudent(securityContext, studentDAO, courseID);
return Response.status(Response.Status.OK).entity("Student successfully added.").build();
@@ -89,4 +94,21 @@ public Response addStudentByCSVFile(@Context SecurityContext securityContext, IM
new CourseInterface().addStudentsFromCSV(securityContext, fileDAO);
return Response.status(Response.Status.OK).entity("Student(s) successfully added.").build();
}
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("courses/{course_id}/profanity/update")
+ @RolesAllowed("professor")
+ public Response updateBlockedWordsForCourse(@Context SecurityContext securityContext, @PathParam("course_id") String course_id, @RequestBody String payload) {
+ new CourseInterface().updateBlockedWordsForCourse(course_id, payload);
+ return Response.status(Response.Status.OK).entity("Profanity settings updated.").build();
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("courses/{course_id}/profanity/view")
+ @RolesAllowed("professor")
+ public Response getBlockedWordsForCourse(@Context SecurityContext securityContext, @PathParam("course_id") String course_id) {
+ return Response.status(Response.Status.OK).entity(new CourseInterface().getBlockedWordsForCourse(course_id)).build();
+ }
}
\ No newline at end of file
diff --git a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/util/CPRException.java b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/util/CPRException.java
old mode 100644
new mode 100755
diff --git a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/util/CSVUtil.java b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/util/CSVUtil.java
old mode 100644
new mode 100755
diff --git a/backend/course-manager-microservice/src/main/java/edu/oswego/cs/util/CourseUtil.java b/backend/course-manager-microservice/src/main/java/edu/oswego/cs/util/CourseUtil.java
old mode 100644
new mode 100755
diff --git a/backend/course-manager-microservice/src/main/liberty/config/server.xml b/backend/course-manager-microservice/src/main/liberty/config/server.xml
old mode 100644
new mode 100755
diff --git a/backend/course-manager-microservice/src/test/java/CourseManagerTests.java b/backend/course-manager-microservice/src/test/java/CourseManagerTests.java
old mode 100644
new mode 100755
index 6029f1e86..a68c4ef4f
--- a/backend/course-manager-microservice/src/test/java/CourseManagerTests.java
+++ b/backend/course-manager-microservice/src/test/java/CourseManagerTests.java
@@ -35,8 +35,8 @@ public class CourseManagerTests {
@BeforeAll
public static void oneTimeSetup() {
- port = "13125";
- baseUrl = "http://moxie.cs.oswego.edu:" + port + "/manage/professor/";
+ port = "3000";
+ baseUrl = "https://localhost:" + port + "/manage/professor/";
String courseName = "JUnit Theory";
String courseSection = "800";
String crn = "54266";
diff --git a/backend/course-viewer-microservice/.dockerignore b/backend/course-viewer-microservice/.dockerignore
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/Dockerfile b/backend/course-viewer-microservice/Dockerfile
old mode 100644
new mode 100755
index 35572be80..1c115bd2b
--- a/backend/course-viewer-microservice/Dockerfile
+++ b/backend/course-viewer-microservice/Dockerfile
@@ -11,4 +11,4 @@ FROM icr.io/appcafe/open-liberty:full-java11-openj9-ubi
# Copy from the intermediate build container.
COPY --from=maven src/main/liberty/config/server.xml /config/
-COPY --from=maven target/*.war /config/apps
+COPY --from=maven target/*.war /config/apps
\ No newline at end of file
diff --git a/backend/course-viewer-microservice/course-viewer-microservice.iml b/backend/course-viewer-microservice/course-viewer-microservice.iml
new file mode 100644
index 000000000..8c54e3b0b
--- /dev/null
+++ b/backend/course-viewer-microservice/course-viewer-microservice.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/course-viewer-microservice/pom.xml b/backend/course-viewer-microservice/pom.xml
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/application/CourseViewerApplication.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/application/CourseViewerApplication.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/daos/AssignmentDAO.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/daos/AssignmentDAO.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/daos/CourseDAO.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/daos/CourseDAO.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/daos/StudentDAO.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/daos/StudentDAO.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/database/CourseInterface.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/database/CourseInterface.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/database/GradeInterface.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/database/GradeInterface.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/resources/CoursesViewerResources.java b/backend/course-viewer-microservice/src/main/java/edu/oswego/cs/resources/CoursesViewerResources.java
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/main/liberty/config/server.xml b/backend/course-viewer-microservice/src/main/liberty/config/server.xml
old mode 100644
new mode 100755
diff --git a/backend/course-viewer-microservice/src/test/java/edu/oswego/edu/CoursesViewerTest.java b/backend/course-viewer-microservice/src/test/java/edu/oswego/edu/CoursesViewerTest.java
old mode 100644
new mode 100755
index 798e6ccac..a87b697bd
--- a/backend/course-viewer-microservice/src/test/java/edu/oswego/edu/CoursesViewerTest.java
+++ b/backend/course-viewer-microservice/src/test/java/edu/oswego/edu/CoursesViewerTest.java
@@ -34,7 +34,7 @@ public class CoursesViewerTest {
@BeforeAll
public static void oneTimeSetup() {
port = "13125";
- baseUrl = "http://moxie.cs.oswego.edu:" + port + "/view/professor/";
+ baseUrl = "https://moxie.cs.oswego.edu:" + port + "/view/professor/";
// variables for inserted courses
diff --git a/backend/login-microservice/.dockerignore b/backend/login-microservice/.dockerignore
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/Dockerfile b/backend/login-microservice/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/login-microservice.iml b/backend/login-microservice/login-microservice.iml
new file mode 100644
index 000000000..8c54e3b0b
--- /dev/null
+++ b/backend/login-microservice/login-microservice.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/login-microservice/pom.xml b/backend/login-microservice/pom.xml
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/application/RestApplication.java b/backend/login-microservice/src/main/java/edu/oswego/cs/application/RestApplication.java
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/controllers/Controller.java b/backend/login-microservice/src/main/java/edu/oswego/cs/controllers/Controller.java
old mode 100644
new mode 100755
index eacde164f..f3dfa2807
--- a/backend/login-microservice/src/main/java/edu/oswego/cs/controllers/Controller.java
+++ b/backend/login-microservice/src/main/java/edu/oswego/cs/controllers/Controller.java
@@ -31,6 +31,7 @@ public Response generateToken(@Context HttpHeaders request) throws JsonException
@Produces(MediaType.APPLICATION_JSON)
public Response refreshToken(@Context SecurityContext securityContext) throws IOException {
return Response.status(Response.Status.OK).entity(new AuthServices().refreshToken(securityContext)).build();
+
}
}
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java b/backend/login-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java b/backend/login-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/database/ProfessorCheck.java b/backend/login-microservice/src/main/java/edu/oswego/cs/database/ProfessorCheck.java
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/services/AuthServices.java b/backend/login-microservice/src/main/java/edu/oswego/cs/services/AuthServices.java
old mode 100644
new mode 100755
index a0f987560..1399bbe70
--- a/backend/login-microservice/src/main/java/edu/oswego/cs/services/AuthServices.java
+++ b/backend/login-microservice/src/main/java/edu/oswego/cs/services/AuthServices.java
@@ -8,7 +8,6 @@
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import edu.oswego.cs.database.DatabaseManager;
-import edu.oswego.cs.database.ProfessorCheck;
import edu.oswego.cs.util.CPRException;
import org.bson.Document;
import org.eclipse.microprofile.jwt.JsonWebToken;
@@ -19,7 +18,6 @@
import java.io.IOException;
import java.security.Principal;
import java.util.*;
-
import static com.mongodb.client.model.Filters.eq;
public class AuthServices {
@@ -34,17 +32,21 @@ public AuthServices() throws IOException {
} catch (WebApplicationException e) {
throw new CPRException(Response.Status.BAD_REQUEST, "Failed to retrieve collections.");
}
- new ProfessorCheck().addProfessors();
}
public Map generateNewToken(String token) {
Payload payload = googleService.validateToken(token);
if (payload == null)
- throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).entity("Invalid token.").build());
+ throw new WebApplicationException(
+ Response.status(Response.Status.UNAUTHORIZED).entity("Invalid token.").build());
Map tokens = new HashMap<>();
String lakerID = payload.getEmail().split("@")[0];
+ CheckForDefaultAdmin(lakerID, payload.get("given_name").toString(), payload.get("family_name").toString());
+
+
+ System.out.println("lakerID: " + lakerID);
Set roles = getRoles(lakerID);
try {
@@ -66,6 +68,7 @@ public Map generateNewToken(String token) {
.claim("aud", "cpr")
.claim("iss", "cpr")
.buildJwt().compact();
+ System.out.println("access token: " + access_token);
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
@@ -74,19 +77,24 @@ public Map generateNewToken(String token) {
} catch (JwtException | InvalidBuilderException | InvalidClaimException e) {
e.printStackTrace();
- throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).entity("Unable to find token.").build());
+ throw new WebApplicationException(
+ Response.status(Response.Status.NOT_FOUND).entity("Unable to find token.").build());
}
}
public Map refreshToken(SecurityContext securityContext) {
+ System.out.println("refreshing token");
Principal user = securityContext.getUserPrincipal();
JsonWebToken payload = (JsonWebToken) user;
if (payload == null)
- throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).entity("JWT is not available.").build());
+ throw new WebApplicationException(
+ Response.status(Response.Status.UNAUTHORIZED).entity("JWT is not available.").build());
Map tokens = new HashMap<>();
String lakerID = payload.getName().split("@")[0];
+
+ // CheckForDefaultAdmin(lakerID, payload.getClaim(), payload.getLastname());
Set roles = getRoles(lakerID);
try {
@@ -102,24 +110,43 @@ public Map refreshToken(SecurityContext securityContext) {
tokens.put("access_token", access_token);
} catch (JwtException | InvalidBuilderException | InvalidClaimException e) {
e.printStackTrace();
- throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).entity("Unable to find token.").build());
+ throw new WebApplicationException(
+ Response.status(Response.Status.NOT_FOUND).entity("Unable to find token.").build());
}
return tokens;
}
public Set getRoles(String lakerID) {
+
Set roles = new HashSet<>();
if (professorCollection.find(eq("professor_id", lakerID)).first() != null) {
roles.add("professor");
+ Document professorDoc = professorCollection.find(eq("professor_id", lakerID)).first();
+ if (professorDoc != null && professorDoc.containsKey("admin") && professorDoc.getBoolean("admin")) {
+ roles.add("admin");
+ }
} else {
roles.add("student");
}
- if (roles.size() == 0)
- throw new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Can't connect to database.").build());
return roles;
+
}
+ // Checks if professor database is empty
+ // if so assign admin status to first professor
+ protected void CheckForDefaultAdmin(String user_id, String first_name, String last_name) {
+ System.out.println("Checking for default admin");
+ if (professorCollection.countDocuments() == 0) {
+ System.out.println("Adding default admin" + user_id);
+ Document professor = new Document("professor_id", user_id)
+ .append("admin", true)
+ .append("first_name", first_name)
+ .append("last_name", last_name)
+ .append("courses", new ArrayList());
+ professorCollection.insertOne(professor);
+ }
+ }
}
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/services/GoogleService.java b/backend/login-microservice/src/main/java/edu/oswego/cs/services/GoogleService.java
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/java/edu/oswego/cs/util/CPRException.java b/backend/login-microservice/src/main/java/edu/oswego/cs/util/CPRException.java
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/main/liberty/config/server.xml b/backend/login-microservice/src/main/liberty/config/server.xml
old mode 100644
new mode 100755
diff --git a/backend/login-microservice/src/test/java/LoginTests.java b/backend/login-microservice/src/test/java/LoginTests.java
old mode 100644
new mode 100755
index 1b0b7366a..6a31fb65c
--- a/backend/login-microservice/src/test/java/LoginTests.java
+++ b/backend/login-microservice/src/test/java/LoginTests.java
@@ -18,7 +18,7 @@ public class LoginTests {
@BeforeAll
public static void onTimeSetup(){
- baseUrl = "http://moxie.cs.oswego.edu:13125/auth/token/generate/";
+ baseUrl = "https://moxie.cs.oswego.edu:13125/auth/token/generate/";
// base jwt token found by intercepting the request in Burp ;D
// we can manipulate this to see what else we can do with this request. XD
diff --git a/backend/peer-review-teams-microservice/.dockerignore b/backend/peer-review-teams-microservice/.dockerignore
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/Dockerfile b/backend/peer-review-teams-microservice/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/pom.xml b/backend/peer-review-teams-microservice/pom.xml
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/application/RestApplication.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/application/RestApplication.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/daos/TeamDAO.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/daos/TeamDAO.java
old mode 100644
new mode 100755
index c1badf11a..bba3ca574
--- a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/daos/TeamDAO.java
+++ b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/daos/TeamDAO.java
@@ -35,4 +35,12 @@ public TeamDAO(
this.teamMembers = new ArrayList<>();
this.teamSize = teamSize;
}
-}
\ No newline at end of file
+ public TeamDAO(String teamName, String courseID, int teamSize) {
+ this.teamID = null;
+ this.courseID = courseID;
+ this.teamFull = false;
+ this.teamLead = null;
+ this.teamMembers = new ArrayList<>();
+ this.teamSize = teamSize;
+ }
+}
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/database/TeamInterface.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/database/TeamInterface.java
old mode 100644
new mode 100755
index f85de579c..0ac56a8ed
--- a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/database/TeamInterface.java
+++ b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/database/TeamInterface.java
@@ -25,7 +25,9 @@
import javax.ws.rs.core.SecurityContext;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
public class TeamInterface {
@@ -33,27 +35,58 @@ public class TeamInterface {
private final MongoCollection studentCollection;
private final MongoCollection teamCollection;
+ private final MongoCollection submissionCollection;
+
public TeamInterface() {
DatabaseManager databaseManager = new DatabaseManager();
try {
MongoDatabase studentDB = databaseManager.getStudentDB();
MongoDatabase courseDB = databaseManager.getCourseDB();
MongoDatabase teamDB = databaseManager.getTeamDB();
+ MongoDatabase assignmentDB = databaseManager.getAssignmentDB();
studentCollection = studentDB.getCollection("students");
courseCollection = courseDB.getCollection("courses");
teamCollection = teamDB.getCollection("teams");
+ submissionCollection = assignmentDB.getCollection("submissions");
} catch (CPRException e) {
throw new CPRException(Response.Status.INTERNAL_SERVER_ERROR, "Failed to retrieve collections.");
}
}
-
- public void createTeam(@Context SecurityContext securityContext, TeamParam request) {
+// public void createTeam(@Context SecurityContext securityContext, TeamParam request) {
+// Document courseDocument = courseCollection.find(eq("course_id", request.getCourseID())).first();
+// if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found");
+// new IdentifyingService().identifyingStudentService(securityContext, request.getStudentID());
+// new IdentifyingService().identifyingProfessorAsStudentService(securityContext, courseCollection, request.getCourseID());
+// new SecurityService().generateTeamNameSecurity(securityContext, teamCollection, courseDocument, request);
+//
+// if (request.getTeamName().length() > 25 || request.getTeamName().length() <= 0) {
+// throw new CPRException(Response.Status.INTERNAL_SERVER_ERROR, "Team name must be within 1 and 25 characters long.");
+// }
+//
+// int teamSize = new TeamService().getTeamSize(courseDocument);
+// TeamDAO newTeam = new TeamDAO(request.getTeamName(), request.getCourseID(), teamSize, request.getStudentID());
+// newTeam.getTeamMembers().add(request.getStudentID());
+// newTeam.setTeamMembers(newTeam.getTeamMembers());
+//
+// if (teamSize == 1) newTeam.setTeamFull(true);
+//
+// Jsonb jsonb = JsonbBuilder.create();
+// Entity courseDAOEntity = Entity.entity(jsonb.toJson(newTeam), MediaType.APPLICATION_JSON_TYPE);
+// Document teamDocument = Document.parse(courseDAOEntity.getEntity());
+// teamCollection.insertOne(teamDocument);
+// }
+
+ public void createTeam(@Context SecurityContext securityContext, TeamParam request) {
Document courseDocument = courseCollection.find(eq("course_id", request.getCourseID())).first();
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found");
new IdentifyingService().identifyingStudentService(securityContext, request.getStudentID());
- new IdentifyingService().identifyingProfessorService(securityContext, courseCollection, request.getCourseID());
+ new IdentifyingService().identifyingProfessorAsStudentService(securityContext, courseCollection, request.getCourseID());
new SecurityService().generateTeamNameSecurity(securityContext, teamCollection, courseDocument, request);
+ if (request.getTeamName().length() > 25 || request.getTeamName().length() <= 0) {
+ throw new CPRException(Response.Status.INTERNAL_SERVER_ERROR, "Team name must be within 1 and 25 characters long.");
+ }
+
int teamSize = new TeamService().getTeamSize(courseDocument);
TeamDAO newTeam = new TeamDAO(request.getTeamName(), request.getCourseID(), teamSize, request.getStudentID());
newTeam.getTeamMembers().add(request.getStudentID());
@@ -67,6 +100,36 @@ public void createTeam(@Context SecurityContext securityContext, TeamParam reque
teamCollection.insertOne(teamDocument);
}
+ public void createTeamProffessor(@Context SecurityContext securityContext, TeamParam request, String studentID) {
+ Document courseDocument = courseCollection.find(eq("course_id", request.getCourseID())).first();
+ if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found");
+ new IdentifyingService().identifyingProfessorAsStudentService(securityContext, courseCollection, request.getCourseID());
+ new SecurityService().generateTeamNameSecurity(securityContext, teamCollection, courseDocument, request);
+
+ if (request.getTeamName().length() > 25 || request.getTeamName().length() <= 0) {
+ throw new CPRException(Response.Status.INTERNAL_SERVER_ERROR, "Team name must be within 1 and 25 characters long.");
+ }
+
+ if (!new SecurityService().isStudentValid(courseDocument, studentID)) {
+ throw new CPRException(Response.Status.NOT_FOUND, "Student not found in this course.");
+ }
+
+ int teamSize = new TeamService().getTeamSize(courseDocument);
+ TeamDAO newTeam = new TeamDAO(request.getTeamName(), request.getCourseID(), teamSize);
+
+ new IdentifyingService().identifyingStudentService(securityContext, studentID);
+ newTeam.getTeamMembers().add(studentID);
+
+ newTeam.setTeamMembers(newTeam.getTeamMembers());
+
+ if (teamSize == 1) newTeam.setTeamFull(true);
+
+ Jsonb jsonb = JsonbBuilder.create();
+ Entity courseDAOEntity = Entity.entity(jsonb.toJson(newTeam), MediaType.APPLICATION_JSON_TYPE);
+ Document teamDocument = Document.parse(courseDAOEntity.getEntity());
+ teamCollection.insertOne(teamDocument);
+ }
+
public List getAllTeams(SecurityContext securityContext, String courseID) {
Document courseDocument = courseCollection.find(eq("course_id", courseID)).first();
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found");
@@ -92,7 +155,7 @@ public Document getTeamByStudentID(SecurityContext securityContext, String cours
if (!new SecurityService().isStudentValid(courseDocument, studentID))
throw new CPRException(Response.Status.NOT_FOUND, "Student not found in this course.");
new IdentifyingService().identifyingStudentService(securityContext, studentID);
- new IdentifyingService().identifyingProfessorService(securityContext, courseCollection, courseID);
+ new IdentifyingService().identifyingProfessorAsStudentService(securityContext, courseCollection, courseID);
if (new SecurityService().isStudentAlreadyInATeam(teamCollection, securityContext, studentID, courseID)) {
MongoCursor cursor = teamCollection.find(eq("course_id", courseID)).iterator();
@@ -120,7 +183,7 @@ public Document getTeamByTeamID(SecurityContext securityContext, String courseID
if (!new SecurityService().isStudentInThisTeam(teamCollection, teamID, userID, courseID))
throw new CPRException(Response.Status.FORBIDDEN, "Principal User is not in this team.");
- Bson teamDocumentFilter = Filters.and(eq("team_id", teamID), eq("course_id", courseID));
+ Bson teamDocumentFilter = and(eq("team_id", teamID), eq("course_id", courseID));
return teamCollection.find(teamDocumentFilter).first();
}
@@ -139,10 +202,10 @@ public void joinTeam(SecurityContext securityContext, TeamParam request) {
Document courseDocument = courseCollection.find(eq("course_id", request.getCourseID())).first();
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new IdentifyingService().identifyingStudentService(securityContext, request.getStudentID());
- new IdentifyingService().identifyingProfessorService(securityContext, courseCollection, request.getCourseID());
+ new IdentifyingService().identifyingProfessorAsStudentService(securityContext, courseCollection, request.getCourseID());
new SecurityService().joinTeamSecurity(securityContext, teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
List teamMembers = teamDocument.getList("team_members", String.class);
@@ -152,6 +215,41 @@ public void joinTeam(SecurityContext securityContext, TeamParam request) {
teamUpdates = Updates.combine(teamUpdates, Updates.set("team_full", true));
UpdateOptions teamOptions = new UpdateOptions().upsert(true);
teamCollection.updateOne(teamDocumentFilter, teamUpdates, teamOptions);
+ //now add the student's user id to all submissions the new team has made for the current course
+
+ //first get the regular assignments and modify them
+ for (Document currentSubmission : submissionCollection.find(and(eq("type", "team_submission"), eq("course_id", request.getCourseID()), eq("team_name", request.getTeamID())))) {
+ //modify currentSubmission's members array to include the new member's ID
+ List curMembers = currentSubmission.getList("members", String.class);
+ curMembers.add(request.getStudentID());
+ Bson submissionUpdate = Updates.set("members", curMembers);
+ UpdateOptions submissionOptions = new UpdateOptions().upsert(true);
+ submissionCollection.updateOne(submissionCollection.find(and(eq("assignment_id", currentSubmission.get("assignment_id")), eq("type", "team_submission"), eq("team_name", currentSubmission.get("team_name")))).first(), submissionUpdate, submissionOptions);
+
+ }
+
+
+ //now get and modify peer reviews that the team submitted
+ for (Document currentSubmission : submissionCollection.find(and(eq("type", "peer_review_submission"), eq("course_id", request.getCourseID()), eq("reviewed_by", request.getTeamID())))) {
+ //modify currentSubmission's members array to include the new member's ID
+ List curMembers = currentSubmission.getList("reviewed_by_members", String.class);
+ curMembers.add(request.getStudentID());
+ Bson submissionUpdate = Updates.set("reviewed_by_members", curMembers);
+ UpdateOptions submissionOptions = new UpdateOptions().upsert(true);
+ submissionCollection.updateOne(Objects.requireNonNull(submissionCollection.find(and(eq("assignment_id", currentSubmission.get("assignment_id")), eq("type", "peer_review_submission"), eq("reviewed_team", currentSubmission.get("reviewed_team")), eq("reviewed_by", currentSubmission.get("reviewed_by")))).first()), submissionUpdate, submissionOptions);
+ }
+
+
+ //now get and modify peer reviews that the team received
+ for (Document currentSubmission : submissionCollection.find(and(eq("type", "peer_review_submission"), eq("course_id", request.getCourseID()), eq("reviewed_team", request.getTeamID())))) {
+ //modify currentSubmission's members array to include the new member's ID
+ List curMembers = currentSubmission.getList("reviewed_team_members", String.class);
+ curMembers.add(request.getStudentID());
+ Bson submissionUpdate = Updates.set("reviewed_team_members", curMembers);
+ UpdateOptions submissionOptions = new UpdateOptions().upsert(true);
+ submissionCollection.updateOne(Objects.requireNonNull(submissionCollection.find(and(eq("assignment_id", currentSubmission.get("assignment_id")), eq("type", "peer_review_submission"), eq("reviewed_team", currentSubmission.get("reviewed_team")), eq("reviewed_by", currentSubmission.get("reviewed_by")))).first()), submissionUpdate, submissionOptions);
+ }
+
}
/* Deprecated */
@@ -160,7 +258,7 @@ public void switchTeam(SwitchTeamParam request) {
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().switchTeamSecurity(teamCollection, courseDocument, request);
- Bson currentTeamDocumentFilter = Filters.and(eq("team_id", request.getCurrentTeamID()), eq("course_id", request.getCourseID()));
+ Bson currentTeamDocumentFilter = and(eq("team_id", request.getCurrentTeamID()), eq("course_id", request.getCourseID()));
Document currentTeamDocument = teamCollection.find(currentTeamDocumentFilter).first();
if (currentTeamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -183,7 +281,7 @@ public void switchTeam(SwitchTeamParam request) {
teamCollection.updateOne(currentTeamDocumentFilter, currentTeamUpdates, currentTeamOptions);
}
- Bson targetTeamDocumentFilter = Filters.and(eq("team_id", request.getTargetTeamID()), eq("course_id", request.getCourseID()));
+ Bson targetTeamDocumentFilter = and(eq("team_id", request.getTargetTeamID()), eq("course_id", request.getCourseID()));
Document targetTeamDocument = teamCollection.find(targetTeamDocumentFilter).first();
if (targetTeamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -203,7 +301,7 @@ public void giveUpTeamLead(TeamParam request) {
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().giveUpTeamLeadSecurity(teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
if (teamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -230,7 +328,7 @@ public void nominateTeamLead(TeamParam request) {
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().nominateTeamLeadSecurity(teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
if (teamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -259,7 +357,7 @@ public void memberConfirmToggle(TeamParam request) {
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().memberConfirmToggleSecurity(teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
List teamConfirmedMembers = teamDocument.getList("team_confirmed_members", String.class);
@@ -277,7 +375,7 @@ public void generateTeamName(SecurityContext securityContext, TeamParam request)
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().generateTeamNameSecurity(securityContext, teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Bson teamNameUpdates = Updates.combine(
Updates.set("team_id", request.getTeamName()),
Updates.set("team_lock", true));
@@ -291,7 +389,7 @@ public void removeTeamMember(@Context SecurityContext securityContext, TeamParam
new IdentifyingService().identifyingProfessorService(securityContext, courseCollection, request.getCourseID());
new SecurityService().removeTeamMemberSecurity(teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
if (teamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -316,7 +414,7 @@ public void toggleTeamLock(TeamParam request) {
if (!new SecurityService().isTeamCreated(teamCollection, request.getTeamID(), request.getCourseID()))
throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
if (teamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -338,7 +436,7 @@ public void editTeamName(TeamParam request) {
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().editTeamNameSecurity(teamCollection, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Bson editTeamNameUpdates = Updates.combine(
Updates.set("team_id", request.getTeamName()),
Updates.set("team_lock", true)
@@ -358,7 +456,7 @@ public void editTeamSize(TeamParam request) {
Document courseDocument = courseCollection.find(eq("course_id", request.getCourseID())).first();
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().editTeamSizeSecurity(teamCollection, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Bson editTeamSizeUpdates = Updates.set("team_size", request.getTeamSize());
teamCollection.updateOne(teamDocumentFilter, editTeamSizeUpdates);
}
@@ -368,7 +466,7 @@ public void assignTeamLead(TeamParam request) {
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Course not found.");
new SecurityService().assignTeamLeadSecurity(teamCollection, courseDocument, request);
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
Document teamDocument = teamCollection.find(teamDocumentFilter).first();
if (teamDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
@@ -397,7 +495,7 @@ public void deleteTeam(SecurityContext securityContext, TeamParam request) {
new IdentifyingService().identifyingProfessorService(securityContext, courseCollection, request.getCourseID());
if (!new SecurityService().isTeamCreated(teamCollection, request.getTeamID(), request.getCourseID()))
throw new CPRException(Response.Status.NOT_FOUND, "Team not found.");
- Bson teamDocumentFilter = Filters.and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
+ Bson teamDocumentFilter = and(eq("team_id", request.getTeamID()), eq("course_id", request.getCourseID()));
teamCollection.deleteOne(teamDocumentFilter);
}
@@ -413,4 +511,4 @@ public List getAllStudentsInThisCourse(String courseID) {
}
return students;
}
-}
\ No newline at end of file
+}
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/requests/SwitchTeamParam.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/requests/SwitchTeamParam.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/requests/TeamParam.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/requests/TeamParam.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/resources/ProfessorTeamResources.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/resources/ProfessorTeamResources.java
old mode 100644
new mode 100755
index 2d5e557bf..3cd4d6f5f
--- a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/resources/ProfessorTeamResources.java
+++ b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/resources/ProfessorTeamResources.java
@@ -3,6 +3,7 @@
import javax.annotation.security.DenyAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
@@ -20,6 +21,18 @@
@Path("teams/professor/team")
@DenyAll
public class ProfessorTeamResources {
+ /**
+ * Endpoint to create course by proffessor
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{student_id}/create")
+ @RolesAllowed("professor")
+ public Response createTeamProffessor(@Context SecurityContext securityContext, TeamParam request, @PathParam("student_id") String studentID) {
+ new TeamInterface().createTeamProffessor(securityContext, request, studentID);
+ return Response.status(Response.Status.CREATED).entity("Team successfully created.").build();
+ }
@GET
@Produces(MediaType.APPLICATION_JSON)
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/resources/StudentTeamResources.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/resources/StudentTeamResources.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/IdentifyingService.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/IdentifyingService.java
old mode 100644
new mode 100755
index eb3bc8288..de67ae487
--- a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/IdentifyingService.java
+++ b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/IdentifyingService.java
@@ -7,6 +7,7 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
+import java.util.ArrayList;
public class IdentifyingService {
public void identifyingStudentService(SecurityContext securityContext, String studentID) {
@@ -17,14 +18,30 @@ public void identifyingStudentService(SecurityContext securityContext, String st
}
}
- public void identifyingProfessorService(SecurityContext securityContext, MongoCollection courseCollection, String courseID) {
+ public void identifyingProfessorAsStudentService(SecurityContext securityContext, MongoCollection courseCollection, String courseID) {
if (securityContext.isUserInRole("professor")) {
String userID = securityContext.getUserPrincipal().getName().split("@")[0];
Document courseDocument = courseCollection.find(Filters.eq("course_id", courseID)).first();
if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This professor does not exist.");
String professorID = courseDocument.getString("professor_id");
- if (!userID.equals(professorID))
+ if (!userID.equals(professorID)) {
+ ArrayList students = (ArrayList) courseDocument.get("students");
+ for(String student: students){
+ if(student.equals(userID))
+ return;
+ }
throw new CPRException(Response.Status.FORBIDDEN, "User principal name doesn't match");
+ }
+ }
+ }
+
+ public void identifyingProfessorService(SecurityContext securityContext, MongoCollection courseCollection, String courseID) {
+ if (securityContext.isUserInRole("professor")) {
+ String userID = securityContext.getUserPrincipal().getName().split("@")[0];
+ Document courseDocument = courseCollection.find(Filters.eq("course_id", courseID)).first();
+ if (courseDocument == null) throw new CPRException(Response.Status.NOT_FOUND, "This course does not exist.");
+ String professorID = courseDocument.getString("professor_id");
+ if (!userID.equals(professorID)) throw new CPRException(Response.Status.FORBIDDEN, "User principal name doesn't match");
}
}
}
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/SecurityService.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/SecurityService.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/TeamService.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/services/TeamService.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/util/CPRException.java b/backend/peer-review-teams-microservice/src/main/java/edu/oswego/cs/util/CPRException.java
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/main/liberty/config/server.xml b/backend/peer-review-teams-microservice/src/main/liberty/config/server.xml
old mode 100644
new mode 100755
diff --git a/backend/peer-review-teams-microservice/src/test/java/TeamsTests.java b/backend/peer-review-teams-microservice/src/test/java/TeamsTests.java
old mode 100644
new mode 100755
index d10b2ac3f..f863ab79b
--- a/backend/peer-review-teams-microservice/src/test/java/TeamsTests.java
+++ b/backend/peer-review-teams-microservice/src/test/java/TeamsTests.java
@@ -1,2 +1,38 @@
-public class TeamsTests {
-}
+import edu.oswego.cs.daos.CourseDAO;
+import edu.oswego.cs.daos.StudentDAO;
+import edu.oswego.cs.daos.TeamDAO;
+import edu.oswego.cs.database.CourseInterface;
+import edu.oswego.cs.database.TeamInterface;
+import edu.oswego.cs.requests.TeamParam;
+
+ public class TeamsTests {
+ @Test
+ /**
+ * Test case for createTeamProffessor
+ * Creates test parameters by:
+ * Create a test courese, add student to test course,Create team parameter with studentID and teamName,Create security context
+ * Calls createTeamProffessor with created test paramaters
+ * Verify team created and team member inserted
+ */
+ public void testTeamCreation() {
+ CourseDAO course = new CourseDAO("CSC578", "Software Engineering", "800", "12345", "Fall", "2024");
+ CourseInterface courseInterface = new CourseInterface();
+ courseInterface.addCourse(null, course);
+ String email = "timmyTest@oswego.edu";
+ StudentDAO studentDAO = new StudentDAO(email, course.abbreviation, course.courseName, course.courseSection,
+ course.crn, course.semester, course.year);
+ courseInterface.addStudent(null, studentDAO, course.courseID);
+ TeamParam teamParam = new TeamParam();
+ teamParam.setCourseID(course.courseID);
+ teamParam.setTeamName("Test Team");
+ String studentID = email;
+ SecurityContext securityContext = mock(SecurityContext.class);
+ TeamInterface teamInterface = new TeamInterface();
+ teamInterface.createTeamProffessor(securityContext, teamParam, studentID);
+ TeamDAO insertedTeam = teamInterface.getTeamsByCourseID(course.courseID).get(0);
+ assertEquals(teamParam.getTeamName(), insertedTeam.getTeamName());
+ assertEquals(teamParam.getCourseID(), insertedTeam.getCourseID());
+ assertEquals(1, insertedTeam.getTeamMembers().size());
+ assertTrue(insertedTeam.getTeamMembers().contains(studentID));
+ }
+ }
diff --git a/backend/professor-assignment-microservice/.dockerignore b/backend/professor-assignment-microservice/.dockerignore
old mode 100644
new mode 100755
diff --git a/backend/professor-assignment-microservice/Dockerfile b/backend/professor-assignment-microservice/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/professor-assignment-microservice/pom.xml b/backend/professor-assignment-microservice/pom.xml
old mode 100644
new mode 100755
index 0cf151ba6..a0771fc49
--- a/backend/professor-assignment-microservice/pom.xml
+++ b/backend/professor-assignment-microservice/pom.xml
@@ -15,7 +15,25 @@
UTF-8
+
+
+ MavenCentralRepo
+ Maven Central Repository
+ https://repo1.maven.org/maven2/
+
+
+ AsposeJavaAPI
+ Aspose Java API
+ https://repository.aspose.com/repo/
+
+
+
+
+ com.aspose
+ aspose-pdf
+ 20.7
+
org.mongodb
mongo-java-driver
@@ -46,7 +64,7 @@
com.ibm.websphere.appserver.api
com.ibm.websphere.appserver.api.jaxrs20
- 1.1.54
+ 1.1.74
provided
@@ -83,6 +101,11 @@
1.0.0-RC1
runtime
+
+ com.ibm.websphere.appserver.spi
+ com.ibm.websphere.appserver.spi.logging
+ 1.1.74
+
jakarta.platform
jakarta.jakartaee-api
@@ -99,6 +122,11 @@
maven-war-plugin
3.3.2
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
io.openliberty.tools
liberty-maven-plugin
diff --git a/backend/professor-assignment-microservice/professor-assignment-microservice.iml b/backend/professor-assignment-microservice/professor-assignment-microservice.iml
new file mode 100644
index 000000000..8c54e3b0b
--- /dev/null
+++ b/backend/professor-assignment-microservice/professor-assignment-microservice.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/application/ProfessorAssignmentApplication.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/application/ProfessorAssignmentApplication.java
old mode 100644
new mode 100755
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/cors/CorsFilter.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/cors/CorsFilter.java
old mode 100644
new mode 100755
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentDAO.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentDAO.java
old mode 100644
new mode 100755
index 54f89413a..6a084b0d4
--- a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentDAO.java
+++ b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentDAO.java
@@ -2,6 +2,10 @@
import lombok.NoArgsConstructor;
import lombok.NonNull;
+import org.bson.BsonBinary;
+import org.bson.codecs.pojo.annotations.BsonId;
+import org.bson.conversions.Bson;
+
import javax.json.bind.annotation.JsonbCreator;
import javax.json.bind.annotation.JsonbProperty;
import javax.persistence.Entity;
@@ -21,10 +25,6 @@ public class AssignmentDAO {
@JsonbProperty("peer_review_instructions") public String peerReviewInstructions;
@JsonbProperty("peer_review_due_date") public String peerReviewDueDate;
@JsonbProperty("peer_review_points") public int peerReviewPoints;
- @JsonbProperty("assignment_instructions") public String assignmentInstruction = "";
- @JsonbProperty("peer_review_template") public String peerReviewTemplate = "";
- @JsonbProperty("peer_review_rubric") public String peerReviewRubric = "";
-
@JsonbCreator
public AssignmentDAO(
@NonNull @JsonbProperty("course_id") String courseID,
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentNoPeerReviewDAO.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentNoPeerReviewDAO.java
new file mode 100755
index 000000000..011fdd45f
--- /dev/null
+++ b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/AssignmentNoPeerReviewDAO.java
@@ -0,0 +1,32 @@
+package edu.oswego.cs.rest.daos;
+
+import lombok.NonNull;
+
+import javax.json.bind.annotation.JsonbCreator;
+import javax.json.bind.annotation.JsonbProperty;
+import javax.persistence.Id;
+
+public class AssignmentNoPeerReviewDAO {
+ @Id
+ @JsonbProperty("course_id") public String courseID;
+ @JsonbProperty("assignment_name") public String assignmentName;
+ @JsonbProperty("assignment_id") public int assignmentID;
+ @JsonbProperty("instructions") public String instructions;
+ @JsonbProperty("due_date") public String dueDate;
+ @JsonbProperty("points") public int points;
+ @JsonbCreator
+ public AssignmentNoPeerReviewDAO(
+ @NonNull @JsonbProperty("course_id") String courseID,
+ @NonNull @JsonbProperty("assignment_name") String assignmentName,
+ @NonNull @JsonbProperty("instructions") String instructions,
+ @NonNull @JsonbProperty("due_date") String dueDate,
+ @NonNull @JsonbProperty("points") Integer points
+ )
+ {
+ this.assignmentName = assignmentName;
+ this.courseID = courseID;
+ this.dueDate = dueDate;
+ this.instructions = instructions;
+ this.points = points;
+ }
+}
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java
old mode 100644
new mode 100755
index 168c0d4b4..349a128c6
--- a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java
+++ b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java
@@ -19,16 +19,6 @@ public class FileDAO {
@JsonbProperty public String courseID;
@JsonbProperty public InputStream file;
- @JsonbCreator
- public FileDAO(
- @NonNull @JsonbProperty("file_name") String fileName,
- @NonNull @JsonbProperty("assignment_id") int assignmentID,
- @NonNull @JsonbProperty("course_id") String courseID) {
- this.fileName = fileName;
- this.assignmentID = assignmentID;
- this.courseID = courseID;
- }
-
public FileDAO(
@NonNull @JsonbProperty("file_name") String fileName,
@NonNull @JsonbProperty("assignment_id") int assignmentID,
@@ -55,13 +45,4 @@ public static FileDAO fileFactory(String fileName, String courseID, IAttachment
InputStream inputStream = attachment.getDataHandler().getInputStream();
return new FileDAO(fileName, assignmentID, courseID, inputStream);
}
-
- /**
- * Writes the inputStream to a file.
- */
- public void writeFile(String filePath) throws IOException {
- OutputStream outputStream = new FileOutputStream(filePath);
- outputStream.write(file.readAllBytes());
- outputStream.close();
- }
}
\ No newline at end of file
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/PeerReviewAddOnDAO.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/PeerReviewAddOnDAO.java
new file mode 100755
index 000000000..aa6e7af18
--- /dev/null
+++ b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/PeerReviewAddOnDAO.java
@@ -0,0 +1,24 @@
+package edu.oswego.cs.rest.daos;
+
+import lombok.NonNull;
+
+import javax.json.bind.annotation.JsonbCreator;
+import javax.json.bind.annotation.JsonbProperty;
+
+public class PeerReviewAddOnDAO {
+ @JsonbProperty("peer_review_instructions") public String peerReviewInstructions;
+ @JsonbProperty("peer_review_due_date") public String peerReviewDueDate;
+ @JsonbProperty("peer_review_points") public int peerReviewPoints;
+
+ @JsonbCreator
+ public PeerReviewAddOnDAO(
+ @NonNull @JsonbProperty("peer_review_instructions") String peerReviewInstructions,
+ @NonNull @JsonbProperty("peer_review_due_date") String peerReviewDueDate,
+ @NonNull @JsonbProperty("peer_review_points") Integer peerReviewPoints
+ )
+ {
+ this.peerReviewInstructions = peerReviewInstructions;
+ this.peerReviewDueDate = peerReviewDueDate;
+ this.peerReviewPoints = peerReviewPoints;
+ }
+}
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/AssignmentInterface.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/AssignmentInterface.java
old mode 100644
new mode 100755
index d79b3e6ac..8c7eaf25b
--- a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/AssignmentInterface.java
+++ b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/AssignmentInterface.java
@@ -4,10 +4,13 @@
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import edu.oswego.cs.rest.daos.AssignmentDAO;
+import edu.oswego.cs.rest.daos.AssignmentNoPeerReviewDAO;
import edu.oswego.cs.rest.daos.FileDAO;
+import edu.oswego.cs.rest.daos.PeerReviewAddOnDAO;
import edu.oswego.cs.rest.util.CPRException;
import org.apache.commons.io.FileUtils;
import org.bson.Document;
+import org.bson.types.Binary;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
@@ -42,147 +45,280 @@ public AssignmentInterface() {
}
}
+
/**
- * Retrieves the relative location of the root Directory
+ * Write file binary data and file name of the assignment instructions to its respective assignment document in the
+ * database.
*
- * @return String directory location the hw files should be saved to
+ * @param fileDAO type FileDAO: Representation of File Data
*/
- public static String getRelPath() {
- String path = (System.getProperty("user.dir").contains("\\")) ? System.getProperty("user.dir").replace("\\", "/") : System.getProperty("user.dir");
- String[] slicedPath = path.split("/");
- String targetDir = "defaultServer";
- StringBuilder relativePathPrefix = new StringBuilder();
- for (int i = slicedPath.length - 1; !slicedPath[i].equals(targetDir); i--) {
- relativePathPrefix.append("../");
- }
- reg = "\\";
- if (System.getProperty("os.name").toLowerCase().contains("win")||(System.getProperty("os.name").toLowerCase().contains("nux") && System.getProperty("os.version").contains("WSL"))) {
- reg = "/";
- relativePathPrefix = new StringBuilder(relativePathPrefix.toString().replace("\\", "/"));
- }
- return relativePathPrefix.toString();
+
+ /**
+ * Write file binary data and file name of the assignment instructions to its respective assignment document in the
+ * database.
+ *
+ * @param fileDAO type FileDAO: Representation of File Data
+ */
+
+ public void writeToAssignment(FileDAO fileDAO) throws IOException {
+ //the line below will get the document we are searching for
+ Document result = assignmentsCollection.find(and(eq("course_id", fileDAO.courseID), eq("assignment_id", fileDAO.assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //add the assignment instructions binary data and file name to the database
+ result.append("assignment_instructions_data", Base64.getDecoder().decode(new String(fileDAO.file.readAllBytes())));
+ result.append("assignment_instructions_name", fileDAO.fileName);
+ assignmentsCollection.replaceOne(and(eq("course_id", fileDAO.courseID), eq("assignment_id", fileDAO.assignmentID)), result);
}
- public static String findFile(String courseID, int assignmentID, String fileName) {
- return getRelPath() + "assignments" + reg + courseID + reg + assignmentID + reg + "assignments" + reg + fileName;
+ /**
+ *
+ * @param fileDAO
+ * @throws IOException
+ */
+ public void writeRubricToPeerReviews(FileDAO fileDAO) throws IOException {
+ //the line below will get the document we are searching for
+ Document result = assignmentsCollection.find(and(eq("course_id", fileDAO.courseID), eq("assignment_id", fileDAO.assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //add the assignment instructions binary data and file name to the database
+ result.append("rubric_data", Base64.getDecoder().decode(new String(fileDAO.file.readAllBytes())));
+ result.append("rubric_name", fileDAO.fileName);
+ assignmentsCollection.replaceOne(and(eq("course_id", fileDAO.courseID), eq("assignment_id", fileDAO.assignmentID)), result);
}
- public static String findPeerReviewFile(String courseID, int assignmentID, String fileName) {
- String filePath = getRelPath() + "assignments" + reg + courseID + reg + assignmentID + reg + "peer-reviews" + reg + fileName;
- if (!new File(filePath).exists())
- throw new CPRException(Response.Status.BAD_REQUEST,filePath + "does not exist");
- return filePath;
+ /**
+ *
+ * @param fileDAO
+ * @throws IOException
+ */
+ public void writeTemplateToPeerReviews(FileDAO fileDAO) throws IOException {
+ //the line below will get the document we are searching for
+ Document result = assignmentsCollection.find(and(eq("course_id", fileDAO.courseID), eq("assignment_id", fileDAO.assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //add the assignment instructions binary data and file name to the database
+ result.append("peer_review_template_data", Base64.getDecoder().decode(new String(fileDAO.file.readAllBytes())));
+ result.append("peer_review_template_name", fileDAO.fileName);
+ assignmentsCollection.replaceOne(and(eq("course_id", fileDAO.courseID), eq("assignment_id", fileDAO.assignmentID)), result);
}
- public void writeToAssignment(FileDAO fileDAO) throws IOException {
- String FileStructure = getRelPath() + "assignments" + reg + fileDAO.courseID + reg + fileDAO.assignmentID + reg + "assignments";
- fileDAO.writeFile(FileStructure + reg + fileDAO.fileName);
- assignmentsCollection.updateOne(and(
- eq("course_id", fileDAO.courseID),
- eq("assignment_id", fileDAO.assignmentID)),
- set("assignment_instructions", fileDAO.fileName));
+
+ /**
+ * Grabs the binary data of the assignment instructions for the respective assignment
+ *
+ * @param courseID type String
+ * @param assignmentID type Integer
+ */
+
+ public byte[] getInstructionFileData(String courseID, Integer assignmentID){
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("assignment_instructions_data")) throw new CPRException(Response.Status.NOT_FOUND, "No assignment instruction data uploaded");
+
+ Binary data = (Binary) result.get("assignment_instructions_data");
+ return data.getData();
}
- public void writeRubricToPeerReviews(FileDAO fileDAO) throws IOException {
- String FileStructure = getRelPath() + "assignments" + reg + fileDAO.courseID + reg + fileDAO.assignmentID + reg + "peer-reviews";
- fileDAO.writeFile(FileStructure + reg + fileDAO.fileName);
- assignmentsCollection.updateOne(and(
- eq("course_id", fileDAO.courseID),
- eq("assignment_id", fileDAO.assignmentID)),
- set("peer_review_rubric", fileDAO.fileName));
+ /**
+ * Grabs the name of the instructions file
+ *
+ * @param courseID type String
+ * @param assignmentID type Integer
+ */
+
+ public String getInstructionFileName(String courseID, Integer assignmentID){
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("assignment_instructions_name")) throw new CPRException(Response.Status.NOT_FOUND, "No assignment instruction data uploaded");
+
+ return (String) result.get("assignment_instructions_name");
}
- public void writeTemplateToPeerReviews(FileDAO fileDAO) throws IOException {
- String FileStructure = getRelPath() + "assignments" + reg + fileDAO.courseID + reg + fileDAO.assignmentID + reg + "peer-reviews";
- fileDAO.writeFile(FileStructure + reg + fileDAO.fileName);
- assignmentsCollection.updateOne(and(
- eq("course_id", fileDAO.courseID),
- eq("assignment_id", fileDAO.assignmentID)),
- set("peer_review_template", fileDAO.fileName));
+
+ /**
+ * Grabs the name of the rubric file
+ *
+ * @param courseID type String
+ * @param assignmentID type Integer
+ */
+
+ public String getRubricFileName(String courseID, Integer assignmentID){
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("rubric_name")) throw new CPRException(Response.Status.NOT_FOUND, "No assignment instruction data uploaded");
+
+ return (String) result.get("rubric_name");
}
- public void removeFile(String courseID, String fileName, int assignmentID) {
- String fileLocation = findFile(courseID, assignmentID, fileName);
- File file = new File(fileLocation);
- if (!file.delete())
- throw new CPRException(Response.Status.BAD_REQUEST,"Assignment does not exist or could not be deleted.");
- assignmentsCollection.updateOne(and(eq("course_id", courseID),
- eq("assignment_id", assignmentID)),
- set("assignment_instructions", ""));
+
+ /**
+ * Grab the binary data of the assignment rubric for the respective assignment
+ *
+ * @param courseID type String
+ * @param assignmentID type Integer
+ */
+
+ public byte[] getRubricFileData(String courseID, Integer assignmentID){
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("rubric_data")) throw new CPRException(Response.Status.NOT_FOUND, "No rubric data uploaded");
+
+ Binary data = (Binary) result.get("rubric_data");
+ return data.getData();
}
- public void removePeerReviewTemplate(String courseID, String fileName, int assignmentID) {
- String fileLocation = findPeerReviewFile(courseID, assignmentID, fileName);
- File file = new File(fileLocation);
- if (!file.delete())
- throw new CPRException(Response.Status.BAD_REQUEST,"Assignment does not exist or could not be deleted.");
- assignmentsCollection.updateOne(and(
- eq("course_id", courseID),
- eq("assignment_id", assignmentID)),
- set("peer_review_template", ""));
+
+ /**
+ * Grabs the name of the rubric file
+ *
+ * @param courseID type String
+ * @param assignmentID type Integer
+ */
+
+ public String getTemplateFileName(String courseID, Integer assignmentID){
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("peer_review_template_name")) throw new CPRException(Response.Status.NOT_FOUND, "No assignment instruction data uploaded");
+
+ return (String) result.get("peer_review_template_name");
}
- public void removePeerReviewRubric(String courseID, String fileName, int assignmentID) {
- String fileLocation = findPeerReviewFile(courseID, assignmentID, fileName);
- File file = new File(fileLocation);
- if (!file.delete())
- throw new CPRException(Response.Status.BAD_REQUEST,"Assignment does not exist or could not be deleted.");
- assignmentsCollection.updateOne(and(
- eq("course_id", courseID),
- eq("assignment_id", assignmentID)),
- set("peer_review_rubric", ""));
+ /**
+ * Grabs the binary data of the peer review template for the respective assignment
+ *
+ * @param courseID type String
+ * @param assignmentID type Integer
+ */
+
+ public byte[] getPeerReviewTemplateData(String courseID, Integer assignmentID){
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("peer_review_template_data")) throw new CPRException(Response.Status.NOT_FOUND, "No template data uploaded");
+
+ Binary data = (Binary) result.get("peer_review_template_data");
+ return data.getData();
}
- public Document createAssignment(AssignmentDAO assignmentDAO) throws IOException {
- Document courseDocument = courseCollection.find(eq("course_id", assignmentDAO.courseID)).first();
- if (courseDocument == null) throw new CPRException(Response.Status.BAD_REQUEST,"Course not found.");
- String FileStructure = getRelPath() + "assignments" + reg + assignmentDAO.courseID;
+ public void removeFile(String courseID, int assignmentID) {
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("assignment_instructions_data")) throw new CPRException(Response.Status.NOT_FOUND, "No template data uploaded");
+ //add the assignment instructions binary data and file name to the database
+ result.remove("assignment_instructions_data");
+ result.remove("assignment_instructions_name");
+ assignmentsCollection.replaceOne(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), result);
+ }
+
+ public void removePeerReviewTemplate(String courseID, int assignmentID) {
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("peer_review_template_data")) throw new CPRException(Response.Status.NOT_FOUND, "No template data uploaded");
+ //add the assignment instructions binary data and file name to the database
+ result.remove("peer_review_template_data");
+ result.remove("peer_review_template_name");
+ assignmentsCollection.replaceOne(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), result);
+ }
- File dir = new File(FileStructure);
- if (!dir.mkdirs() && !dir.exists()) throw new CPRException(Response.Status.BAD_REQUEST,"Failed to create directory at" + dir.getAbsolutePath());
+ public void removePeerReviewRubric(String courseID, int assignmentID) {
+ Document result = assignmentsCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ //makes sure the result isn't null
+ if (result == null) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment found");
+
+ //grab the assignment instructions data and return it, ensure the assignment instructions data exists first
+ if(!result.containsKey("rubric_data")) throw new CPRException(Response.Status.NOT_FOUND, "No template data uploaded");
+ //add the assignment instructions binary data and file name to the database
+ result.remove("rubric_data");
+ result.remove("rubric_name");
+ assignmentsCollection.replaceOne(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), result);
+ }
- String[] dirList = dir.list();
- if (dirList == null) throw new CPRException(Response.Status.BAD_REQUEST,"Directory must exist to make file structure.");
+ /**
+ * Creates the assignment data based on the POST request's sent data. Previously, this function would make
+ * a file structure on the host machine to store the PDFs. Now it just stores the assignment data JSON
+ * and the writeToAssignment function handles writing the Assignment PDF data in the database.
+ *
+ * @param assignmentDAO type AssignmentDAO: Representation of Assignment Data
+ * @return Document
+ */
+ public Document createAssignment(AssignmentDAO assignmentDAO) throws IOException {
+ Document courseDocument = courseCollection.find(eq("course_id", assignmentDAO.courseID)).first();
+ if (courseDocument == null) throw new CPRException(Response.Status.BAD_REQUEST,"Course not found.");
int nextPos = generateAssignmentID();
assignmentDAO.assignmentID = nextPos;
- FileStructure += reg + nextPos;
- if (!new File(FileStructure + reg + "team-submissions").mkdirs()) throw new CPRException(Response.Status.BAD_REQUEST,"Failed to create team-submission directory.");
+ Jsonb jsonb = JsonbBuilder.create();
+ Entity assignmentDAOEntity = Entity.entity(jsonb.toJson(assignmentDAO), MediaType.APPLICATION_JSON_TYPE);
+ Document assignmentDocument = Document.parse(assignmentDAOEntity.getEntity());
+ assignmentDocument
+ .append("submission_is_past_due", false)
+ .append("peer_review_is_past_due", false)
+ .append("grade_finalized", false)
+ .append("has_peer_review", true);
- if (!new File(FileStructure + reg + "peer-reviews").mkdirs()) {
- deleteFile(FileStructure + reg + "team-submissions");
- throw new CPRException(Response.Status.BAD_REQUEST,"Failed to create peer-review directory.");
- }
+ MongoCursor query = assignmentsCollection.find(assignmentDocument).iterator();
+ if (query.hasNext()) {
+ query.close();
- if (!new File(FileStructure + reg + "assignments").mkdirs()) {
- deleteFile(FileStructure + reg + "team-submissions");
- deleteFile(FileStructure + reg + "peer-reviews");
- throw new CPRException(Response.Status.BAD_REQUEST,"Failed to create assignments directory");
+ throw new CPRException(Response.Status.BAD_REQUEST,"This assignment already exists.");
}
- if (!new File(FileStructure + reg + "peer-review-submission").mkdirs()) {
- deleteFile(FileStructure + reg + "team-submissions");
- deleteFile(FileStructure + reg + "peer-reviews");
- deleteFile(FileStructure + reg + "assignments");
- throw new CPRException(Response.Status.BAD_REQUEST,"Failed to create peer-review-submission directory");
- }
+ assignmentsCollection.insertOne(assignmentDocument);
+ return assignmentDocument;
+ }
+
+ /**
+ *Creates assignment data for assignments without any peer review data
+ *
+ * @param assignmentDAO type AssignmentNoPeerReviewDAO: Representation of Assignment Data
+ * @return Document
+ */
+
+ public Document createAssignmentNoPeerReview(AssignmentNoPeerReviewDAO assignmentDAO) throws IOException {
+ Document courseDocument = courseCollection.find(eq("course_id", assignmentDAO.courseID)).first();
+ if (courseDocument == null) throw new CPRException(Response.Status.BAD_REQUEST,"Course not found.");
+ int nextPos = generateAssignmentID();
+ assignmentDAO.assignmentID = nextPos;
Jsonb jsonb = JsonbBuilder.create();
Entity assignmentDAOEntity = Entity.entity(jsonb.toJson(assignmentDAO), MediaType.APPLICATION_JSON_TYPE);
Document assignmentDocument = Document.parse(assignmentDAOEntity.getEntity());
assignmentDocument
.append("submission_is_past_due", false)
- .append("peer_review_is_past_due", false)
- .append("grade_finalized", false);
+ .append("grade_finalized", false)
+ .append("has_peer_review", false);
MongoCursor query = assignmentsCollection.find(assignmentDocument).iterator();
if (query.hasNext()) {
query.close();
- deleteFile(FileStructure + reg + "team-submissions");
- deleteFile(FileStructure + reg + "peer-reviews");
- deleteFile(FileStructure + reg + "assignments");
- deleteFile(FileStructure + reg + "peer-review-submission");
throw new CPRException(Response.Status.BAD_REQUEST,"This assignment already exists.");
}
@@ -191,6 +327,26 @@ public Document createAssignment(AssignmentDAO assignmentDAO) throws IOException
return assignmentDocument;
}
+ /**
+ * Appends peer review data onto an assignment that previously had no peer review data
+ *
+ * @param courseID
+ * @param AssignmentID
+ * @param peerReviewAddOnDAO
+ */
+ public String addPeerReviewDataToAssignment(String courseID, int AssignmentID, PeerReviewAddOnDAO peerReviewAddOnDAO){
+ Document assignmentDocument = assignmentsCollection.find(and(eq("assignment_id", AssignmentID),eq("course_id", courseID))).first();
+ if (assignmentDocument == null) throw new CPRException(Response.Status.BAD_REQUEST,"This assignment does not exist.");
+ assignmentDocument.append("peer_review_due_date", peerReviewAddOnDAO.peerReviewDueDate)
+ .append("peer_review_instructions", peerReviewAddOnDAO.peerReviewInstructions)
+ .append("peer_review_points", peerReviewAddOnDAO.peerReviewPoints)
+ .append("peer_review_is_past_due", false);
+ assignmentDocument.replace("has_peer_review", true);
+
+ assignmentsCollection.replaceOne(and(eq("assignment_id", AssignmentID),eq("course_id", courseID)), assignmentDocument);
+ return (String) assignmentDocument.get("assignment_name");
+ }
+
public List getAllAssignments() {
MongoCursor query = assignmentsCollection.find().iterator();
List assignments = new ArrayList<>();
@@ -201,6 +357,12 @@ public List getAllAssignments() {
return assignments;
}
+ /**
+ *
+ * @param courseID
+ * @return
+ */
+
public List getAssignmentsByCourse(String courseID) {
MongoCursor query = assignmentsCollection.find(eq("course_id", courseID)).iterator();
if (!query.hasNext()) return Collections.emptyList();
@@ -213,6 +375,13 @@ public List getAssignmentsByCourse(String courseID) {
return assignments;
}
+ /**
+ *
+ * @param courseID
+ * @param AssignmentID
+ * @return
+ */
+
public Document getSpecifiedAssignment(String courseID, int AssignmentID) {
Document assignment = assignmentsCollection.find(and(
eq("course_id", courseID),
@@ -221,6 +390,13 @@ public Document getSpecifiedAssignment(String courseID, int AssignmentID) {
return assignment;
}
+ /**
+ *
+ * @param assignmentDAO
+ * @param courseID
+ * @param assignmentID
+ */
+
public void updateAssignment(AssignmentDAO assignmentDAO, String courseID, int assignmentID) {
Document assignmentDocument = assignmentsCollection.find(and(eq("assignment_id", assignmentID),eq("course_id", courseID))).first();
if (assignmentDocument == null) throw new CPRException(Response.Status.BAD_REQUEST,"This assignment does not exist.");
@@ -235,6 +411,31 @@ public void updateAssignment(AssignmentDAO assignmentDAO, String courseID, int a
assignmentsCollection.replaceOne(and(eq("assignment_id", assignmentID),eq("course_id", courseID)), assignmentDocument);
}
+ /**
+ * Update an assignment's data that has no peer review
+ *
+ * @param assignmentNoPeerReviewDAO
+ * @param courseID
+ * @param assignmentID
+ */
+ public void updateAssignmentWithNoPeerReview(AssignmentNoPeerReviewDAO assignmentNoPeerReviewDAO, String courseID, int assignmentID) {
+ Document assignmentDocument = assignmentsCollection.find(and(eq("assignment_id", assignmentID),eq("course_id", courseID))).first();
+ if (assignmentDocument == null) throw new CPRException(Response.Status.BAD_REQUEST,"This assignment does not exist.");
+ assignmentDocument.replace("assignment_name", assignmentNoPeerReviewDAO.assignmentName);
+ assignmentDocument.replace("due_date", assignmentNoPeerReviewDAO.dueDate);
+ assignmentDocument.replace("instructions", assignmentNoPeerReviewDAO.instructions);
+ assignmentDocument.replace("points", assignmentNoPeerReviewDAO.points);
+
+ assignmentsCollection.replaceOne(and(eq("assignment_id", assignmentID),eq("course_id", courseID)), assignmentDocument);
+ }
+
+ /**
+ *
+ * @param AssignmentID
+ * @param courseID
+ * @throws IOException
+ */
+
public void removeAssignment(int AssignmentID, String courseID) throws IOException {
MongoCursor results = assignmentsCollection.find(and(
eq("assignment_id", AssignmentID),
@@ -243,7 +444,6 @@ public void removeAssignment(int AssignmentID, String courseID) throws IOExcepti
while (results.hasNext()) {
Document assignment = results.next();
- deleteFile(getRelPath() + "assignments" + reg + courseID + reg + assignment.get("assignment_id"));
assignmentsCollection.findOneAndDelete(assignment);
}
removeSubmissions(AssignmentID, courseID);
@@ -254,6 +454,12 @@ public void removeSubmissions(int AssignmentID, String courseID) throws IOExcept
submissionCollection.findOneAndDelete(submissionDoc);
}
+ /**
+ *
+ * @param courseID
+ * @throws IOException
+ */
+
public void removeCourse(String courseID) throws IOException {
MongoCursor results = assignmentsCollection.find(eq("course_id", courseID)).iterator();
if (!results.hasNext()) throw new CPRException(Response.Status.BAD_REQUEST,"No assignment by this name found.");
@@ -262,13 +468,21 @@ public void removeCourse(String courseID) throws IOException {
Document assignmentDocument = results.next();
assignmentsCollection.findOneAndDelete(assignmentDocument);
}
-
- deleteFile(getRelPath() + "assignments" + reg + courseID);
}
- private static void deleteFile(String destination) throws IOException {
- FileUtils.deleteDirectory(new File(destination));
- }
+
+ /**
+ *
+ * Iterates the assignment id by one based on how many assignments currently exist in the DB
+ *
+ **/
+
+
+ /**
+ *
+ * Iterates the assignment id by one based on how many assignments currently exist in the DB
+ *
+ **/
public int generateAssignmentID() {
List assignmentsDocuments = getAllAssignments();
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/DatabaseManager.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/ProfessorAssignmentResource.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/ProfessorAssignmentResource.java
old mode 100644
new mode 100755
index e73e418cb..5d65f1f2d
--- a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/ProfessorAssignmentResource.java
+++ b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/ProfessorAssignmentResource.java
@@ -2,7 +2,9 @@
import com.ibm.websphere.jaxrs20.multipart.IAttachment;
import edu.oswego.cs.rest.daos.AssignmentDAO;
+import edu.oswego.cs.rest.daos.AssignmentNoPeerReviewDAO;
import edu.oswego.cs.rest.daos.FileDAO;
+import edu.oswego.cs.rest.daos.PeerReviewAddOnDAO;
import edu.oswego.cs.rest.database.AssignmentInterface;
import org.bson.Document;
@@ -13,12 +15,95 @@
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
+import java.util.Base64;
import java.util.List;
@Path("professor")
@DenyAll
public class ProfessorAssignmentResource {
+ @POST
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/create-assignment")
+ public Response createAssignment(AssignmentDAO assignmentDAO) throws IOException {
+ Document assignmentDocument = new AssignmentInterface().createAssignment(assignmentDAO);
+ return Response.status(Response.Status.OK).entity(assignmentDocument).build();
+ }
+
+ /**
+ * Create an assignment with no initial peer review data
+ *
+ * @param assignmentNoPeerReviewDAO
+ * @return
+ * @throws IOException
+ */
+
+ @POST
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/create-assignment-no-peer-review")
+ public Response createAssignmentNoPeerReview(AssignmentNoPeerReviewDAO assignmentNoPeerReviewDAO) throws IOException {
+ Document assignmentDocument = new AssignmentInterface().createAssignmentNoPeerReview(assignmentNoPeerReviewDAO);
+ return Response.status(Response.Status.OK).entity(assignmentDocument).build();
+ }
+
+
+ @DELETE
+ @RolesAllowed("professor")
+ @Path("/courses/{courseID}/assignments/{assignmentID}/remove")
+ public Response removeAssignment(@PathParam("assignmentID") int assignmentID, @PathParam("courseID") String courseID) throws IOException {
+ new AssignmentInterface().removeAssignment(assignmentID, courseID);
+ return Response.status(Response.Status.OK).entity("Assignment successfully deleted.").build();
+ }
+
+ @PUT
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/{courseID}/assignments/{assignmentID}/edit")
+ public Response updateAssignment(AssignmentDAO assignmentDAO, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
+ new AssignmentInterface().updateAssignment(assignmentDAO, courseID, assignmentID);
+ String response = assignmentDAO.courseID + ": " + assignmentDAO.assignmentName + " successfully updated.";
+ return Response.status(Response.Status.OK).entity(response).build();
+ }
+
+ /**
+ * Edit an assignment with no peer review data
+ *
+ * @param assignmentNoPeerReviewDAO
+ * @param courseID
+ * @param assignmentID
+ * @return
+ */
+
+ @PUT
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/{courseID}/assignments/{assignmentID}/editNoPeerReview")
+ public Response updateAssignmentNoPeerReview(AssignmentNoPeerReviewDAO assignmentNoPeerReviewDAO, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
+ new AssignmentInterface().updateAssignmentWithNoPeerReview(assignmentNoPeerReviewDAO, courseID, assignmentID);
+ String response = assignmentNoPeerReviewDAO.courseID + ": " + assignmentNoPeerReviewDAO.assignmentName + " successfully updated.";
+ return Response.status(Response.Status.OK).entity(response).build();
+ }
+
+
+ /**
+ * Add peer review data to an assignment that has none
+ *
+ * @param peerReviewAddOnDAO
+ * @param courseID
+ * @param assignmentID
+ * @return
+ */
+ @PUT
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/{courseID}/assignments/{assignmentID}/addPeerReviewData")
+ public Response addPeerReviewData(PeerReviewAddOnDAO peerReviewAddOnDAO, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
+ String assignmentName = new AssignmentInterface().addPeerReviewDataToAssignment(courseID, assignmentID, peerReviewAddOnDAO);
+ return Response.status(Response.Status.OK).entity("Successfully added peer review data to " + courseID + ":" + assignmentName).build();
+ }
+
@GET
@RolesAllowed("professor")
@Produces(MediaType.APPLICATION_JSON)
@@ -43,26 +128,32 @@ public Response viewSpecifiedAssignment(@PathParam("courseID") String courseID,
return Response.status(Response.Status.OK).entity(new AssignmentInterface().getSpecifiedAssignment(courseID, assignmentID)).build();
}
+
/**
- * File is uploaded as form-data and passed back as a List
- * The attachment is processed in FileDao.FileFactory, which reads and
- * reconstructs the file through inputStream and outputStream respectively
+ * File's Base64 string is uploaded as form-data and passed back as a List. The file's name and
+ * extension is saved in the form-data's name
+ * The attachment is processed and turned back into its binary representation. The binary data and file name is then
+ * saved with its respective assignment document in the DB.
*
- * @param attachments type List: file(s) passed back as form-data
+ * @param attachments type List: file(s) Base64 Strings passed back as form-data
* @param courseID type String
* @param assignmentID type int
* @return Response
*/
+
@POST
@RolesAllowed("professor")
@Consumes(MediaType.MULTIPART_FORM_DATA)
- @Produces({MediaType.MULTIPART_FORM_DATA, "application/pdf"})
+ @Produces(MediaType.APPLICATION_JSON)
@Path("/courses/{courseID}/assignments/{assignmentID}/upload")
- public Response addFileToAssignment(List attachments, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) throws Exception {
+ public Response addFileToAssignment
+ (List attachments,
+ @PathParam("courseID") String courseID,
+ @PathParam("assignmentID") int assignmentID)
+ throws Exception {
for (IAttachment attachment : attachments) {
if (attachment == null) continue;
String fileName = attachment.getDataHandler().getName();
-
if (!fileName.endsWith("pdf") && !fileName.endsWith("zip") && !fileName.endsWith("docx"))
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).build();
new AssignmentInterface().writeToAssignment(FileDAO.fileFactory(fileName, courseID, attachment, assignmentID));
@@ -83,20 +174,20 @@ public Response addFileToAssignment(List attachments, @PathParam("c
@POST
@RolesAllowed("professor")
@Consumes(MediaType.MULTIPART_FORM_DATA)
- @Produces({MediaType.MULTIPART_FORM_DATA, "application/pdf"})
+ @Produces(MediaType.APPLICATION_JSON)
@Path("/courses/{courseID}/assignments/{assignmentID}/peer-review/rubric/upload")
public Response addRubricToPeerReview(List attachments, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) throws Exception {
for (IAttachment attachment : attachments) {
if (attachment == null) continue;
String fileName = attachment.getDataHandler().getName();
-
if (!fileName.endsWith("pdf") && !fileName.endsWith("zip") && !fileName.endsWith("docx"))
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).build();
new AssignmentInterface().writeRubricToPeerReviews(FileDAO.fileFactory(fileName, courseID, attachment, assignmentID));
}
- return Response.status(Response.Status.OK).entity("Successfully added file to peer reviews.").build();
+ return Response.status(Response.Status.OK).entity("Successfully added file to assignment.").build();
}
+
/**
* File is uploaded as form-data and passed back as a List
* The attachment is processed in FileDao.FileFactory, which reads and
@@ -110,74 +201,41 @@ public Response addRubricToPeerReview(List attachments, @PathParam(
@POST
@RolesAllowed("professor")
@Consumes(MediaType.MULTIPART_FORM_DATA)
- @Produces({MediaType.MULTIPART_FORM_DATA, "application/pdf"})
+ @Produces(MediaType.APPLICATION_JSON)
@Path("/courses/{courseID}/assignments/{assignmentID}/peer-review/template/upload")
public Response addTemplateToPeerReview(List attachments, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) throws Exception {
for (IAttachment attachment : attachments) {
if (attachment == null) continue;
String fileName = attachment.getDataHandler().getName();
-
if (!fileName.endsWith("pdf") && !fileName.endsWith("zip") && !fileName.endsWith("docx"))
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).build();
new AssignmentInterface().writeTemplateToPeerReviews(FileDAO.fileFactory(fileName, courseID, attachment, assignmentID));
}
- return Response.status(Response.Status.OK).entity("Successfully added file to peer reviews.").build();
- }
-
- @DELETE
- @RolesAllowed("professor")
- @Consumes(MediaType.APPLICATION_JSON)
- @Path("/courses/{course-id}/assignments/{assignment-id}/remove-file/{file-name}")
- public Response removeFileFromAssignment(@PathParam("course-id") String courseID, @PathParam("assignment-id") int assignmentID, @PathParam("file-name") String fileName) {
- new AssignmentInterface().removeFile(courseID, fileName, assignmentID);
- return Response.status(Response.Status.OK).entity("File successfully deleted.").build();
- }
-
- @DELETE
- @RolesAllowed("professor")
- @Consumes(MediaType.APPLICATION_JSON)
- @Path("/courses/{course-id}/assignments/{assignment-id}/peer-review-template/remove-file/{file-name}")
- public Response removeFileFromPeerReviewTemplate(@PathParam("course-id") String courseID, @PathParam("assignment-id") int assignmentID, @PathParam("file-name") String fileName) {
- new AssignmentInterface().removePeerReviewTemplate(courseID, fileName, assignmentID);
- return Response.status(Response.Status.OK).entity("File successfully deleted.").build();
- }
-
- @DELETE
- @RolesAllowed("professor")
- @Consumes(MediaType.APPLICATION_JSON)
- @Path("/courses/{course-id}/assignments/{assignment-id}/peer-review-rubric/remove-file/{file-name}")
- public Response removeFileFromPeerReviewRubric(@PathParam("course-id") String courseID, @PathParam("assignment-id") int assignmentID, @PathParam("file-name") String fileName) {
- new AssignmentInterface().removePeerReviewRubric(courseID, fileName, assignmentID);
- return Response.status(Response.Status.OK).entity("File successfully deleted.").build();
+ return Response.status(Response.Status.OK).entity("Successfully added file to assignment.").build();
}
- @POST
- @RolesAllowed("professor")
- @Consumes(MediaType.APPLICATION_JSON)
- @Path("/courses/create-assignment")
- public Response createAssignment(AssignmentDAO assignmentDAO) throws IOException {
- Document assignmentDocument = new AssignmentInterface().createAssignment(assignmentDAO);
-// DueDateChecker.assignmentDocuments.add(assignmentDocument);
- return Response.status(Response.Status.OK).entity(assignmentDocument).build();
- }
+ /**
+ * Retrieves the assignment instructions file from the DB and passes its Base64 representation to the front end via
+ * the request header.
+ *
+ * @param courseID String
+ * @param assignmentID int
+ * @return response
+ **/
+ @GET
+ @RolesAllowed({"professor", "student"})
+ @Produces(MediaType.MULTIPART_FORM_DATA)
+ @Path("/courses/{courseID}/assignments/{assignmentID}/download")
+ public Response downloadAssignment(@PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
+ byte[] fileData = new AssignmentInterface().getInstructionFileData(courseID, assignmentID);
+ String fileName = new AssignmentInterface().getInstructionFileName(courseID, assignmentID);
- @DELETE
- @RolesAllowed("professor")
- @Path("/courses/{courseID}/assignments/{assignmentID}/remove")
- public Response removeAssignment(@PathParam("assignmentID") int assignmentID, @PathParam("courseID") String courseID) throws IOException {
- new AssignmentInterface().removeAssignment(assignmentID, courseID);
- return Response.status(Response.Status.OK).entity("Assignment successfully deleted.").build();
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(fileData));
+ response.header("Content-Disposition", "attachment; filename=" + fileName);
+ return response.build();
}
- @PUT
- @RolesAllowed("professor")
- @Consumes(MediaType.APPLICATION_JSON)
- @Path("/courses/{courseID}/assignments/{assignmentID}/edit")
- public Response updateAssignment(AssignmentDAO assignmentDAO, @PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
- new AssignmentInterface().updateAssignment(assignmentDAO, courseID, assignmentID);
- String response = assignmentDAO.courseID + ": " + assignmentDAO.assignmentName + " successfully updated.";
- return Response.status(Response.Status.OK).entity(response).build();
- }
+ //change
/**
* Retrieves the assignment from its location on the server and passes it to the front end via the request header
@@ -190,15 +248,13 @@ public Response updateAssignment(AssignmentDAO assignmentDAO, @PathParam("course
@GET
@RolesAllowed({"professor", "student"})
@Produces(MediaType.MULTIPART_FORM_DATA)
- @Path("/courses/{courseID}/assignments/{assignmentID}/download/{fileName}")
- public Response downloadAssignment(@PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID, @PathParam("fileName") String fileName) {
- new AssignmentInterface();
- File file = new File(AssignmentInterface.findFile(courseID, assignmentID, fileName));
- if (!file.exists())
- return Response.status(Response.Status.BAD_REQUEST).entity("Assignment does not exist.").build();
-
- Response.ResponseBuilder response = Response.ok(file);
- response.header("Content-Disposition", "attachment; filename=" + file.getName());
+ @Path("/courses/{courseID}/assignments/{assignmentID}/peer-review/template/download")
+ public Response downloadPeerReviewTemplate(@PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
+ byte[] fileData = new AssignmentInterface().getPeerReviewTemplateData(courseID, assignmentID);
+ String fileName = new AssignmentInterface().getTemplateFileName(courseID, assignmentID);
+
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(fileData));
+ response.header("Content-Disposition", "attachment; filename=" + fileName);
return response.build();
}
@@ -213,11 +269,44 @@ public Response downloadAssignment(@PathParam("courseID") String courseID, @Path
@GET
@RolesAllowed({"professor", "student"})
@Produces(MediaType.MULTIPART_FORM_DATA)
- @Path("/courses/{courseID}/assignments/{assignmentID}/peer-review/download/{fileName}")
- public Response downloadPeerReview(@PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID, @PathParam("fileName") String fileName) {
- File file = new File(AssignmentInterface.findPeerReviewFile(courseID, assignmentID, fileName));
+ @Path("/courses/{courseID}/assignments/{assignmentID}/peer-review/rubric/download")
+ public Response downloadPeerReview(@PathParam("courseID") String courseID, @PathParam("assignmentID") int assignmentID) {
+ byte[] fileData = new AssignmentInterface().getRubricFileData(courseID, assignmentID);
+ String fileName = new AssignmentInterface().getRubricFileName(courseID, assignmentID);
+
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(fileData));
+ response.header("Content-Disposition", "attachment; filename=" + fileName);
+ return response.build();
+ }
+
- return Response.ok(file).header("Content-Disposition", "attachment; filename=" + file.getName()).build();
+ //Change
+ @DELETE
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/{course-id}/assignments/{assignment-id}/remove-file")
+ public Response removeFileFromAssignment(@PathParam("course-id") String courseID, @PathParam("assignment-id") int assignmentID) {
+ new AssignmentInterface().removeFile(courseID, assignmentID);
+ return Response.status(Response.Status.OK).entity("File successfully deleted.").build();
+ }
+
+
+ @DELETE
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/{course-id}/assignments/{assignment-id}/peer-review/template/remove-file")
+ public Response removeFileFromPeerReviewTemplate(@PathParam("course-id") String courseID, @PathParam("assignment-id") int assignmentID) {
+ new AssignmentInterface().removePeerReviewTemplate(courseID, assignmentID);
+ return Response.status(Response.Status.OK).entity("File successfully deleted.").build();
+ }
+
+ @DELETE
+ @RolesAllowed("professor")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/courses/{course-id}/assignments/{assignment-id}/peer-review/rubric/remove-file")
+ public Response removeFileFromPeerReviewRubric(@PathParam("course-id") String courseID, @PathParam("assignment-id") int assignmentID) {
+ new AssignmentInterface().removePeerReviewRubric(courseID, assignmentID);
+ return Response.status(Response.Status.OK).entity("File successfully deleted.").build();
}
@DELETE
@@ -227,4 +316,5 @@ public Response removeCourse(@PathParam("courseID") String courseID) throws IOEx
new AssignmentInterface().removeCourse(courseID);
return Response.status(Response.Status.OK).entity("Course successfully deleted from assignments database and assignments folder.").build();
}
+
}
diff --git a/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/util/CPRException.java b/backend/professor-assignment-microservice/src/main/java/edu/oswego/cs/rest/util/CPRException.java
old mode 100644
new mode 100755
diff --git a/backend/professor-assignment-microservice/src/main/liberty/config/server.xml b/backend/professor-assignment-microservice/src/main/liberty/config/server.xml
old mode 100644
new mode 100755
index f1af77098..65e79858e
--- a/backend/professor-assignment-microservice/src/main/liberty/config/server.xml
+++ b/backend/professor-assignment-microservice/src/main/liberty/config/server.xml
@@ -23,4 +23,5 @@
+
diff --git a/backend/professor-assignment-microservice/src/test/java/ProfessorAssignmentTests.java b/backend/professor-assignment-microservice/src/test/java/ProfessorAssignmentTests.java
old mode 100644
new mode 100755
index b71256064..99f1ad3b3
--- a/backend/professor-assignment-microservice/src/test/java/ProfessorAssignmentTests.java
+++ b/backend/professor-assignment-microservice/src/test/java/ProfessorAssignmentTests.java
@@ -1,22 +1,26 @@
-//import edu.oswego.cs.rest.daos.AssignmentDAO;
-//import org.junit.jupiter.api.*;
-//
-//import javax.json.bind.Jsonb;
-//import javax.json.bind.JsonbBuilder;
-//import javax.ws.rs.client.Client;
-//import javax.ws.rs.client.ClientBuilder;
-//import javax.ws.rs.client.Entity;
-//import javax.ws.rs.client.WebTarget;
-//import javax.ws.rs.core.MediaType;
-//import javax.ws.rs.core.Response;
-//import java.util.ArrayList;
-//import java.util.HashMap;
-//import java.util.List;
+import com.ibm.websphere.jaxrs20.multipart.AttachmentBuilder;
+import com.ibm.websphere.jaxrs20.multipart.IAttachment;
+import com.mongodb.client.MongoCollection;
+import edu.oswego.cs.rest.daos.AssignmentNoPeerReviewDAO;
+import edu.oswego.cs.rest.daos.FileDAO;
+import edu.oswego.cs.rest.daos.PeerReviewAddOnDAO;
+import edu.oswego.cs.rest.database.AssignmentInterface;
+import edu.oswego.cs.rest.database.DatabaseManager;
+import org.bson.Document;
+import org.junit.jupiter.api.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import static com.mongodb.client.model.Filters.eq;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
//
//// DISCLAIMER: Don't run all the tests at the same time. You'll likely screw up the database and fail the tests in some way.
//// Read through the tests to see what they create, update and delete before you run them please.
//
-//public class ProfessorAssignmentTests {
+public class ProfessorAssignmentTests {
//
// private static final Jsonb jsonb = JsonbBuilder.create();
// private static final ArrayList expectedAssignments = new ArrayList<>();
@@ -30,7 +34,7 @@
// @BeforeAll
// public static void oneTimeSetup() {
// port = "13125";
-// baseUrl = "http://moxie.cs.oswego.edu:" + port + "/assignments/professor";
+// baseUrl = "https://moxie.cs.oswego.edu:" + port + "/assignments/professor";
//
//
// // variables for inserted assignments
@@ -324,5 +328,116 @@
// targetUrl = "/courses/"+assignment1.getCourseID()+"/assignments/"+assignmentIdCount+"/view-files";
// // (NOT INCLUDED HERE)
// }
-//}
-//
+// @Test
+// void uploadAssignmentFilesTest() throws IOException {
+// //make a dummy assignment in the database
+//
+// //String fileBase64 = "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";
+// MongoCollection courses = new DatabaseManager().getCourseDB().getCollection("courses");
+// MongoCollection assignments = new DatabaseManager().getAssignmentDB().getCollection("assignments");
+// Document course = new Document();
+// course
+// .append("abbreviation", "CSC212")
+// .append("course_id", "CSC212-100-Spring-2023")
+// .append("course_name", "Intro to CS")
+// .append("course_section", "100")
+// .append("crn", "12345")
+// .append("professor_id", "daltamur")
+// .append("semester", "spring")
+// .append("students", new ArrayList())
+// .append("team_size", 1)
+// .append("year", "2023");
+// courses.insertOne(course);
+//
+// Document assignment=new Document();
+// assignment
+// .append("assignment_id", 5000)
+// .append("assignment_name", "Solve the SPainter Problem")
+// .append("course_id", "CSC212-100-Spring-2023")
+// .append("due_date", "2024-03-24")
+// .append("instructions", "Write a program to solve the problem given in the attached instructions")
+// .append("peer_review_due_date", "2023-04-15")
+// .append("peer_review_instructions", "Rate your classmate's solution out of 15 points")
+// .append("peer_review_points", 15)
+// .append("points", 15)
+// .append("submission_is_past_due", false)
+// .append("peer_review_is_past_due", false)
+// .append("grade_finalized", false)
+// .append("assigned_teams", new ArrayList<>())
+// .append("completed_teams", new ArrayList<>())
+// .append("all_teams", new ArrayList<>())
+// .append("reviews_per_team",1);
+// assignments.insertOne(assignment);
+//
+// assertEquals(assignment.get("points"), 15);
+//
+//
+// //upload instruction, template, and rubric data
+// IAttachment instructionsAttachment = AttachmentBuilder.newBuilder("instructions")
+// //file stuff will go here
+// .build();
+// System.out.println("asdf");
+// FileDAO instructionsDAO = FileDAO.fileFactory("instructions.docx", "CSC212-100-Spring-2023", instructionsAttachment, 5000);
+// new AssignmentInterface().writeToAssignment(instructionsDAO);
+//
+// //get rid of the test assignment and course
+// courses.findOneAndDelete(eq("course_id","CSC 101-800-12312-Spring-2023"));
+// }
+
+ @Test
+ public void makeAssignmentNoPeerReview() throws IOException {
+ // Note that this test runs under the assumption that the assignments database is empty
+ // Make a dummy assignment
+ AssignmentNoPeerReviewDAO testDAO = new AssignmentNoPeerReviewDAO("CSC212-800-12313-Spring-2023", "Rate your classmate's submission", "Rate your classmate's java solution on a scale of 1-10", "2023-05-15", 10);
+ Document madeAssignment = new AssignmentInterface().createAssignmentNoPeerReview(testDAO);
+ // Find the assignment and make sure it contains no peer review data and that it does, in fact, exist
+ Document foundAssignment = new AssignmentInterface().getSpecifiedAssignment("CSC212-800-12313-Spring-2023", (Integer) madeAssignment.get("assignment_id"));
+ //we don't really have to make sure every single value gets copied over, it's fine just to make sure it exists and contains on peer review data
+ assertNotNull(foundAssignment);
+ assertEquals(foundAssignment.get("has_peer_review"), false);
+ new AssignmentInterface().removeAssignment((Integer) foundAssignment.get("assignment_id"), "CSC212-800-12313-Spring-2023");
+ }
+
+
+ @Test
+ public void updateAssignmentNoPeerReview() throws IOException {
+ //make an assignment to go into the DB that we will later edit
+ AssignmentNoPeerReviewDAO testDAO = new AssignmentNoPeerReviewDAO("CSC212-800-12313-Spring-2023", "Rate your classmate's submission", "Rate your classmate's java solution on a scale of 1-10", "2023-05-15", 10);
+ Document madeAssignment = new AssignmentInterface().createAssignmentNoPeerReview(testDAO);
+ AssignmentNoPeerReviewDAO updatedTestDAO = new AssignmentNoPeerReviewDAO("CSC212-800-12313-Spring-2023", "Java Solution Review", "Rate your classmate's java solution on a scale of 1-100", "2023-05-16", 100);
+ new AssignmentInterface().updateAssignmentWithNoPeerReview(updatedTestDAO, "CSC212-800-12313-Spring-2023", (Integer) madeAssignment.get("assignment_id"));
+ //find the assignment after updating it, assert the values are equal to the updated ones
+ Document foundAssignment = new AssignmentInterface().getSpecifiedAssignment("CSC212-800-12313-Spring-2023", (Integer) madeAssignment.get("assignment_id"));
+ assertNotNull(foundAssignment);
+ assertEquals(foundAssignment.get("assignment_name"), updatedTestDAO.assignmentName);
+ assertEquals(foundAssignment.get("due_date"), updatedTestDAO.dueDate);
+ assertEquals(foundAssignment.get("instructions"), updatedTestDAO.instructions);
+ assertEquals(foundAssignment.get("points"), updatedTestDAO.points);
+ assertEquals(foundAssignment.get("has_peer_review"), false);
+ new AssignmentInterface().removeAssignment((Integer) foundAssignment.get("assignment_id"), "CSC212-800-12313-Spring-2023");
+ }
+
+ @Test
+ public void addPeerReviewInformation() throws IOException {
+ //make an assignment to go into the DB that we will later edit
+ AssignmentNoPeerReviewDAO testDAO = new AssignmentNoPeerReviewDAO("CSC212-800-12313-Spring-2023", "Rate your classmate's submission", "Rate your classmate's java solution on a scale of 1-10", "2023-05-15", 10);
+ Document madeAssignment = new AssignmentInterface().createAssignmentNoPeerReview(testDAO);
+ PeerReviewAddOnDAO peerReviewAddOnDAO = new PeerReviewAddOnDAO("Rate your classmate's performance out of 15", "2023-05-26", 15);
+ new AssignmentInterface().addPeerReviewDataToAssignment("CSC212-800-12313-Spring-2023", (int) madeAssignment.get("assignment_id"), peerReviewAddOnDAO);
+ //find the assignment with the new peer review data now, ensure it contains all the right values
+ Document foundAssignment = new AssignmentInterface().getSpecifiedAssignment("CSC212-800-12313-Spring-2023", (Integer) madeAssignment.get("assignment_id"));
+ assertNotNull(foundAssignment);
+ assertEquals(foundAssignment.get("assignment_name"), testDAO.assignmentName);
+ assertEquals(foundAssignment.get("due_date"), testDAO.dueDate);
+ assertEquals(foundAssignment.get("instructions"), testDAO.instructions);
+ assertEquals(foundAssignment.get("points"), testDAO.points);
+ assertEquals(foundAssignment.get("has_peer_review"), true);
+ assertEquals(foundAssignment.get("peer_review_instructions"), peerReviewAddOnDAO.peerReviewInstructions);
+ assertEquals(foundAssignment.get("peer_review_due_date"), peerReviewAddOnDAO.peerReviewDueDate);
+ assertEquals(foundAssignment.get("peer_review_points"), peerReviewAddOnDAO.peerReviewPoints);
+ new AssignmentInterface().removeAssignment((Integer) foundAssignment.get("assignment_id"), "CSC212-800-12313-Spring-2023");
+ }
+
+
+}
+
diff --git a/backend/professor-assignment-microservice/src/test/java/solution.docx b/backend/professor-assignment-microservice/src/test/java/solution.docx
new file mode 100755
index 000000000..f94641308
Binary files /dev/null and b/backend/professor-assignment-microservice/src/test/java/solution.docx differ
diff --git a/backend/student-assignment-microservice/.dockerignore b/backend/student-assignment-microservice/.dockerignore
old mode 100644
new mode 100755
diff --git a/backend/student-assignment-microservice/Dockerfile b/backend/student-assignment-microservice/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/student-assignment-microservice/pom.xml b/backend/student-assignment-microservice/pom.xml
old mode 100644
new mode 100755
index 23a6b6384..70b258ce2
--- a/backend/student-assignment-microservice/pom.xml
+++ b/backend/student-assignment-microservice/pom.xml
@@ -16,6 +16,16 @@
+
+ org.apache.tika
+ tika-core
+ 2.7.0
+
+
+ org.apache.tika
+ tika-parsers-standard-package
+ 2.7.0
+
jakarta.platform
jakarta.jakartaee-api
diff --git a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/application/StudentAssignmentApplication.java b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/application/StudentAssignmentApplication.java
old mode 100644
new mode 100755
diff --git a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/cors/CorsFilter.java b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/cors/CorsFilter.java
old mode 100644
new mode 100755
index e751de1ba..87dc9431a
--- a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/cors/CorsFilter.java
+++ b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/cors/CorsFilter.java
@@ -23,5 +23,7 @@ public void filter(ContainerRequestContext requestContext,
responseContext.getHeaders().add(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
+ responseContext.getHeaders().add("Access-Control-Expose-Headers",
+ "Content-Disposition");
}
}
\ No newline at end of file
diff --git a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java
old mode 100644
new mode 100755
index 8f295a721..029d9ab67
--- a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java
+++ b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/daos/FileDAO.java
@@ -1,17 +1,33 @@
package edu.oswego.cs.rest.daos;
import com.ibm.websphere.jaxrs20.multipart.IAttachment;
+import edu.oswego.cs.rest.database.AssignmentInterface;
+import edu.oswego.cs.rest.database.DatabaseManager;
import lombok.AllArgsConstructor;
import lombok.Getter;
+import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
+import org.apache.poi.xwpf.usermodel.LineSpacingRule;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.pdf.PDFParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.BodyContentHandler;
+import org.xml.sax.SAXException;
import java.io.*;
+import java.util.Base64;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
@Getter
@AllArgsConstructor
public class FileDAO {
private String filename;
private String courseID;
- private InputStream file;
+ private byte[] file;
private int assignmentID;
private String teamName;
@@ -23,19 +39,51 @@ public class FileDAO {
* @param courseID String
* @param attachment form-data
* @return FileDAO Instance
- * @throws IOException File Corruption Exception
+ * @throws IOException File Corruption Exception or contains profanity
+ * @throws TikaException File Corruption Exception
+ * @throws SAXException File Corruption Exception
*/
- public static FileDAO fileFactory(String fileName, String courseID, IAttachment attachment, int assignmentID, String teamName) throws IOException {
- InputStream inputStream = attachment.getDataHandler().getInputStream();
- return new FileDAO(fileName, courseID, inputStream, assignmentID, teamName);
+ public static FileDAO fileFactory(String fileName, String courseID, IAttachment attachment, int assignmentID, String teamName) throws IOException, TikaException, SAXException {
+ InputStream inputStream = new BufferedInputStream(attachment.getDataHandler().getInputStream());
+ byte[] fileData = inputStream.readAllBytes();
+ contentFilter(new ByteArrayInputStream(Base64.getDecoder().decode(new String(fileData))), fileName, courseID);
+ inputStream.mark(inputStream.available());
+ return new FileDAO(fileName, courseID, fileData, assignmentID, teamName);
}
/**
- * Writes the inputStream to a file.
+ * Checks the file for profanity
+ *
+ * @param stream InputStream
+ * @throws IOException File Corruption Exception or contains profanity
+ * @throws TikaException File Corruption Exception
+ * @throws SAXException File Corruption Exception
*/
- public void writeFile(String filePath) throws IOException {
- OutputStream outputStream = new FileOutputStream(filePath);
- outputStream.write(file.readAllBytes());
- outputStream.close();
+ public static void contentFilter(InputStream stream, String fileName, String courseID) throws TikaException, IOException, SAXException {
+ //get the list of blocked words for the course
+ List blockedWords = new AssignmentInterface().getCourseProfanityWords(courseID);
+ String addedWords = "";
+ if(blockedWords!=null && blockedWords.size()!=0){
+ addedWords+=blockedWords.get(0);
+ for(int i = 1; i assignmentsCollection;
+
+ static MongoCollection courseCollection;
static MongoCollection teamsCollection;
static MongoCollection submissionCollection;
+ static MongoCollection professorCollection;
+ static MongoCollection studentCollection;
+
+ static ConcurrentHashMap assignmentLock = new ConcurrentHashMap<>();
static String reg = "/";
@@ -34,6 +55,9 @@ public AssignmentInterface() {
assignmentsCollection = assignmentDatabase.getCollection("assignments");
submissionCollection = assignmentDatabase.getCollection("submissions");
teamsCollection = teamsDatabase.getCollection("teams");
+ professorCollection = manager.getProfessorDB().getCollection("professors");
+ courseCollection = manager.getCourseDB().getCollection("courses");
+ studentCollection = manager.getStudentDB().getCollection("students");
} catch (WebApplicationException e) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Failed to retrieve collections.").build());
@@ -41,16 +65,11 @@ public AssignmentInterface() {
}
public void writeToAssignment(FileDAO fileDAO) throws IOException {
- String path = "assignments" + reg
- + fileDAO.getCourseID() + reg
- + fileDAO.getAssignmentID() + reg
- + "team-submissions";
+ makeSubmission(fileDAO.getCourseID(), fileDAO.getAssignmentID(), fileDAO.getFilename(), fileDAO.getTeamName(), fileDAO.getFile());
+ }
- if (!new File(path).exists()) {
- new File(path).mkdirs();
- }
- fileDAO.writeFile(path + reg + fileDAO.getFilename());
- makeSubmission(fileDAO.getCourseID(), fileDAO.getAssignmentID(), fileDAO.getFilename(), fileDAO.getTeamName());
+ public List getCourseProfanityWords(String courseID) {
+ return courseCollection.find(eq("course_id", courseID)).first().getList("blocked_words", String.class);
}
public List getAllUserAssignments(String courseID, String studentID) {
@@ -68,6 +87,12 @@ public List getAllUserAssignments(String courseID, String studentID) {
return assignments;
}
+ public Document getSpecifiedTeamSubmission(String courseID, int assignmentID, String teamID) {
+ Document teamSubmission = submissionCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID), eq("team_name", teamID), eq("type", "team_submission"))).first();
+ if (teamSubmission == null) throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Assignment does not exist").build());
+ return teamSubmission;
+ }
+
public List getSpecifiedUserAssignment(String courseID, int assignmentID, String studentID) {
MongoCursor query = submissionCollection.find(and(eq("course_id", courseID),
eq("members", studentID),
@@ -99,7 +124,7 @@ public List getAssignmentSubmissions(String courseID, int assignmentID
return assignments;
}
- public void makeSubmission(String course_id, int assignment_id, String file_name, String teamName) {
+ public void makeSubmission(String course_id, int assignment_id, String file_name, String teamName, byte[] fileData) throws IOException {
Document team = teamsCollection.find(and(eq("team_id", teamName), eq("course_id", course_id))).first();
Document assignment = assignmentsCollection.find(and(
eq("course_id", course_id),
@@ -108,7 +133,6 @@ public void makeSubmission(String course_id, int assignment_id, String file_name
if (assignment == null)
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("this assignment was not found in this course").build());
String assignmentName = assignment.getString("assignment_name");
- System.out.println(team);
if (team == null) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("this team was not found in this course").build());
@@ -119,29 +143,42 @@ public void makeSubmission(String course_id, int assignment_id, String file_name
if (team.get("team_id", String.class) == null) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("team_id not defined").build());
}
- String path = "assignments" + reg + course_id + reg + assignment_id + reg + "team_submissions";
Document new_submission = new Document()
.append("course_id", course_id)
.append("assignment_id", assignment_id)
.append("assigment_name", assignmentName)
.append("submission_name", file_name)
+ .append("submission_data", Base64.getDecoder().decode(new String(fileData)))
.append("team_name", team.getString("team_id"))
.append("members", team.getList("team_members", String.class))
.append("type", "team_submission")
.append("grade", -1)
- .append("path", path + reg + file_name)
- .append("peer_review_due_date", assignment.get("peer_review_due_date"));
- System.out.println(new_submission);
+ .append("peer_review_due_date", assignment.get("peer_review_due_date"))
+ .append("due_date", assignment.get("due_date"));
+
boolean submissionCheck = submissionCollection.find(and(eq("course_id", course_id), eq("assignment_id", assignment_id), eq("team_name", team.getString("team_id")))).iterator().hasNext();
+ // do some sort of spinning lock here so that only one teammate at a time is submitting an assignment at a time
+ //key is assignment_ID+team_name+type
+ while(assignmentLock.containsKey(assignment_id+team.getString("team_id")+"team_submission"));
+ //set the lock
+ assignmentLock.put(assignment_id+team.getString("team_id")+"team_submission", true);
if (submissionCheck) {
Document extensionCheck = submissionCollection.find(and(eq("course_id", course_id), eq("assignment_id", assignment_id), eq("team_name", team.getString("team_id")))).first();
if (extensionCheck.getString("submission_name").equals(file_name)) {
+ //remove the lock
+ assignmentLock.remove(assignment_id+team.getString("team_id")+"team_submission");
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("submission already exists").build());
} else {
submissionCollection.deleteOne(extensionCheck);
submissionCollection.insertOne(new_submission);
+ //remove the lock
+ assignmentLock.remove(assignment_id+team.getString("team_id")+"team_submission");
}
- } else submissionCollection.insertOne(new_submission);
+ } else {
+ submissionCollection.insertOne(new_submission);
+ //remove the lock
+ assignmentLock.remove(assignment_id+team.getString("team_id")+"team_submission");
+ }
}
public Document allAssignments(String course_id, String student_id) {
@@ -240,6 +277,27 @@ public void makeFinalGrades(String courseID, int assignmentID) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("team: " + review + "'s review has no points.").build());
} else {
total_points += team_review.get("grade", Integer.class);
+
+ /* Insert the peer review score into each student in the team in the student collection */
+ List teamMembers = team_review.getList("reviewed_team", String.class);
+ String teamName = team_review.getString("reviewed_team");
+ double grade = team_review.getDouble("grade");
+ String reviewedBy = team_review.getString("reviewed_by");
+ int assignmentId = team_review.getInteger("assignment_id");
+ teamMembers.forEach(studentId -> {
+ Bson studentQuery = eq("student_id", studentId);
+ Document student = studentCollection.find(studentQuery).first();
+ List peerReviewGrades = student.getList("peer_reviews", Document.class);
+ Document newPeerReview = new Document()
+ .append("team_name", teamName)
+ .append("grade", grade)
+ .append("reviewed_by", reviewedBy)
+ .append("assignment_id", assignmentId);
+ peerReviewGrades.add(newPeerReview);
+ Bson updateRule = Updates.set("peer_reviews", peerReviewGrades);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ studentCollection.updateOne(studentQuery, updateRule, options);
+ });
}
}
}
@@ -247,6 +305,21 @@ public void makeFinalGrades(String courseID, int assignmentID) {
final_grade = ((int) (final_grade * 100) / 100.0); //round to the nearest 10th
submissionCollection.findOneAndUpdate(team_submission, set("grade", final_grade));
assignmentsCollection.findOneAndUpdate(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), set("grade_finalized", true));
+
+ /* Updating student's grade */
+ for (String member : team_submission.getList("members", String.class)) {
+ List grades = new ArrayList<>();
+ grades.addAll(studentCollection.find(eq("student_id", member)).first().getList("grades", Document.class));
+ Document newAssignmentGrade = new Document()
+ .append("assignment_id", assignmentID)
+ .append("grade", final_grade)
+ .append("team_name", team_submission.getString("team_name"));
+ grades.add(newAssignmentGrade);
+ Bson filter = eq("student_id", member);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ Bson update = Updates.set("team_submissions", grades);
+ studentCollection.updateOne(filter, update, options);
+ }
}
}
}
@@ -270,4 +343,424 @@ public List getToDosByCourse(String courseID, String studentID) {
}
return assignments;
}
+
+ /**
+ * Ensures that a request carried out by a client that needs to access assignment information is actually
+ * the professor of the course in context
+ * @param securityContext
+ * @param courseID
+ */
+ public void checkProfessor(SecurityContext securityContext, String courseID){
+ String professorID = securityContext.getUserPrincipal().getName().split("@")[0];
+ Document professorDocument = professorCollection.find(eq("professor_id", professorID)).first();
+ if (professorDocument == null) throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).entity("This professor does not exist.").build());
+ Document courseDocument = courseCollection.find(Filters.eq("course_id", courseID)).first();
+ if(courseDocument == null) throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).entity("This course does not exist.").build());
+ String professorIDActual = courseDocument.getString("professor_id");
+ if (!professorIDActual.equals(professorID)) throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN).entity("User principal name doesn't match").build());
+ }
+
+
+ /**
+ * Inserts the file data related to a predefined assignment
+ * @param submissions
+ * @param zipFolder
+ */
+ public void insertAssignmentFiles(MongoCursor submissions, String courseID, ZipOutputStream zipFolder) throws IOException {
+ while(submissions.hasNext()){
+ //get the current submission
+ Document currentSubmission = submissions.next();
+ //make the file path to save the submission in
+ String path;
+ String assignmentNameNoSpaces = currentSubmission.getString("assigment_name").replace(" ", "_");
+ Binary submission_data = (Binary) currentSubmission.get("submission_data");
+ if(currentSubmission.getString("type").equals("team_submission")){
+ path = courseID+"/"+assignmentNameNoSpaces+"/"+currentSubmission.getString("team_name")+"/submission/"+currentSubmission.getString("submission_name");
+ System.out.println(path);
+ ZipEntry curEntry = new ZipEntry(path);
+ zipFolder.putNextEntry(curEntry);
+ zipFolder.write(submission_data.getData(), 0, submission_data.getData().length);
+ zipFolder.closeEntry();
+ }else{
+ //if it is a peer review, save the data in the folder in both the to/from teams
+ String fromTeam = currentSubmission.getString("reviewed_by");
+ String toTeam = currentSubmission.getString("reviewed_team");
+ //from team first
+ path = courseID+"/"+assignmentNameNoSpaces+"/"+fromTeam+"/peer-reviews/given/"+currentSubmission.getString("submission_name");
+ ZipEntry curEntryFromTeam = new ZipEntry(path);
+ zipFolder.putNextEntry(curEntryFromTeam);
+ zipFolder.write(submission_data.getData(), 0, submission_data.getData().length);
+ zipFolder.closeEntry();
+
+
+ //to team last
+ path = courseID+"/"+assignmentNameNoSpaces+"/"+toTeam+"/peer-reviews/received/"+currentSubmission.getString("submission_name");
+ ZipEntry curEntryToTeam = new ZipEntry(path);
+ zipFolder.putNextEntry(curEntryToTeam);
+ zipFolder.write(submission_data.getData(), 0, submission_data.getData().length);
+ zipFolder.closeEntry();
+ }
+ }
+
+ }
+
+ /**
+ * Add all assignment files related to a specific student
+ * @param submissions
+ * @param courseID
+ * @param studentID
+ * @param zipFolder
+ * @throws IOException
+ */
+ public void insertAssignmentFilesStudent(MongoCursor submissions, String courseID, String studentID, ZipOutputStream zipFolder) throws IOException {
+ while(submissions.hasNext()){
+ //get the current submission
+ Document currentSubmission = submissions.next();
+ //check and make sure the submission has the current studentID attached to the submitter. If it does not, just continue the loop
+ if(currentSubmission.getString("type").equals("team_submission")){
+ List submission_members = currentSubmission.getList("members", String.class);
+ if (submission_members.stream().noneMatch(s -> s.equals(studentID))) {
+ continue;
+ }
+ }else{
+ List submission_members = currentSubmission.getList("reviewed_by_members", String.class);
+ if (submission_members.stream().noneMatch(s -> s.equals(studentID))) {
+ continue;
+ }
+ }
+
+ //make the file path to save the submission in
+ String path;
+ String assignmentNameNoSpaces = currentSubmission.getString("assigment_name").replace(" ", "_");
+ Binary submission_data = (Binary) currentSubmission.get("submission_data");
+ if(currentSubmission.getString("type").equals("team_submission")){
+ path = courseID+"/"+assignmentNameNoSpaces+"/"+currentSubmission.getString("team_name")+"/submission/"+currentSubmission.getString("submission_name");
+ System.out.println(path);
+ ZipEntry curEntry = new ZipEntry(path);
+ zipFolder.putNextEntry(curEntry);
+ zipFolder.write(submission_data.getData(), 0, submission_data.getData().length);
+ zipFolder.closeEntry();
+ }else{
+ //if it is a peer review, save the data in the folder in both the to/from teams
+ String fromTeam = currentSubmission.getString("reviewed_by");
+ String toTeam = currentSubmission.getString("reviewed_team");
+ //from team first
+ path = courseID+"/"+assignmentNameNoSpaces+"/"+fromTeam+"/peer-reviews/given/"+currentSubmission.getString("submission_name");
+ ZipEntry curEntryFromTeam = new ZipEntry(path);
+ zipFolder.putNextEntry(curEntryFromTeam);
+ zipFolder.write(submission_data.getData(), 0, submission_data.getData().length);
+ zipFolder.closeEntry();
+
+
+ //to team last
+ path = courseID+"/"+assignmentNameNoSpaces+"/"+toTeam+"/peer-reviews/received/"+currentSubmission.getString("submission_name");
+ ZipEntry curEntryToTeam = new ZipEntry(path);
+ zipFolder.putNextEntry(curEntryToTeam);
+ zipFolder.write(submission_data.getData(), 0, submission_data.getData().length);
+ zipFolder.closeEntry();
+ }
+ }
+
+ }
+
+
+ /**
+ * Returns a zip file containing all completed assignment submissions
+ * @param courseID
+ * @return File
+ */
+ public File aggregateSubmissions(String courseID) throws IOException {
+ //make the temporary zip folder
+ File tempFile = Files.createTempFile(courseID, ".zip").toFile();
+ String tempPath = tempFile.getAbsolutePath();
+ ZipOutputStream zipFolder = new ZipOutputStream(new FileOutputStream(tempPath));
+ //get a list of all the assignments
+ for (Document currentAssignment : assignmentsCollection.find(eq("course_id", courseID))) {
+ //get the next assignment related to the current course
+ //get this to get the submissions quicker
+ Integer assignment_ID = currentAssignment.getInteger("assignment_id");
+ //now iterate through the submissions for the assignment and put them in the proper place in the zip folder
+ insertAssignmentFiles(submissionCollection.find(eq("assignment_id", assignment_ID)).iterator(), courseID, zipFolder);
+ }
+ zipFolder.close();
+
+ //return the file with all the assignment data
+ return tempFile;
+ }
+
+ /**
+ * Returns a zip file containing all completed assignment submissions for a particular assignment
+ * @param courseID
+ * @param assignment_ID
+ * @return File
+ */
+ public File aggregateSubmissions(String courseID, Integer assignment_ID) throws IOException {
+ //make the temporary zip folder
+ File tempFile = Files.createTempFile(courseID, ".zip").toFile();
+ String tempPath = tempFile.getAbsolutePath();
+ ZipOutputStream zipFolder = new ZipOutputStream(new FileOutputStream(tempPath));
+ insertAssignmentFiles(submissionCollection.find(eq("assignment_id", assignment_ID)).iterator(), courseID, zipFolder);
+ zipFolder.close();
+
+ //return the file with all the assignment data
+ return tempFile;
+ }
+
+
+ /**
+ * Returns a zip file containing all submissions related to the student with the given studentID
+ * @param courseID
+ * @param studentID
+ * @return
+ * @throws IOException
+ */
+ public File aggregateSubmissionsStudent(String courseID, String studentID) throws IOException {
+ //make the temporary zip folder
+ File tempFile = Files.createTempFile(courseID+studentID, ".zip").toFile();
+ String tempPath = tempFile.getAbsolutePath();
+ ZipOutputStream zipFolder = new ZipOutputStream(new FileOutputStream(tempPath));
+ //get a list of all the assignments
+ for (Document currentAssignment : assignmentsCollection.find(eq("course_id", courseID))) {
+ //get the next assignment related to the current course
+ //get this to get the submissions quicker
+ Integer assignment_ID = currentAssignment.getInteger("assignment_id");
+ //now iterate through the submissions for the assignment and put them in the proper place in the zip folder
+ insertAssignmentFilesStudent(submissionCollection.find(eq("assignment_id", assignment_ID)).iterator(), courseID, studentID, zipFolder);
+ }
+ zipFolder.close();
+
+ //return the file with all the assignment data
+ return tempFile;
+ }
+
+ /**
+ * Retrieves all assignments pertaining to a specific course.
+ *
+ * @param courseId id of the course
+ */
+ public List getAllCourseAssignmentsAndPeerReviews(String courseId) {
+
+ /* Get all individual grades on assignments for students in the course */
+ Document course = courseCollection.find(eq("course_id", courseId)).first();
+ if (course == null) {
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Course does not exist.").toString());
+ }
+ List enrolledStudents = course.getList("students", String.class);
+ List submissions = new ArrayList<>();
+ for (String studentId : enrolledStudents) {
+ List subs = submissionCollection.find(
+ and(
+ eq("course_id", courseId),
+ (eq("members", studentId)),
+ (eq("type", "team_submission"))
+ )
+ ).into(new ArrayList<>());
+ for (Document sub : subs) {
+ sub.append("student_id", studentId);
+ submissions.add(sub);
+ }
+ }
+
+ /* Get all peer review scores for each team for assignments done by said team */
+ List peerReviews = submissionCollection.find(
+ and(
+ eq("course_id", courseId),
+ (eq("type", "peer_review_submission"))
+ )
+ ).into(new ArrayList<>());
+ submissions.addAll(peerReviews);
+ return submissions;
+ }
+
+ /**
+ * Updates an individual's score for an assignment or specific peer review.
+ *
+ * @param studentId id of the student whose grade is being updated.
+ * @param submissionId the id of the student's submission to be edited.
+ * @param newGrade the new grade for the student on the given assignment.
+ * @param type indicates if the grade being updated is for an assignment or a peer review.
+ * @throws WebApplicationException if the student does not exist or the student has no grade
+ * for the assignment.
+ */
+ public void updateIndividualGrade(String studentId, int submissionId, String reviewingTeam, String newGrade, String type) {
+ /* Find the correct student */
+ System.out.println(studentId);
+ Bson studentQuery = eq("student_id", studentId);
+ Document student = studentCollection.find(studentQuery).first();
+
+ /* Throw an exception if no such student exists. */
+ if (student == null) {
+ throw new WebApplicationException("Student could not be found.", 300);
+ }
+
+ /* Find the correct assignment to update for the student. */
+ List assignmentGrades = student.getList("team_submissions", Document.class);
+ List peerReviewGrades = student.getList("peer_reviews", Document.class);
+ Document assignment = null;
+
+ if (type.equalsIgnoreCase("teamSubmission")) {
+ for (Document studentAssignment : assignmentGrades) {
+ if (studentAssignment.getInteger("assignment_id") == submissionId) {
+ assignmentGrades.remove(studentAssignment);
+ assignment = new Document()
+ .append("assignment_id", studentAssignment.getInteger("assignment_id"))
+ .append("grade", Double.valueOf(newGrade))
+ .append("team_name", studentAssignment.getString("team_name"));
+ assignmentGrades.add(assignment);
+ break;
+ }
+ }
+
+ /* Throw an exception if the student does not have a grade for the specified assignment */
+ if (assignment == null) {
+ throw new WebApplicationException("The student does not have a grade for the given assignment.", 402);
+ }
+
+ /* Update the student's grade */
+ Bson replaceStudentQuery = eq("student_id", studentId);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ Bson update = Updates.set("team_submissions", assignmentGrades);
+ } else if (type.equalsIgnoreCase("peerReview")) {
+ for (Document studentAssignment : peerReviewGrades) {
+ if (studentAssignment.getInteger("assignment_id") == submissionId && studentAssignment.getString("reviewed_by").equals(reviewingTeam)) {
+ peerReviewGrades.remove(studentAssignment);
+ assignment = new Document()
+ .append("assignment_id", studentAssignment.getInteger("assignment_id"))
+ .append("grade", Double.valueOf(newGrade))
+ .append("reviewed_by", studentAssignment.getString("reviewed_by"))
+ .append("reviewed_team", studentAssignment.getString("reviewed_team"));
+ peerReviewGrades.add(assignment);
+ break;
+ }
+ }
+
+ /* Throw an exception if the student does not have a grade for the specified assignment */
+ if (assignment == null) {
+ throw new WebApplicationException("The student does not have a grade for the given assignment.", 404);
+ }
+
+ /* Update the student's grade */
+ Bson replaceStudentQuery = eq("student_id", studentId);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ Bson update = Updates.set("peer_reviews", peerReviewGrades);
+ studentCollection.updateOne(replaceStudentQuery, update, options);
+ } else {
+ throw new WebApplicationException("Invalid type given.", 400);
+ }
+ }
+
+ /**
+ * Updates a team's grade for either an assignment or an individual peer review.
+ *
+ * @param reviewedTeamName the name of the team that was reviewed
+ * @param reviewingTeamName the name of the team who completed the peer review (if applicable)
+ * @param assignmentId id of the assignment whose grade is to be edited
+ * @param newGrade the new grade for the submission
+ * @param type indicating if it is a team submission or peer review to be edited
+ */
+ public void updateTeamGrade(String reviewedTeamName, String reviewingTeamName, int assignmentId, String newGrade, String type) {
+ /* Find the correct submission */
+ Bson assignmentQuery = null;
+ if (type.equalsIgnoreCase("teamSubmission")) {
+ assignmentQuery = and(
+ eq("team_name", reviewedTeamName),
+ eq("assignment_id", assignmentId),
+ eq("type", "team_submission")
+ );
+ } else if (type.equalsIgnoreCase("peerReview")) {
+ assignmentQuery = and(
+ eq("reviewed_by", reviewingTeamName),
+ eq("reviewed_team", reviewedTeamName),
+ eq("assignment_id", assignmentId),
+ eq("type", "peer_review_submission")
+ );
+ } else {
+ throw new WebApplicationException("Invalid type given.", 400);
+ }
+ Document assignment = submissionCollection.find(assignmentQuery).first();
+
+ /* Throw an exception if the given submission does not exist */
+ if (assignment == null) {
+ throw new WebApplicationException("No such assignment exists.", 303);
+ }
+
+ /* Update the grade in the submissions collection */
+ Bson submissionUpdate = Updates.set("grade", newGrade);
+ submissionCollection.updateOne(assignmentQuery, submissionUpdate, new UpdateOptions().upsert(true));
+
+ /* Update the grade for each student in the students collection */
+ if (type.equalsIgnoreCase("teamSubmission")) {
+ List teamMembers = assignment.getList("members", String.class);
+ teamMembers.forEach(teamMemberId -> {
+ Bson studentQuery = eq("student_id", teamMemberId);
+ Document student = studentCollection.find(studentQuery).first();
+ assert student != null;
+ List teamSubmissionGrades = student.getList("team_submissions", Document.class);
+ Document teamSubmissionDocument = null;
+ for (Document teamSubmission : teamSubmissionGrades) {
+ if (teamSubmission.getInteger("assignment_id") == assignmentId && teamSubmission.getString("team_name").equals(reviewedTeamName)) {
+ teamSubmissionGrades.remove(teamSubmission);
+ teamSubmissionDocument = new Document()
+ .append("assignment_id", teamSubmission.getInteger("assignment_id"))
+ .append("grade", Double.valueOf(newGrade))
+ .append("team_name", teamSubmission.getString("team_name"));
+ teamSubmissionGrades.add(teamSubmissionDocument);
+ break;
+ }
+ }
+ if (teamSubmissionDocument == null) {
+ throw new WebApplicationException("This student does not have a grade for the specified assignment", 301);
+ }
+ Bson update = Updates.set("team_submissions", teamSubmissionGrades);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ studentCollection.updateOne(studentQuery, update, options);
+ });
+ } else {
+ List teamMembers = assignment.getList("reviewed_team_members", String.class);
+ teamMembers.forEach(teamMemberId -> {
+ Bson studentQuery = eq("student_id", teamMemberId);
+ Document student = studentCollection.find(studentQuery).first();
+ assert student != null;
+ List peerReviewGrades = student.getList("peer_reviews", Document.class);
+ Document peerReviewDocument = null;
+ for (Document peerReview : peerReviewGrades) {
+ System.out.println("new one");
+ System.out.println("Student ID: " + teamMemberId);
+
+ System.out.println("Assignment ID");
+ System.out.println(peerReview.getInteger("assignment_id") == assignmentId);
+ System.out.println(assignmentId);
+ System.out.println(peerReview.getInteger("assignment_id"));
+
+ System.out.println("Reviewed By");
+ System.out.println(peerReview.getString("reviewed_by").equalsIgnoreCase(reviewingTeamName));
+ System.out.println(reviewingTeamName);
+ System.out.println(peerReview.getString("reviewed_by"));
+
+ System.out.println("Team name");
+ System.out.println(peerReview.getString("reviewed_team").equalsIgnoreCase(reviewedTeamName));
+ System.out.println(reviewedTeamName);
+ System.out.println(peerReview.getString("reviewed_team"));
+
+ if ((peerReview.getInteger("assignment_id") == assignmentId) && (peerReview.getString("reviewed_by").equalsIgnoreCase(reviewingTeamName))
+ && (peerReview.getString("reviewed_team").equalsIgnoreCase(reviewedTeamName))) {
+ peerReviewGrades.remove(peerReview);
+ peerReviewDocument = new Document()
+ .append("assignment_id", peerReview.getInteger("assignment_id"))
+ .append("grade", Double.valueOf(newGrade))
+ .append("team_name", peerReview.getString("reviewed_team"))
+ .append("reviewed_by", reviewingTeamName);
+ peerReviewGrades.add(peerReviewDocument);
+ break;
+ }
+ }
+ if (peerReviewDocument == null) {
+ throw new WebApplicationException("This student does not have a grade for the specified assignment", 300);
+ }
+ Bson update = Updates.set("peer_reviews", peerReviewGrades);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ studentCollection.updateOne(studentQuery, update, options);
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/DatabaseManager.java b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/studentAssignmentResource.java b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/studentAssignmentResource.java
old mode 100644
new mode 100755
index 2e20bf006..fe469391e
--- a/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/studentAssignmentResource.java
+++ b/backend/student-assignment-microservice/src/main/java/edu/oswego/cs/rest/resources/studentAssignmentResource.java
@@ -1,25 +1,34 @@
+
package edu.oswego.cs.rest.resources;
import com.ibm.websphere.jaxrs20.multipart.IAttachment;
import edu.oswego.cs.rest.daos.FileDAO;
import edu.oswego.cs.rest.database.AssignmentInterface;
+import org.apache.tika.exception.TikaException;
import org.bson.Document;
+import org.bson.types.Binary;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+import org.xml.sax.SAXException;
import javax.annotation.security.DenyAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
import java.io.File;
import java.io.IOException;
-import java.util.Arrays;
+import java.nio.file.Files;
+import java.util.Base64;
import java.util.List;
-import java.util.Optional;
@Path("student")
@DenyAll
public class studentAssignmentResource {
+ //change
+
/**
* Retrieves the assignment from its location on the server and passes it to the front end via the request header
* as a stream. The request entity passes an InputStream[] with the assignment files in each array.
@@ -37,17 +46,18 @@ public Response downloadAssignment(
@PathParam("courseID") String courseID,
@PathParam("assignmentID") int assignmentID,
@PathParam("teamName") String teamName) {
-
- String path = "assignments" + "/" + courseID + "/" + assignmentID + "/" + "team-submissions" + "/";
- Optional file = Arrays.stream(new File(path).listFiles()).filter(f -> f.getName().contains(teamName)).findFirst();
- if (file.isEmpty())
+ Document teamSubmission = new AssignmentInterface().getSpecifiedTeamSubmission(courseID, assignmentID, teamName);
+ if (teamSubmission == null)
return Response.status(Response.Status.BAD_REQUEST).entity("Assignment Does Not Exist").build();
- Response.ResponseBuilder response = Response.ok(file.get());
- response.header("Content-Disposition", "attachment; filename=" + file.get().getName());
+ Binary fileData = (Binary) teamSubmission.get("submission_data");
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(fileData.getData()));
+ response.header("Content-Disposition", "attachment; filename=" + teamSubmission.get("submission_name"));
return response.build();
}
+ //change
+
/**
* File is uploaded as form-data and passed back as a List
* The attachment is processed in FileDao.FileFactory, which reads and
@@ -60,21 +70,26 @@ public Response downloadAssignment(
*/
@POST
@RolesAllowed({"professor", "student"})
- @Produces({MediaType.MULTIPART_FORM_DATA, "application/pdf"})
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Produces(MediaType.MULTIPART_FORM_DATA)
@Path("/courses/{courseID}/assignments/{assignmentID}/{teamName}/upload")
public Response addFileToAssignment(
List attachments,
@PathParam("courseID") String courseID,
@PathParam("assignmentID") int assignmentID,
@PathParam("teamName") String teamName
- ) throws IOException {
+ ) throws TikaException, SAXException {
for (IAttachment attachment : attachments) {
if (attachment == null) continue;
String fileName = attachment.getDataHandler().getName();
String fileExt = fileName.substring(fileName.indexOf("."));
if (!fileName.endsWith("pdf") && !fileName.endsWith("docx"))
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).build();
- new AssignmentInterface().writeToAssignment(FileDAO.fileFactory(teamName.concat(fileExt), courseID, attachment, assignmentID, teamName));
+ try {
+ new AssignmentInterface().writeToAssignment(FileDAO.fileFactory(teamName.concat(fileExt), courseID, attachment, assignmentID, teamName));
+ } catch (IOException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
}
return Response.status(Response.Status.OK).entity("Successfully uploaded assignment.").build();
}
@@ -128,4 +143,160 @@ public Response viewSubmissions(
public Response viewToDos(@PathParam("courseID") String courseID, @PathParam("student_id") String student_id) {
return Response.status(Response.Status.OK).entity(new AssignmentInterface().getToDosByCourse(courseID, student_id)).build();
}
-}
\ No newline at end of file
+
+ /**
+ * @param courseID
+ * @return String
+ *
+ * Returns a Base64 string of the zip file containing all submission/peer review information relative to all the assignments
+ * in a given course that the frontend can decode and get a zip file from. Note that if no peer reviews are added to the assignment
+ * the Peer-Reviews folder won't be made.
+ * Structure of the unzipped file goes:
+ *
+ * |
+ * |
+ * L->
+ * |
+ * |
+ * L->
+ * |
+ * |
+ * L-> --->
+ * L->
+ * |
+ * |
+ * L-> --->
+ * L-> --->
+ */
+ @GET
+ @RolesAllowed("professor")
+ @Path("{course_id}/course-assignment-files")
+ @Produces({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_OCTET_STREAM})
+ public Response getAllCourseDocuments(@Context SecurityContext securityContext, @PathParam("course_id") String courseID) throws IOException {
+ //make sure the user is the professor of the course
+ new AssignmentInterface().checkProfessor(securityContext, courseID);
+ //create the zip file
+ File zipFile = new AssignmentInterface().aggregateSubmissions(courseID);
+ //create the base64 representation
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(Files.readAllBytes(zipFile.toPath())));
+ response.header("Content-Disposition", "attachment; filename=" + courseID + ".zip");
+ //delete the temp file
+ zipFile.delete();
+ //send back the zip file Base64
+ return response.build();
+ }
+
+ /**
+ * @param courseID
+ * @return String
+ *
+ * Returns a Base64 string of the zip file containing all submission/peer review information relative to all the assignments
+ * in a given course that the frontend can decode and get a zip file from. Note that if no peer reviews are added to the assignment
+ * the Peer-Reviews folder won't be made.
+ * Structure of the unzipped file goes:
+ *
+ * |
+ * |
+ * L->
+ * |
+ * |
+ * |
+ * L-> --->
+ * L->
+ * |
+ * |
+ * L-> --->
+ * L-> --->
+ */
+ @GET
+ @RolesAllowed("professor")
+ @Path("{course_id}/{student_id}/course-assignment-files-student")
+ @Produces({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_OCTET_STREAM})
+ public Response getAllCourseDocumentsStudent(@Context SecurityContext securityContext, @PathParam("course_id") String courseID, @PathParam("student_id") String studentID) throws IOException {
+ //make sure the user is the professor of the course
+ System.out.println("here we are");
+ new AssignmentInterface().checkProfessor(securityContext, courseID);
+ //create the zip file
+ File zipFile = new AssignmentInterface().aggregateSubmissionsStudent(courseID, studentID);
+ //create the base64 representation
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(Files.readAllBytes(zipFile.toPath())));
+ response.header("Content-Disposition", "attachment; filename=" + courseID + "-" + studentID + "Submissions.zip");
+ //delete the temp file
+ zipFile.delete();
+ //send back the zip file Base64
+ return response.build();
+ }
+
+ /**
+ * @param courseID
+ * @param assignmentID
+ * @return String
+ *
+ * Returns a Base64 string of the zip file containing all submission/peer review information relative to the given assignment
+ * that the frontend can decode and get a zip file from. Note that if no peer reviews are added to the assignment
+ * the Peer-Reviews folder won't be made.
+ * Structure of unzipped file goes:
+ *
+ *
+ * |
+ * |
+ * L->
+ * |
+ * |
+ * L->
+ * |
+ * |
+ * L-> --->
+ * L->
+ * |
+ * |
+ * L-> --->
+ * L-> --->
+ */
+ @GET
+ @RolesAllowed("professor")
+ @Path("{assignment_id}/{course_id}/course-assignment-files")
+ @Produces({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_OCTET_STREAM})
+ public Response getAssignmentDocuments(@Context SecurityContext securityContext, @PathParam("course_id") String courseID, @PathParam("assignment_id") String assignmentID) throws IOException {
+ //make sure the user is the professor of the course
+ new AssignmentInterface().checkProfessor(securityContext, courseID);
+ //create the zip file
+ File zipFile = new AssignmentInterface().aggregateSubmissions(courseID, Integer.parseInt(assignmentID));
+ //create the base64 representation
+ Response.ResponseBuilder response = Response.ok(Base64.getEncoder().encode(Files.readAllBytes(zipFile.toPath())));
+ response.header("Content-Disposition", "attachment; filename=" + courseID + ".zip");
+ //delete the temp file
+ zipFile.delete();
+ //send back the zip file Base64
+ return response.build();
+ }
+
+ @GET
+ @RolesAllowed("professor")
+ @Path("{courseID}/all-grades")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getAllAssignmentsAndPeerReviews(@PathParam("courseID") String courseId) {
+ List docs = new AssignmentInterface().getAllCourseAssignmentsAndPeerReviews(courseId);
+ return Response.status(Response.Status.OK).entity(docs).build();
+ }
+
+ @POST
+ @RolesAllowed("professor")
+ @Path("edit/{assignmentId}/{teamName}/{type}/{reviewedBy}/{newGrade}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateTeamGrade(@PathParam("assignmentId") int assignmentId, @PathParam("teamName") String teamName, @PathParam("type") String type,
+ @PathParam("reviewedBy") String reviewedBy, @PathParam("newGrade") String newGrade) {
+ new AssignmentInterface().updateTeamGrade(teamName, reviewedBy, assignmentId, newGrade, type);
+ return Response.status(201).build();
+ }
+
+ @POST
+ @RolesAllowed("professor")
+ @Path("edit/{studentId}/{assignmentId}/{reviewedBy}/{type}/{newGrade}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateIndividualGrade(@PathParam("newGrade") String newGrade, @PathParam("studentId") String studentId, @PathParam("assignmentId") int assignmentId,
+ @PathParam("type") String type, @PathParam("reviewedBy") String reviewedBy) {
+ new AssignmentInterface().updateIndividualGrade(studentId, assignmentId, reviewedBy,newGrade, type);
+ return Response.status(201).build();
+ }
+}
diff --git a/backend/student-assignment-microservice/src/main/liberty/config/server.xml b/backend/student-assignment-microservice/src/main/liberty/config/server.xml
old mode 100644
new mode 100755
diff --git a/backend/student-assignment-microservice/src/test/java/StudentAssignmentTests.java b/backend/student-assignment-microservice/src/test/java/StudentAssignmentTests.java
old mode 100644
new mode 100755
index 3f7aaa432..bdd48061f
--- a/backend/student-assignment-microservice/src/test/java/StudentAssignmentTests.java
+++ b/backend/student-assignment-microservice/src/test/java/StudentAssignmentTests.java
@@ -1,10 +1,351 @@
+import com.mongodb.*;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.model.Filters;
+import edu.oswego.cs.rest.database.AssignmentInterface;
+import org.bson.Document;
import org.junit.jupiter.api.*;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
+import java.util.Arrays;
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+//please use the default db setup outlined in the "Database Default for Testing" file
+//TC|ID 1.1 = TCIDOnePointOne is the naming convention for now
+public class StudentAssignmentTests {
+
+ AssignmentInterface assignInter = new AssignmentInterface();
+ MongoClient mongoClient, mongoClient2, mongoClient3, mongoClient4, mongoClient5;
+ MongoCollection teamCollection, assignmentCollection, submissionsCollection;
+
+
+//------------------------------------------------------------------------------------------------------------------------------
+
+ //To test:
+ //public void makeSubmission(String course_id, int assignment_id, String file_name, String teamName)
+
+ @Test
+ public void makeSubmissionTCIDOnePointOne(){//valid course_id
+
+ //gives access to db
+ //------------------------------------------------------------------------------------------------------------------------------
+ //from env file
+ String username = "root";
+ String database = "admin";
+ String password = "password";
+ //Create credentials
+ MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder()
+ .writeConcern(WriteConcern.JOURNALED).build();
+
+ mongoClient4 = new MongoClient(new ServerAddress("localhost", 27040), Arrays.asList(mongoCredential), options);
+ submissionsCollection = mongoClient4.getDatabase("cpr").getCollection("submissions");
+ //------------------------------------------------------------------------------------------------------------------------------
+
+ String course_id= "CSC-480-46374-SPR-2023";
+ int assignment_id= 1;
+ String file_name= "file1.pdf";//to add to submissions Collection
+ String teamName= "Team1";//name of team that submitted the file
+// assignInter.makeSubmission(course_id,assignment_id,file_name,teamName);//call method to be tested
+
+ //check if file was added to submissions Collection
+
+ MongoCursor query = submissionsCollection.find(and(
+ eq("course_id", course_id),
+ eq("assignment_id", assignment_id),
+ eq("submission_name", file_name),
+ eq("team_name", teamName))).iterator();
+ Document docFromQuery= query.next();//gets the Document returned by the query
+ String queriedSubmissionName= docFromQuery.get("submission_name",file_name);
+
+ Document expectedDoc= new Document()
+ .append("course_id", course_id)
+ .append("assignment_id", assignment_id)
+ .append("submission_name", file_name)
+ .append("team_name", teamName);
+ String expectedSubmissionName= expectedDoc.get("submission_name",file_name);
+
+ assertEquals(expectedSubmissionName,queriedSubmissionName);
+
+ //remove the change we made to the db (ie. remove the Document we just added)
+ submissionsCollection.deleteOne(and(Filters.
+ eq("course_id", course_id),
+ eq("assignment_id", assignment_id),
+ eq("submission_name", file_name),
+ eq("team_name", teamName)));
+
+
+ }
+
+ @Test
+ public void makeSubmissionTCIDOnePointA(){//null course_id
+
+ //gives access to db
+ //------------------------------------------------------------------------------------------------------------------------------
+ //from env file
+ String username = "root";
+ String database = "admin";
+ String password = "password";
+ //Create credentials
+ MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder()
+ .writeConcern(WriteConcern.JOURNALED).build();
+
+ mongoClient4 = new MongoClient(new ServerAddress("localhost", 27040), Arrays.asList(mongoCredential), options);
+ submissionsCollection = mongoClient4.getDatabase("cpr").getCollection("submissions");
+ //------------------------------------------------------------------------------------------------------------------------------
+
+ String course_id= null;
+ int assignment_id= 1;
+ String file_name= "file1.pdf";//to add to submissions Collection
+ String teamName= "Team1";//name of team that submitted the file
+
+ //asserts that a javax.ws.rs.WebApplicationException is thrown
+// Exception exception = assertThrows(
+// javax.ws.rs.WebApplicationException.class,
+// () -> assignInter.makeSubmission(course_id,assignment_id,file_name,teamName));//call method to be tested
+
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 0})
+ public void makeSubmissionTCIDTwoPointAB() {//-1,0 assignment_id
+
+ //gives access to db
+ //------------------------------------------------------------------------------------------------------------------------------
+ //from env file
+ String username = "root";
+ String database = "admin";
+ String password = "password";
+ //Create credentials
+ MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder()
+ .writeConcern(WriteConcern.JOURNALED).build();
+
+ mongoClient4 = new MongoClient(new ServerAddress("localhost", 27040), Arrays.asList(mongoCredential), options);
+ submissionsCollection = mongoClient4.getDatabase("cpr").getCollection("submissions");
+ //------------------------------------------------------------------------------------------------------------------------------
+
+ String course_id ="CSC-480-46374-SPR-2023";
+ int assignment_id = -1;
+ String file_name = "file1.pdf";//to add to submissions Collection
+ String teamName = "Team1";//name of team that submitted the file
+
+ //asserts that a javax.ws.rs.WebApplicationException is thrown
+// Exception exception = assertThrows(
+// javax.ws.rs.WebApplicationException.class,
+// () -> assignInter.makeSubmission(course_id,assignment_id,file_name,teamName));//call method to be tested
+
+ }
+
+ //does not pass when run with makeSubmissionTCIDThreePointA();
+ //passes after resetting db and not running together with makeSubmissionTCIDThreePointA()
+ @Test
+ public void makeSubmissionTCIDThreePointOne(){//valid file_name
+
+ //gives access to db
+ //------------------------------------------------------------------------------------------------------------------------------
+ //from env file
+ String username = "root";
+ String database = "admin";
+ String password = "password";
+ //Create credentials
+ MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder()
+ .writeConcern(WriteConcern.JOURNALED).build();
+
+ mongoClient4 = new MongoClient(new ServerAddress("localhost", 27040), Arrays.asList(mongoCredential), options);
+ submissionsCollection = mongoClient4.getDatabase("cpr").getCollection("submissions");
+ //------------------------------------------------------------------------------------------------------------------------------
+
+ String course_id= "CSC-480-46374-SPR-2023";
+ int assignment_id= 1;
+ String file_name= "file1.csv";//to add to submissions Collection
+ String teamName= "Team1";//name of team that submitted the file
+// assignInter.makeSubmission(course_id,assignment_id,file_name,teamName);//call method to be tested
+
+ //check if file was added to submissions Collection
+
+ MongoCursor query = submissionsCollection.find(and(
+ eq("course_id", course_id),
+ eq("assignment_id", assignment_id),
+ eq("submission_name", file_name),
+ eq("team_name", teamName))).iterator();
+ Document docFromQuery= query.next();//gets the Document returned by the query
+ String queriedSubmissionName= docFromQuery.get("submission_name",file_name);
+
+ Document expectedDoc= new Document()
+ .append("course_id", course_id)
+ .append("assignment_id", assignment_id)
+ .append("submission_name", file_name)
+ .append("team_name", teamName);
+ String expectedSubmissionName= expectedDoc.get("submission_name",file_name);
+
+ assertEquals(expectedSubmissionName,queriedSubmissionName);
+
+ //remove the change we made to the db (ie. remove the Document we just added)
+ submissionsCollection.deleteOne(and(Filters.
+ eq("course_id", course_id),
+ eq("assignment_id", assignment_id),
+ eq("submission_name", file_name),
+ eq("team_name", teamName)));
+
+
+ }
+
+ /* @Test
+ public void makeSubmissionTCIDThreePointA(){//null file_name
+
+ //gives access to db
+ //------------------------------------------------------------------------------------------------------------------------------
+ //from env file
+ String username = "root";
+ String database = "admin";
+ String password = "password";
+ //Create credentials
+ MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder()
+ .writeConcern(WriteConcern.JOURNALED).build();
+
+ mongoClient4 = new MongoClient(new ServerAddress("localhost", 27040), Arrays.asList(mongoCredential), options);
+ submissionsCollection = mongoClient4.getDatabase("cpr").getCollection("submissions");
+ //------------------------------------------------------------------------------------------------------------------------------
+
+ String course_id= "CSC-480-46374-SPR-2023";
+ int assignment_id= 1;
+ String file_name= null;//to add to submissions Collection
+ String teamName= "Team1";//name of team that submitted the file
+
+ //asserts that a javax.ws.rs.WebApplicationException is thrown
+ Exception exception = assertThrows(
+ javax.ws.rs.WebApplicationException.class,
+ () -> assignInter.makeSubmission(course_id,assignment_id,file_name,teamName));//call method to be tested
+
+ }*/
+
+ @Test
+ public void makeSubmissionTCIDFourPointA(){//null teamName
+ //gives access to db
+ //------------------------------------------------------------------------------------------------------------------------------
+ //from env file
+ String username = "root";
+ String database = "admin";
+ String password = "password";
+ //Create credentials
+ MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
+ MongoClientOptions options = MongoClientOptions.builder()
+ .writeConcern(WriteConcern.JOURNALED).build();
+
+ mongoClient4 = new MongoClient(new ServerAddress("localhost", 27040), Arrays.asList(mongoCredential), options);
+ submissionsCollection = mongoClient4.getDatabase("cpr").getCollection("submissions");
+ //------------------------------------------------------------------------------------------------------------------------------
+
+ String course_id= "CSC-480-46374-SPR-2023";
+ int assignment_id= 1;
+ String file_name= "file1.pdf";//to add to submissions Collection
+ String teamName= null;//name of team that submitted the file
+
+ //asserts that a javax.ws.rs.WebApplicationException is thrown
+// Exception exception = assertThrows(
+// javax.ws.rs.WebApplicationException.class,
+// () -> assignInter.makeSubmission(course_id,assignment_id,file_name,teamName));//call method to be tested
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//The following is code from last year:
+/*
// DISCLAIMER: Don't run all the tests at the same time. You'll likely screw up the database and fail the tests in some way.
// Read through the tests to see what they create, update and delete before you run them please.
@@ -60,3 +401,5 @@ public void studentDownloadFileTest() {
}
}
+*/
+
diff --git a/backend/student-peer-review-assignment-microservice/.dockerignore b/backend/student-peer-review-assignment-microservice/.dockerignore
old mode 100644
new mode 100755
diff --git a/backend/student-peer-review-assignment-microservice/Dockerfile b/backend/student-peer-review-assignment-microservice/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/student-peer-review-assignment-microservice/pom.xml b/backend/student-peer-review-assignment-microservice/pom.xml
old mode 100644
new mode 100755
diff --git a/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/application/RestApplication.java b/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/application/RestApplication.java
old mode 100644
new mode 100755
diff --git a/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java b/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java
old mode 100644
new mode 100755
index ab7d6e54a..a60fb7f86
--- a/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java
+++ b/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/cors/CorsFilter.java
@@ -11,17 +11,19 @@
public class CorsFilter implements ContainerResponseFilter {
@Override
- public void filter(ContainerRequestContext requestContext,
- ContainerResponseContext responseContext) throws IOException {
- responseContext.getHeaders().add(
- "Access-Control-Allow-Origin", "*");
- responseContext.getHeaders().add(
- "Access-Control-Allow-Credentials", "true");
- responseContext.getHeaders().add(
- "Access-Control-Allow-Headers",
- "origin, content-type, accept, authorization");
- responseContext.getHeaders().add(
- "Access-Control-Allow-Methods",
- "GET, POST, PUT, DELETE, OPTIONS, HEAD");
+ public void filter(ContainerRequestContext requestContext,
+ ContainerResponseContext responseContext) throws IOException {
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Origin", "*");
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Credentials", "true");
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Headers",
+ "origin, content-type, accept, authorization");
+ responseContext.getHeaders().add(
+ "Access-Control-Allow-Methods",
+ "GET, POST, PUT, DELETE, OPTIONS, HEAD");
+ responseContext.getHeaders().add("Access-Control-Expose-Headers",
+ "Content-Disposition");
}
}
\ No newline at end of file
diff --git a/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java b/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/database/DatabaseManager.java
old mode 100644
new mode 100755
diff --git a/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/database/PeerReviewAssignmentInterface.java b/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/database/PeerReviewAssignmentInterface.java
old mode 100644
new mode 100755
index a03795bf2..b545252b0
--- a/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/database/PeerReviewAssignmentInterface.java
+++ b/backend/student-peer-review-assignment-microservice/src/main/java/edu/oswego/cs/database/PeerReviewAssignmentInterface.java
@@ -1,28 +1,36 @@
package edu.oswego.cs.database;
-import com.ibm.websphere.jaxrs20.multipart.IAttachment;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
-import edu.oswego.cs.daos.FileDAO;
+import com.mongodb.client.model.UpdateOptions;
+import com.mongodb.client.model.Updates;
+import com.mongodb.util.JSON;
import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.bson.types.Binary;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
+import javax.ws.rs.core.SecurityContext;
+import java.io.*;
import java.util.*;
+import java.text.DecimalFormat;
+import java.util.concurrent.ConcurrentHashMap;
import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Updates.set;
+import com.mongodb.client.FindIterable;
public class PeerReviewAssignmentInterface {
private final MongoCollection teamCollection;
private final MongoCollection assignmentCollection;
private final MongoCollection submissionsCollection;
+ private final MongoCollection studentCollection;
+ private final MongoCollection professorCollection;
+
+ static ConcurrentHashMap peerReviewLock = new ConcurrentHashMap();
MongoDatabase assignmentDB;
public PeerReviewAssignmentInterface() {
@@ -33,12 +41,15 @@ public PeerReviewAssignmentInterface() {
teamCollection = teamDB.getCollection("teams");
assignmentCollection = assignmentDB.getCollection("assignments");
submissionsCollection = assignmentDB.getCollection("submissions");
+ professorCollection = databaseManager.getProfessorDB().getCollection("professors");
+ studentCollection = databaseManager.getStudentDB().getCollection("students");
+
} catch (WebApplicationException e) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Failed to retrieve collections.").build());
}
}
- public void addPeerReviewSubmission(String course_id, int assignment_id, String srcTeamName, String destinationTeam, String fileName, int grade) {
+ public void addPeerReviewSubmission(String course_id, int assignment_id, String srcTeamName, String destinationTeam, String fileName, int grade, InputStream fileData) throws IOException {
Document reviewedByTeam = teamCollection.find(eq("team_id", srcTeamName)).first();
Document reviewedTeam = teamCollection.find(eq("team_id", destinationTeam)).first();
Document assignment = assignmentCollection.find(and(eq("course_id", course_id), eq("assignment_id", assignment_id))).first();
@@ -64,17 +75,52 @@ public void addPeerReviewSubmission(String course_id, int assignment_id, String
.append("assignment_id", assignment_id)
.append("assigment_name", assignment.getString("assignment_name"))
.append("submission_name", fileName)
+ .append("submission_data", Base64.getDecoder().decode(new String(fileData.readAllBytes())))
.append("reviewed_by", reviewedByTeam.getString("team_id"))
.append("reviewed_by_members", reviewedByTeam.getList("team_members", String.class))
.append("reviewed_team", reviewedTeam.getString("team_id"))
.append("reviewed_team_members", reviewedTeam.getList("team_members", String.class))
.append("type", "peer_review_submission")
- .append("grade", grade)
- .append("path", path + reg + fileName);
+ .append("peer_review_due_date", assignment.get("peer_review_due_date"))
+ .append("due_date", assignment.getString("due_date"))
+ .append("grade", grade);
+ List teamMembers = reviewedTeam.getList("team_members", String.class);
+ for (String member : teamMembers) {
+ Document newStudentSubmission = new Document()
+ .append("assignment_id", assignment_id)
+ .append("reviewed_team", reviewedTeam.getString("team_id"))
+ .append("reviewed_by", reviewedByTeam.getString("team_id"))
+ .append("grade", grade);
+ Bson studentQuery = eq("student_id", member);
+ Document student = studentCollection.find(studentQuery).first();
+ List peerReviews = student.getList("peer_reviews", Document.class);
+ peerReviews.add(newStudentSubmission);
+ Bson update = Updates.set("peer_reviews", peerReviews);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ studentCollection.updateOne(studentQuery, update, options);
+ }
+ //wait for the lock to be dropped.
+ //key is assignment_id+reviewed_by_team_id+reviewed_team+"peer_review_submission"
+ while (peerReviewLock.containsKey(assignment_id + reviewedByTeam.getString("team_id") + reviewedTeam.getString("team_id") + "peer_review_submission"))
+ ;
+ //lock the submission
+ peerReviewLock.put(assignment_id + reviewedByTeam.getString("team_id") + reviewedTeam.getString("team_id") + "peer_review_submission", true);
if (submissionsCollection.find(new_submission).iterator().hasNext()) {
+ //let go of the lock
+ peerReviewLock.remove(assignment_id + reviewedByTeam.getString("team_id") + reviewedTeam.getString("team_id") + "peer_review_submission");
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("submission already exists").build());
} else submissionsCollection.insertOne(new_submission);
+ //remove the lock
+ peerReviewLock.remove(assignment_id + reviewedByTeam.getString("team_id") + reviewedTeam.getString("team_id") + "peer_review_submission");
+ // Store reviewed_team_members and teams in "teams" collection
+ teamCollection.updateOne(
+ eq("team_id", reviewedTeam.getString("team_id")),
+ new Document("$set", new Document()
+ .append("reviewed_team", reviewedByTeam.getString("team_id"))
+ .append("reviewed_members", reviewedByTeam.getList("team_members", String.class))));
+ System.out.println(reviewedTeam.getString("team_id"));
+ System.out.println(reviewedByTeam.getList("team_members", String.class));
addCompletedTeam(course_id, assignment_id, srcTeamName, destinationTeam);
}
@@ -103,36 +149,37 @@ public void addCompletedTeam(String courseID, int assignmentID, String sourceTea
}
}
if (currentNumOfReviews == (int) assignmentDocument.get("reviews_per_team")) {
- makeFinalGrade(courseID, assignmentID, targetTeam);
+ //makeFinalGrade(courseID, assignmentID, targetTeam);
+ redoneMakeFinalGrade(courseID, assignmentID, targetTeam);
}
- if (assignmentDocument.get("completed_teams") == assignmentDocument.get("assigned_teams")) {
- assignmentCollection.findOneAndUpdate(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), set("grade_finalized", true));
+
+ //check if all team grades have been finalized
+ int num_of_reviews_needed = completedTeams.keySet().size()*assignmentDocument.getInteger("reviews_per_team");
+ int total_num_of_reviews = 0;
+ for (Map.Entry> entry : completedTeams.entrySet()) {
+ List list = entry.getValue();
+ total_num_of_reviews+=list.size();
}
- }
- public void uploadPeerReview(String courseID, int assignmentID, String srcTeamName, String destTeamName, IAttachment attachment) throws IOException {
- FileDAO fileDAO = FileDAO.fileFactory(courseID, srcTeamName, destTeamName, assignmentID, attachment);
- String path = "assignments" + "/" + courseID + "/" + assignmentID + "/peer-review-submissions/";
- if (!new File(path).exists()) {
- new File(path).mkdirs();
+
+ if (total_num_of_reviews==num_of_reviews_needed) {
+ assignmentCollection.findOneAndUpdate(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), set("grade_finalized", true));
}
- OutputStream outputStream = new FileOutputStream(path + fileDAO.fileName + ".pdf");
- outputStream.write(fileDAO.inputStream.readAllBytes());
- outputStream.close();
}
- public File downloadFinishedPeerReview(String courseID, int assignmentID, String srcTeamName, String destTeamName) {
- String path = "assignments/" + courseID + "/" + assignmentID + "/peer-review-submissions/";
- if (!new File(path).exists())
- throw new WebApplicationException("Peer reviews do not exist for this course yet.");
+ public String downloadFinishedPeerReviewName(String courseID, int assignmentID, String srcTeamName, String destTeamName) {
+ Document submittedPeerReview = submissionsCollection.find(and(eq("type", "peer_review_submission"), eq("assignment_id", assignmentID), eq("course_id", courseID), eq("reviewed_by", srcTeamName), eq("reviewed_team", destTeamName))).first();
+ if (submittedPeerReview == null)
+ throw new WebApplicationException("No peer review from team " + srcTeamName + " for " + destTeamName);
+ return (String) submittedPeerReview.get("submission_name");
- Optional file = Arrays.stream(new File(path).listFiles())
- .filter(f -> f.getName().contains(srcTeamName) && f.getName().contains(destTeamName))
- .findFirst();
+ }
- if (file.isEmpty())
+ public Binary downloadFinishedPeerReview(String courseID, int assignmentID, String srcTeamName, String destTeamName) {
+ Document submittedPeerReview = submissionsCollection.find(and(eq("type", "peer_review_submission"), eq("assignment_id", assignmentID), eq("course_id", courseID), eq("reviewed_by", srcTeamName), eq("reviewed_team", destTeamName))).first();
+ if (submittedPeerReview == null)
throw new WebApplicationException("No peer review from team " + srcTeamName + " for " + destTeamName);
- return file.get();
+ return (Binary) submittedPeerReview.get("submission_data");
}
@@ -260,6 +307,34 @@ public List getTeams(String courseID, int assignmentID) {
return teams;
}
+
+ public Document redoneGetTeamGrades(String courseID, int assignmentID, String teamName) {
+
+ List results = new ArrayList<>();
+
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("reviewed_team", teamName),
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission")
+ ));
+
+ iterable.into(results);
+ System.out.println(results);
+ //for each grade this team received
+ List teams = new ArrayList<>();
+ for (Document teamGradesReceived : results) {
+ //grabs the team that assigned this team the grade
+ Document document = new Document().append("team_name", teamGradesReceived.get("reviewed_by"));
+ if (results == null) document.append("grade_given", "pending");
+ else document.append("grade_given", teamGradesReceived.getInteger("grade"));
+ teams.add(document);
+ }
+
+ return new Document().append("teams", teams);
+ }
+
+ @Deprecated
public Document getTeamGrades(String courseID, int assignmentID, String teamName) {
Document team = submissionsCollection.find(and(
eq("course_id", courseID),
@@ -285,7 +360,6 @@ public Document getTeamGrades(String courseID, int assignmentID, String teamName
}
return new Document().append("teams", teams);
}
-
public Document professorUpdate(String courseID, int assignmentID, String teamName, int grade) {
Document team = submissionsCollection.findOneAndUpdate(and(
eq("course_id", courseID),
@@ -298,6 +372,98 @@ public Document professorUpdate(String courseID, int assignmentID, String teamNa
return team;
}
+ //this is a method to test for creating a new makeFinalGrade method functionality, currently in testing
+ public void redoneMakeFinalGrade(String courseID, int assignmentID, String teamName) {
+ //need to know what the grade was 'out of' for rounding/final grade purposes
+ Document assignment = assignmentCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ if (assignment == null)
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Assignment not found.").build());
+ int points = assignment.getInteger("points");
+
+ //get iterable of teams that have graded this team
+ List results = new ArrayList<>();
+
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("reviewed_team", teamName),
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission")
+ ));
+
+ iterable.into(results);
+ System.out.println(results);
+
+ if (iterable == null)
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Assigned teams not found for: " + teamName + "for assignment: " + assignmentID).build());
+
+ int count_of_reviews_submitted = results.size();
+ int total_points = 0;
+ //for each grade this team received
+ for (Document teamGradesReceived : results) {
+ total_points += teamGradesReceived.get("grade", Integer.class);
+ }
+
+ DecimalFormat tenth = new DecimalFormat("0.##");
+ double final_grade = Double.parseDouble(tenth.format((((double) total_points / count_of_reviews_submitted) / points) * 100));//round
+
+
+ Document team_submission = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("team_name", teamName),
+ eq("type", "team_submission"))).first();
+ submissionsCollection.findOneAndUpdate(team_submission, set("grade", final_grade));
+
+ //set the peer review grades for the student.
+ List teams_that_graded = team_submission.getList("reviews", String.class);
+ for (String review : teams_that_graded) {
+ Document team_review = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("reviewed_by", review),
+ eq("reviewed_team", teamName),
+ eq("type", "peer_review_submission"))).first();
+ if (team_review == null) {
+ count_of_reviews_submitted--;
+ } else {
+ if (team_review.get("grade", Integer.class) == null) {
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("team: " + review + "'s review has no points.").build());
+ } else {
+ total_points += team_review.get("grade", Integer.class);
+ for (String teamMember : team_review.getList("reviewed_team_members", String.class)) {
+ Document newPeerReview = new Document()
+ .append("course_id", courseID)
+ .append("grade", team_review.getInteger("grade"))
+ .append("team_name", teamName);
+ List peerReviews = studentCollection.find(eq("student_id", teamMember)).first().getList("peer_reviews", Document.class);
+ peerReviews.add(newPeerReview);
+ Bson studentQuery = eq("student_id", teamMember);
+ Bson update = Updates.set("team_submissions", peerReviews);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ studentCollection.updateOne(studentQuery, update, options);
+ }
+ }
+ }
+ }
+
+ //set the final grade
+ for (String member : team_submission.getList("members", String.class)) {
+ List grades = new ArrayList();
+ grades.addAll(studentCollection.find(eq("student_id", member)).first().getList("team_submissions", Document.class));
+ Document newAssignmentGrade = new Document()
+ .append("assignment_id", assignmentID)
+ .append("grade", final_grade)
+ .append("team_name", team_submission.getString("team_name"));
+ grades.add(newAssignmentGrade);
+ Bson filter = eq("student_id", member);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ Bson update = Updates.set("team_submissions", grades);
+ studentCollection.updateOne(filter, update, options);
+ }
+ }
+
+
+ @Deprecated
public void makeFinalGrade(String courseID, int assignmentID, String teamName) {
Document assignment = assignmentCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
if (assignment == null)
@@ -310,15 +476,27 @@ public void makeFinalGrade(String courseID, int assignmentID, String teamName) {
eq("type", "team_submission"))).first();
List teams_that_graded = team_submission.getList("reviews", String.class);
+
+
if (teams_that_graded == null)
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Assigned teams not found for: " + teamName + "for assignment: " + assignmentID).build());
int total_points = 0;
int count_of_reviews_submitted = teams_that_graded.size();
+
+ //my code
+ String[] temp = new String[count_of_reviews_submitted];
+ int counter = 0;
+ for (String teamsThatGraded : teams_that_graded) {
+ temp[counter] = teamsThatGraded;
+ counter++;
+ }
+ int currentTeam = 0;
for (String review : teams_that_graded) {
Document team_review = submissionsCollection.find(and(
eq("course_id", courseID),
eq("assignment_id", assignmentID),
eq("reviewed_by", review),
+ eq("reviewed_team", teamName),
eq("type", "peer_review_submission"))).first();
if (team_review == null) {
count_of_reviews_submitted--;
@@ -327,12 +505,39 @@ public void makeFinalGrade(String courseID, int assignmentID, String teamName) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("team: " + review + "'s review has no points.").build());
} else {
total_points += team_review.get("grade", Integer.class);
+ for (String teamMember : team_review.getList("reviewed_team", String.class)) {
+ Document newPeerReview = new Document()
+ .append("course_id", courseID)
+ .append("grade", team_review.getInteger("grade"))
+ .append("team_name", teamName);
+ List peerReviews = studentCollection.find(eq("student_id", teamMember)).first().getList("peer_reviews", Document.class);
+ peerReviews.add(newPeerReview);
+ Bson studentQuery = eq("student_id", teamMember);
+ Bson update = Updates.set("team_submissions", peerReviews);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ studentCollection.updateOne(studentQuery, update, options);
+ }
}
}
+ currentTeam++;
}
- double final_grade = (((double) total_points / count_of_reviews_submitted) / points) * 100;
- final_grade = ((int) (final_grade * 100) / 100.0); //round to the nearest 10th
+ DecimalFormat tenth = new DecimalFormat("0.##");
+ double final_grade = Double.parseDouble(tenth.format((((double) total_points / count_of_reviews_submitted) / points) * 100));//round
+
submissionsCollection.findOneAndUpdate(team_submission, set("grade", final_grade));
+ for (String member : team_submission.getList("members", String.class)) {
+ List grades = new ArrayList();
+ grades.addAll(studentCollection.find(eq("student_id", member)).first().getList("team_submissions", Document.class));
+ Document newAssignmentGrade = new Document()
+ .append("assignment_id", assignmentID)
+ .append("grade", final_grade)
+ .append("team_name", team_submission.getString("team_name"));
+ grades.add(newAssignmentGrade);
+ Bson filter = eq("student_id", member);
+ UpdateOptions options = new UpdateOptions().upsert(true);
+ Bson update = Updates.set("team_submissions", grades);
+ studentCollection.updateOne(filter, update, options);
+ }
}
public void makeFinalGrades(String courseID, int assignmentID) {
@@ -367,6 +572,7 @@ public void makeFinalGrades(String courseID, int assignmentID) {
eq("course_id", courseID),
eq("assignment_id", assignmentID),
eq("reviewed_by", review),
+ //eq("reviewed_team", teamName),
eq("type", "peer_review_submission"))).first();
if (team_review == null) {
count_of_reviews_submitted--;
@@ -375,11 +581,13 @@ public void makeFinalGrades(String courseID, int assignmentID) {
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("team: " + review + "'s review has no points.").build());
} else {
total_points += team_review.get("grade", Integer.class);
+
}
}
}
- double final_grade = (((double) total_points / count_of_reviews_submitted) / points) * 100;
- final_grade = ((int) (final_grade * 100) / 100.0); //round to the nearest 10th
+ DecimalFormat tenth = new DecimalFormat("0.##");
+ double final_grade = Double.parseDouble(tenth.format((((double) total_points / count_of_reviews_submitted) / points) * 100));//round
+
submissionsCollection.findOneAndUpdate(team_submission, set("grade", final_grade));
assignmentCollection.findOneAndUpdate(and(eq("course_id", courseID), eq("assignment_id", assignmentID)), set("grade_finalized", true));
}
@@ -397,4 +605,1054 @@ public Document getGradeForTeam(String courseID, int assignmentID, String teamNa
if (result.getInteger("grade") == null) return new Document("grade", -1);
else return new Document("grade", result.getInteger("grade"));
}
-}
\ No newline at end of file
+ /**
+ * The method gets the team names and their members from teamCollection and gets the final grade from submissionsCollection,
+ * then it returns a document object containg individual student and their grade.
+ */
+ public Document getGradeForStudent(String courseID, int assignmentID, String teamID, String studentID) {
+ Document reviewedTeam = teamCollection.find(eq("reviewed_team", teamID)).first();
+ //System.out.println(reviewedTeam);
+ if (reviewedTeam == null)
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Reviewed Team not found.").build());
+ List teamMembers = reviewedTeam.getList("reviewed_members", String.class);
+ //System.out.println(teamMembers);
+ if (teamMembers == null)
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("No students found.").build());
+ if (!teamMembers.contains(studentID))
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Student not found.").build());
+ Document result = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("team_name", teamID),
+ eq("type", "team_submission")
+ )).first();
+ if (result == null) {
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("No submission found.").build());
+ } else {
+ Document gradeDoc = new Document()
+ .append("studentID", studentID)
+ .append("grade", result.getDouble("grade"));
+ //
+ // System.out.println(gradeDoc);
+ return gradeDoc;
+ }
+ }
+
+
+ //redone matrix outlier detection
+ public Document getMatrixOfOutlierAndGrades(String courseID, int assignmentID) {
+
+ //get all teams that were assigned this assignment in the course(will be the 'iterable')
+ Document assignment = assignmentCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ if (assignment == null)
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Assignment not found.").build());
+ //grab all teams assigned this
+ List allTeams = assignment.getList("all_teams", String.class);
+
+
+ Document matrixHolder = new Document();
+ //for every team assigned this assignment
+ for (String individialTeam : allTeams) {
+
+ //for each team, we will query the DB for a list of documents where they have received a review
+ //get iterable of teams that have graded this team
+ List teamsThatReviewedThisTeam = new ArrayList<>();
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("reviewed_team", individialTeam),
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission")
+ ));
+ //put iterable contents into array list
+ iterable.into(teamsThatReviewedThisTeam);
+
+ //we then iterate through this document, grabbing the grade and then determining if that grade
+ //is an outlier, if it is append a true flag to that grade, else append false
+
+ //document to hold the team that graded and the grade
+ Document gradeHolder = new Document();
+ int totalGrade = 0;
+ double averageGradeReceived;
+ int numTeamsReviewed = 0; //= teamsThatReviewedThisTeam.size();
+ for (Document respectiveGradesReceived : teamsThatReviewedThisTeam) {
+ Document gradeToOutlier = new Document();
+ //grab the grade received respectively
+ int gradeReceived = respectiveGradesReceived.get("grade", Integer.class);
+ totalGrade += gradeReceived;
+ numTeamsReviewed++;
+
+ //then find every team that graded them(should be a base function call already made inside this file)
+ Document matrixOfGrades = new Document();
+
+ String teamThatGraded = respectiveGradesReceived.get("reviewed_by", String.class);
+ //if the value is an outlier, mark true, else false
+ if (isOutlier(courseID, assignmentID, gradeReceived)) {
+ gradeToOutlier.append(String.valueOf(gradeReceived), true);
+ } else {
+ gradeToOutlier.append(String.valueOf(gradeReceived), false);
+ }
+
+ gradeHolder.append(teamThatGraded, gradeToOutlier);
+ }
+
+ if (numTeamsReviewed == teamsThatReviewedThisTeam.size()) {
+ double average = (double) totalGrade / (double) numTeamsReviewed;
+ gradeHolder.append("Average Grade Received", new Document(String.valueOf(average), isOutlier(courseID, assignmentID, average)));
+ }
+
+
+ matrixHolder.append(individialTeam, gradeHolder);
+
+ }
+
+
+ //now that we have the grades and average grades received, we can easily qeury to find the grades
+ //given using the mistaken query for iterating over the reviews array
+
+ List getEachAssignment = new ArrayList<>();
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "team_submission")
+ ));
+ //put iterable contents into array list
+ iterable.into(getEachAssignment);
+
+ //document to hold grades given
+ int numTeamsReviewed = 0;
+ int sumOfTeamReviewGradesGiven = 0;
+ Document gradesGivenHolder = new Document();
+ //List allTeams = assignment.getList("all_teams", String.class);
+ for (Document individialTeam : getEachAssignment) {
+
+ //List reviews = individialTeam.getList("reviews", String.class);
+ List individualReviews = new ArrayList<>();
+ FindIterable getReviews = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission"),
+ eq("reviewed_by", individialTeam.get("team_name"))
+ ));
+ //put iterable contents into array list
+ getReviews.into(individualReviews);
+
+ int totalGradesGiven = 0;
+ int counter = 0;
+ Document indGradesHolder = new Document();
+ for (Document review : individualReviews) {
+ totalGradesGiven += review.get("grade", Integer.class);
+ counter++;
+ }
+ double average = (double) totalGradesGiven / (double) counter;
+ if (isOutlier(courseID, assignmentID, average))
+ indGradesHolder.append(String.valueOf(average), true);
+ else
+ indGradesHolder.append(String.valueOf(average), false);
+
+ String teamName = (String) individialTeam.get("team_name");
+ //append to overall grades holder
+ gradesGivenHolder.append(teamName, indGradesHolder);
+
+ }
+
+ matrixHolder.append("Average Grades Given", gradesGivenHolder);
+ // matrixHolder.append("Average Grade Given", gradeHolder);
+
+ //create document to then append to the matrix doc(for grades given averages)
+// for (String key : temp.keySet()) {
+// //first calculate average
+// double average = (double) teamsToGradesGiven.get(assignmentNumber).get(key) / (double) teamsToCountOfReviews.get(assignmentNumber).get(key);
+// }
+ return matrixHolder;
+ }
+
+
+ //redone outlier detection over time
+ public Document getAllPotentialOutliersAndGrades(String courseID) {
+
+ //grab all of the assignments currently finished for the course
+
+ //must increment at end of each loop
+ List allOutliers = new ArrayList<>();
+ FindIterable iter = assignmentCollection.find();
+ iter.into(allOutliers);
+
+ Document allPotentialOutliers = new Document();
+ for (Document eachAssignment : allOutliers) {
+ Document eachMatrixHolder = new Document();
+
+ int assignmentID = eachAssignment.get("assignment_id", Integer.class);
+
+ //get all teams that were assigned this assignment in the course(will be the 'iterable')
+ Document assignment = assignmentCollection.find(and(eq("course_id", courseID), eq("assignment_id", assignmentID))).first();
+ if (assignment == null)
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Assignment not found.").build());
+ //grab all teams assigned this
+ List allTeams = assignment.getList("all_teams", String.class);
+
+
+ Document matrixHolder = new Document();
+ //for every team assigned this assignment
+ for (String individialTeam : allTeams) {
+
+ //for each team, we will query the DB for a list of documents where they have received a review
+ //get iterable of teams that have graded this team
+ List teamsThatReviewedThisTeam = new ArrayList<>();
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("reviewed_team", individialTeam),
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission")
+ ));
+ //put iterable contents into array list
+ iterable.into(teamsThatReviewedThisTeam);
+
+ //we then iterate through this document, grabbing the grade and then determining if that grade
+ //is an outlier, if it is append a true flag to that grade, else append false
+
+ //document to hold the team that graded and the grade
+ Document gradeHolder = new Document();
+ int totalGrade = 0;
+ double averageGradeReceived;
+ int numTeamsReviewed = 0; //= teamsThatReviewedThisTeam.size();
+ for (Document respectiveGradesReceived : teamsThatReviewedThisTeam) {
+ Document gradeToOutlier = new Document();
+ //grab the grade received respectively
+ int gradeReceived = respectiveGradesReceived.get("grade", Integer.class);
+ totalGrade += gradeReceived;
+ numTeamsReviewed++;
+
+
+ String teamThatGraded = respectiveGradesReceived.get("reviewed_by", String.class);
+ //if the value is an outlier, mark true, else false
+ if (isOutlier(courseID, gradeReceived)) {
+ gradeToOutlier.append(String.valueOf(gradeReceived), true);
+ } else {
+ gradeToOutlier.append(String.valueOf(gradeReceived), false);
+ }
+
+ gradeHolder.append(teamThatGraded, gradeToOutlier);
+ }
+
+ if (numTeamsReviewed == teamsThatReviewedThisTeam.size()) {
+ double average = (double) totalGrade / (double) numTeamsReviewed;
+ gradeHolder.append("Average Grade Received", new Document(String.valueOf(average), isOutlier(courseID, average)));
+ }
+
+
+ matrixHolder.append(individialTeam, gradeHolder);
+
+ }
+
+
+ //now that we have the grades and average grades received, we can easily qeury to find the grades
+ //given using the mistaken query for iterating over the reviews array
+
+ List getEachAssignment = new ArrayList<>();
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "team_submission")
+ ));
+ //put iterable contents into array list
+ iterable.into(getEachAssignment);
+
+ //document to hold grades given
+ int numTeamsReviewed = 0;
+ int sumOfTeamReviewGradesGiven = 0;
+ Document gradesGivenHolder = new Document();
+ //List allTeams = assignment.getList("all_teams", String.class);
+ for (Document individialTeam : getEachAssignment) {
+
+ //List reviews = individialTeam.getList("reviews", String.class);
+ List individualReviews = new ArrayList<>();
+ FindIterable getReviews = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission"),
+ eq("reviewed_by", individialTeam.get("team_name"))
+ ));
+ //put iterable contents into array list
+ getReviews.into(individualReviews);
+
+ int totalGradesGiven = 0;
+ int counter = 0;
+ Document indGradesHolder = new Document();
+ for (Document review : individualReviews) {
+ totalGradesGiven += review.get("grade", Integer.class);
+ counter++;
+ }
+ double average = (double) totalGradesGiven / (double) counter;
+ if (isOutlier(courseID, average))
+ indGradesHolder.append(String.valueOf(average), true);
+ else
+ indGradesHolder.append(String.valueOf(average), false);
+
+ String teamName = (String) individialTeam.get("team_name");
+ //append to overall grades holder
+ gradesGivenHolder.append(teamName, indGradesHolder);
+
+ }
+ // matrixOfGrades.append(teamForThisAssignment, gradesToOutliers);
+
+ }
+
+ // matrixHolder.append("Average Grades Given", gradesGivenHolder);
+
+ //to get the grade document we need to go one step further
+ // Document gradesAndBoolean = (Document) valuesOfEachKey.get(subKeySet);
+
+ // allPotentialOutliers.append(String.valueOf(assignmentID), matrixHolder);
+
+ return allPotentialOutliers;
+ }
+
+ // matrixOfGrades.append("Average Grade Given", gradesHolder);
+
+ // return matrixOfGrades;
+ //}
+
+ /**
+ * abstraction method that calls calculate IQR, and uses the values calculated from there
+ * to return a boolean value of whether a number is an outlier or not, based on the current
+ * grades received for this assignment(this function takes an int to compare)
+ */
+ public boolean isOutlier(String courseID, double numberToCompare) {
+ HashMap calculatedQuantities = new HashMap();
+ calculatedQuantities = calculateIQR(courseID);
+ int Q1 = calculatedQuantities.get("Q1");
+ int Q3 = calculatedQuantities.get("Q3");
+ int IQR = calculatedQuantities.get("IQR");
+
+ //if value is an outlier
+ if ((numberToCompare < (Q1 - (1.5 * IQR))) || (numberToCompare > (Q3 + (1.5 * IQR)))) {
+ return true;
+ }
+ //if its not an outlier
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * abstraction method that calls calculate IQR, and uses the values calculated from there
+ * to return a boolean value of whether a number is an outlier or not, based on the current
+ * grades received for this assignment(this function takes an int to compare)
+ */
+ public boolean isOutlier(String courseID, int numberToCompare) {
+ HashMap calculatedQuantities = new HashMap();
+ calculatedQuantities = calculateIQR(courseID);
+ int Q1 = calculatedQuantities.get("Q1");
+ int Q3 = calculatedQuantities.get("Q3");
+ int IQR = calculatedQuantities.get("IQR");
+
+ //if value is an outlier
+ if ((numberToCompare < (Q1 - (1.5 * IQR))) || (numberToCompare > (Q3 + (1.5 * IQR)))) {
+ return true;
+ }
+ //if its not an outlier
+ else {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * abstraction method that calls calculate IQR, and uses the values calculated from there
+ * to return a boolean value of whether a number is an outlier or not, based on the current
+ * grades received for this assignment(this function takes a double to compare)
+ */
+ public boolean isOutlier(String courseID, int assignmentID, double numberToCompare) {
+ HashMap calculatedQuantities = new HashMap();
+ calculatedQuantities = calculateIQR(courseID, assignmentID);
+ int Q1 = calculatedQuantities.get("Q1");
+ int Q3 = calculatedQuantities.get("Q3");
+ int IQR = calculatedQuantities.get("IQR");
+
+ //if value is an outlier
+ if ((numberToCompare < (Q1 - (1.5 * IQR))) || (numberToCompare > (Q3 + (1.5 * IQR)))) {
+ return true;
+ }
+ //if its not an outlier
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * abstraction method that calls calculate IQR, and uses the values calculated from there
+ * to return a boolean value of whether a number is an outlier or not, based on the current
+ * grades received for this assignment(this function takes an int to compare)
+ */
+ public boolean isOutlier(String courseID, int assignmentID, int numberToCompare) {
+ HashMap calculatedQuantities = new HashMap();
+ calculatedQuantities = calculateIQR(courseID, assignmentID);
+ int Q1 = calculatedQuantities.get("Q1");
+ int Q3 = calculatedQuantities.get("Q3");
+ int IQR = calculatedQuantities.get("IQR");
+
+ //if value is an outlier
+ if ((numberToCompare < (Q1 - (1.5 * IQR))) || (numberToCompare > (Q3 + (1.5 * IQR)))) {
+ return true;
+ }
+ //if its not an outlier
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * This function is used to calculate the IQR, returning a hashmap of values
+ * that consist of the q1, q3, and IQR values to allow for computation and
+ * outlier detection.
+ * */
+ /**
+ * This function is used to calculate the IQR, returning a hashmap of values
+ * that consist of the q1, q3, and IQR values to allow for computation and
+ * outlier detection.
+ */
+ public HashMap calculateIQR(String courseID, int assignmentID) {
+
+
+ //for every team in the course, grab the points, and add them to an integer array
+ List gradesForAssignment = new ArrayList();
+
+
+ //grab each grade that has been given for this respectove assignment
+ List getEachAssignment = new ArrayList<>();
+ FindIterable iterable = submissionsCollection.find(and(
+ eq("course_id", courseID),
+ eq("assignment_id", assignmentID),
+ eq("type", "peer_review_submission")
+ ));
+ //put iterable contents into array list
+ iterable.into(getEachAssignment);
+
+ //for every grade that has been given, add it to the list
+ for (Document grade : getEachAssignment) {
+
+ if (grade.get("grade", Integer.class) == null) {
+ throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity("Error retreiving grade for team").build());
+ } else {
+
+ int respectiveGrade = grade.get("grade", Integer.class);
+ gradesForAssignment.add(respectiveGrade);
+ }
+
+ }
+
+ int IQR = 0;
+ //after all of the team grades are obtained,
+
+ //sort the list
+ Collections.sort(gradesForAssignment);
+
+ HashMap> subsets = getSubsetOfArray(gradesForAssignment);
+
+ //get subsets from hashmap
+ List q1Subset = subsets.get(1);
+ List q3Subset = subsets.get(2);
+
+ //get medians from subsets
+ int q1Median = findMedian(q1Subset);
+ int q3Median = findMedian(q3Subset);
+
+ //get IQR from subtracting both values
+ IQR = q3Median - q1Median;
+ HashMap returnValues = new HashMap();
+ returnValues.put("Q1", q1Median);
+ returnValues.put("Q3", q3Median);
+ returnValues.put("IQR", IQR);
+
+ return returnValues;
+ }
+
+ /**
+ * This fucntion returns a subset of the array to then be able to calculate the
+ * median for each 'Q'
+ */
+ public HashMap> getSubsetOfArray(List input) {
+ List firstSubSet = new ArrayList();
+ List secondSubSet = new ArrayList();
+
+ HashMap> subsetOfArrays = new HashMap<>();
+ //if true this is odd
+ if ((input.size() & 1) == 1) {
+ firstSubSet = input.subList(0, input.size() / 2);
+ secondSubSet = input.subList(input.size() / 2 + 1, input.size());
+ } else {
+ firstSubSet = input.subList(0, input.size() / 2);
+ secondSubSet = input.subList(input.size() / 2, input.size());
+ }
+ subsetOfArrays.put(1, firstSubSet);
+ subsetOfArrays.put(2, secondSubSet);
+
+ return subsetOfArrays;
+
+ }
+
+ /**
+ * This function returns the median of any dataset that is larger than 2, also
+ * it assumes that the data is already sorted when passed in
+ */
+ public int findMedian(List dataSet) {
+ //this fucntion won't accept an array of length less than 2,
+ if (dataSet.size() < 2 || dataSet == null)
+ return -1;
+ //& 1 is a bitwise operator that is much faster than modulo and determines whether a number is odd or even
+ if ((dataSet.size() & 1) == 1)
+ //use int division return median
+ return dataSet.get(dataSet.size() / 2);
+ else
+ //must use formula (((dataSet.length/2) + (dataSet.length/2 -1)) / 2) to obtain the proper index of even length dataset )
+ return (dataSet.get(dataSet.size() / 2) + dataSet.get(dataSet.size() / 2 - 1)) / 2;
+
+ }
+
+
+ /**
+ * This method will be used to grab all of the averages/assignments given over for a given course
+ */
+ public HashMap calculateIQR(String courseID) {
+
+ //must increment at end of each loop
+ List