diff --git a/pom.xml b/pom.xml
index 086d14c..9da5ccd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,24 @@
mysql-connector-java8.0.15
+
+
+ org.apache.commons
+ commons-csv
+ 1.5
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.6
+
+
+
+ org.kohsuke
+ github-api
+ 1.95
+
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/TeachingAssistantAssistantApplication.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/TeachingAssistantAssistantApplication.java
index 984e6f9..c5adf7d 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/TeachingAssistantAssistantApplication.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/TeachingAssistantAssistantApplication.java
@@ -27,6 +27,5 @@ public class TeachingAssistantAssistantApplication implements CommandLineRunner
@Override
public void run(String... args) throws Exception {
System.out.println("Running startup...");
-
}
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/config/DataSourceConfig.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/config/DataSourceConfig.java
deleted file mode 100644
index f6b0a94..0000000
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/config/DataSourceConfig.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package nl.andrewlalis.teaching_assistant_assistant.config;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.jdbc.DataSourceBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-
-import javax.sql.DataSource;
-
-/**
- * Configures the data source for this application.
- */
-@Configuration
-public class DataSourceConfig {
-
- private static final String USERNAME = "root";
- private static final String PASSWORD = "root";
- private static final String DB_HOST = "localhost";
- private static final String DB_NAME = "teaching_assistant_assistant";
-
- @ConfigurationProperties(prefix = "spring.datasource")
- @Bean
- @Primary
- public DataSource getDataSource() {
- return DataSourceBuilder
- .create()
- .url("jdbc:h2:~/" + DB_NAME)
- .username(USERNAME)
- .password(PASSWORD)
- .build();
- }
-
-}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Courses.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Courses.java
index 6935735..ea5df0e 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Courses.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Courses.java
@@ -41,7 +41,6 @@ public class Courses {
consumes = "application/x-www-form-urlencoded"
)
public String post(@ModelAttribute Course course) {
- System.out.println("Object submitted: " + course);
this.courseRepository.save(course);
return "courses/entity";
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Students.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Students.java
new file mode 100644
index 0000000..4086ce3
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/Students.java
@@ -0,0 +1,22 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@Controller
+public class Students {
+
+ private StudentRepository studentRepository;
+
+ protected Students(StudentRepository studentRepository) {
+ this.studentRepository = studentRepository;
+ }
+
+ @GetMapping("/students")
+ public String get(Model model) {
+ model.addAttribute("students", this.studentRepository.findAll());
+ return "students";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/TeachingAssistants.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/TeachingAssistants.java
new file mode 100644
index 0000000..a863b10
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/TeachingAssistants.java
@@ -0,0 +1,53 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import java.util.Optional;
+
+@Controller
+public class TeachingAssistants {
+
+ private TeachingAssistantRepository teachingAssistantRepository;
+ private CourseRepository courseRepository;
+
+ protected TeachingAssistants(TeachingAssistantRepository teachingAssistantRepository, CourseRepository courseRepository) {
+ this.teachingAssistantRepository = teachingAssistantRepository;
+ this.courseRepository = courseRepository;
+ }
+
+ @GetMapping("/teaching_assistants")
+ public String get(Model model) {
+ model.addAttribute("teaching_assistants", teachingAssistantRepository.findAll());
+ return "teaching_assistants";
+ }
+
+ @GetMapping("/courses/{code}/teaching_assistants/create")
+ public String getCreate(@PathVariable String code, Model model) {
+ model.addAttribute("teachingAssistant", new TeachingAssistant("First Name", "Last Name", "github Username", "me@example.com"));
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/teaching_assistants/create";
+ }
+
+ @PostMapping(
+ value = "/courses/{code}/teaching_assistants",
+ consumes = "application/x-www-form-urlencoded"
+ )
+ public String post(@PathVariable String code, @ModelAttribute("teachingAssistant") TeachingAssistant teachingAssistant) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> {
+ course.addParticipant(teachingAssistant);
+ this.courseRepository.save(course);
+ });
+ return "redirect:/teaching_assistants";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/CourseEntity.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/CourseEntity.java
new file mode 100644
index 0000000..a052060
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/CourseEntity.java
@@ -0,0 +1,89 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Optional;
+
+/**
+ * Controller for the course entity, that is, one individual course.
+ */
+@Controller
+public class CourseEntity {
+
+ private CourseRepository courseRepository;
+
+ protected CourseEntity(CourseRepository courseRepository) {
+ this.courseRepository = courseRepository;
+ }
+
+ /**
+ * Handles get requests to a course with a given code.
+ * @param code The unique course code used to identify a course entity.
+ * @param model The view model that will be populated with data.
+ * @return The template which will be used in conjunction with the model to build a view.
+ */
+ @GetMapping("/courses/{code}")
+ public String get(@PathVariable String code, Model model) {
+ Optional courseOptional = this.courseRepository.findByCode(code);
+ courseOptional.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity";
+ }
+
+ /**
+ * Gets the student teams for a particular course.
+ * @param code The course code.
+ * @param model The view model.
+ * @return The template for viewing the list of student teams.
+ */
+ @GetMapping("/courses/{code}/student_teams")
+ public String getStudentTeams(@PathVariable String code, Model model) {
+ Optional courseOptional = this.courseRepository.findByCode(code);
+ courseOptional.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/student_teams";
+ }
+
+ /**
+ * Gets the teaching assistant teams for a particular course.
+ * @param code The course code.
+ * @param model The view model.
+ * @return The template for viewing the list of student teams.
+ */
+ @GetMapping("/courses/{code}/teaching_assistant_teams")
+ public String getTeachingAssistantTeams(@PathVariable String code, Model model) {
+ Optional courseOptional = this.courseRepository.findByCode(code);
+ courseOptional.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/teaching_assistant_teams";
+ }
+
+ /**
+ * Gets the students for a particular course.
+ * @param code The course code.
+ * @param model The view model.
+ * @return The template for viewing the list of students.
+ */
+ @GetMapping("/courses/{code}/students")
+ public String getStudents(@PathVariable String code, Model model) {
+ Optional courseOptional = this.courseRepository.findByCode(code);
+ courseOptional.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/students";
+ }
+
+ /**
+ * Gets the teaching assistants for a particular course.
+ * @param code The course code.
+ * @param model The view model.
+ * @return The template for viewing the list of teaching assistants.
+ */
+ @GetMapping("/courses/{code}/teaching_assistants")
+ public String getTeachingAssistants(@PathVariable String code, Model model) {
+ Optional courseOptional = this.courseRepository.findByCode(code);
+ courseOptional.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/teaching_assistants";
+ }
+
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Create.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/CreateCourse.java
similarity index 88%
rename from src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Create.java
rename to src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/CreateCourse.java
index 08376e2..7d8cd0d 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Create.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/CreateCourse.java
@@ -7,11 +7,11 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
-public class Create {
+public class CreateCourse {
private CourseRepository courseRepository;
- protected Create(CourseRepository courseRepository) {
+ protected CreateCourse(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Entity.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Entity.java
deleted file mode 100644
index 8bc8d33..0000000
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Entity.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package nl.andrewlalis.teaching_assistant_assistant.controllers.courses;
-
-import nl.andrewlalis.teaching_assistant_assistant.model.Course;
-import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-
-import java.util.Optional;
-
-/**
- * Controller for the course entity, that is, one individual course.
- */
-@Controller
-public class Entity {
-
- private CourseRepository courseRepository;
-
- protected Entity(CourseRepository courseRepository) {
- this.courseRepository = courseRepository;
- }
-
- /**
- * Handles get requests to a course with a given code.
- * @param code The unique course code used to identify a course entity.
- * @param model The view model that will be populated with data.
- * @return The template which will be used in conjunction with the model to build a view.
- */
- @GetMapping("/courses/{code}")
- public String get(@PathVariable String code, Model model) {
- Optional courseOptional = this.courseRepository.findByCode(code);
-
- if (courseOptional.isPresent()) {
- Course course = courseOptional.get();
- model.addAttribute("course", course);
- }
-
- return "courses/entity";
- }
-
-}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Generate.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Generate.java
index c7f8ae5..2d5227b 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Generate.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/Generate.java
@@ -26,7 +26,7 @@ public class Generate {
this.courseRepository.saveAll(courses);
model.addAttribute("courses", courseRepository.findAll());
- return "courses";
+ return "redirect:/courses";
}
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/ImportStudents.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/ImportStudents.java
new file mode 100644
index 0000000..516efcc
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/ImportStudents.java
@@ -0,0 +1,96 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentRepository;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentTeamRepository;
+import nl.andrewlalis.teaching_assistant_assistant.util.team_importing.StudentTeamImporter;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Controller for importing students from a CSV sheet.
+ */
+@Controller
+public class ImportStudents {
+
+ private CourseRepository courseRepository;
+
+ private StudentTeamRepository studentTeamRepository;
+
+ private StudentRepository studentRepository;
+
+ protected ImportStudents(CourseRepository courseRepository, StudentTeamRepository studentTeamRepository, StudentRepository studentRepository) {
+ this.courseRepository = courseRepository;
+ this.studentTeamRepository = studentTeamRepository;
+ this.studentRepository = studentRepository;
+ }
+
+ @GetMapping("/courses/{code}/import_students")
+ public String get(@PathVariable String code, Model model) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/import_students";
+ }
+
+ /**
+ * Performs the actual importing when this controller receives a post request.
+ * @param code The course code.
+ * @param file The file which the user has uploaded.
+ * @return Redirect to the course which will be shown.
+ */
+ @PostMapping(
+ value = "/courses/{code}/import_students"
+ )
+ public String post(@PathVariable String code, @RequestParam MultipartFile file) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+
+ if (!optionalCourse.isPresent()) {
+ System.out.println("No course found.");
+ return "redirect:/courses";
+ }
+
+ Course course = optionalCourse.get();
+
+ try {
+ List studentTeams = StudentTeamImporter.importFromCSV(file.getInputStream(), optionalCourse.get());
+
+ // Save all the new students first, then save all the teams they belong to.
+ for (StudentTeam team : studentTeams) {
+ team.getMembers().forEach(student -> student.assignToCourse(course));
+ this.studentRepository.saveAll(team.getStudents());
+ team.setCourse(course);
+ }
+
+ studentTeams.forEach(team -> {
+ team.getMembers().forEach(student -> {
+ student.assignToCourse(course);
+ course.addParticipant(student);
+ });
+ team.setCourse(course);
+ course.addStudentTeam(team);
+
+ this.studentRepository.saveAll(team.getStudents());
+ });
+
+ this.studentTeamRepository.saveAll(studentTeams);
+
+ this.courseRepository.save(course);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return "redirect:/courses/{code}";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/ExportStudentTeams.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/ExportStudentTeams.java
new file mode 100644
index 0000000..7375204
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/ExportStudentTeams.java
@@ -0,0 +1,91 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Controller for exporting team information into readable files.
+ */
+@Controller
+public class ExportStudentTeams {
+
+ private CourseRepository courseRepository;
+
+ protected ExportStudentTeams(CourseRepository courseRepository) {
+ this.courseRepository = courseRepository;
+ }
+
+ @GetMapping("/courses/{code}/student_teams/export")
+ public void export(@PathVariable String code, HttpServletResponse response) throws IOException {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ if (!optionalCourse.isPresent()) {
+ response.sendError(404, "Course with code " + code + " not found");
+ return;
+ }
+
+ Course course = optionalCourse.get();
+ response.setContentType("text/plain");
+ response.setCharacterEncoding("UTF-8");
+
+ response.getOutputStream().write(getStudentTeamsSummary(course));
+
+ response.flushBuffer();
+ }
+
+ @GetMapping("/courses/{code}/student_teams/export_contact_info")
+ public void exportContactInfo(@PathVariable String code, HttpServletResponse response) throws IOException {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ if (!optionalCourse.isPresent()) {
+ response.sendError(404, "Course with code " + code + " not found");
+ return;
+ }
+
+ Course course = optionalCourse.get();
+ response.setContentType("text/plain");
+ response.setCharacterEncoding("UTF-8");
+
+ response.getOutputStream().write(getContactInfo(course));
+
+ response.flushBuffer();
+ }
+
+ private byte[] getStudentTeamsSummary(Course course) {
+ StringBuilder sb = new StringBuilder("Student Teams Export for Course: ");
+ sb.append(course.getName()).append('\n');
+ for (TeachingAssistantTeam teachingAssistantTeam : course.getTeachingAssistantTeams()) {
+ sb.append("Teaching Assistant Team ").append(teachingAssistantTeam.getId()).append(", Github Team Name: ").append(teachingAssistantTeam.getGithubTeamName()).append('\n');
+ List assignedTeams = teachingAssistantTeam.getAssignedStudentTeams();
+ for (StudentTeam studentTeam : assignedTeams) {
+ sb.append("\tStudent Team ").append(studentTeam.getId()).append(": ");
+ for (Student student : studentTeam.getStudents()) {
+ sb.append(student.getFullName()).append(" (S").append(student.getStudentNumber()).append("), ");
+ }
+ sb.append('\n');
+ }
+ }
+ return sb.toString().getBytes();
+ }
+
+ private byte[] getContactInfo(Course course) {
+ StringBuilder sb = new StringBuilder("Student Team Contact Details\n");
+ for (StudentTeam team : course.getStudentTeams()) {
+ sb.append("2019_Team_").append(team.getId()).append(": ");
+ for (Student student : team.getStudents()) {
+ sb.append(student.getFullName()).append(" (").append(student.getEmailAddress()).append("), ");
+ }
+ sb.append("\n");
+ }
+ return sb.toString().getBytes();
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/GenerateRepositories.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/GenerateRepositories.java
new file mode 100644
index 0000000..9d05182
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/GenerateRepositories.java
@@ -0,0 +1,68 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentTeamRepository;
+import nl.andrewlalis.teaching_assistant_assistant.util.github.GithubManager;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import java.io.IOException;
+import java.util.Optional;
+
+@Controller
+public class GenerateRepositories {
+
+ private CourseRepository courseRepository;
+ private StudentTeamRepository studentTeamRepository;
+
+ protected GenerateRepositories(CourseRepository courseRepository, StudentTeamRepository studentTeamRepository) {
+ this.courseRepository = courseRepository;
+ this.studentTeamRepository = studentTeamRepository;
+ }
+
+ @GetMapping("/courses/{courseCode}/student_teams/generate_repositories")
+ public String get(@PathVariable String courseCode, Model model) {
+ Optional optionalCourse = this.courseRepository.findByCode(courseCode);
+ optionalCourse.ifPresent(course -> model.addAttribute("course", course));
+
+ return "courses/entity/student_teams/generate_repositories";
+ }
+
+ @PostMapping(
+ value = "/courses/{courseCode}/student_teams/generate_repositories",
+ consumes = "application/x-www-form-urlencoded"
+ )
+ public String post(@PathVariable String courseCode) {
+ System.out.println("Post received for " + courseCode);
+ Optional optionalCourse = this.courseRepository.findByCode(courseCode);
+ optionalCourse.ifPresent(course -> {
+ GithubManager manager;
+
+ try {
+ manager = new GithubManager(course.getApiKey());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ for (StudentTeam studentTeam : course.getStudentTeams()) {
+ System.out.println("Generating repository for team " + studentTeam.getId());
+ String repositoryName = manager.generateStudentTeamRepository(studentTeam);
+ if (repositoryName == null) {
+ System.err.println("An error occurred while generating a repository for student team " + studentTeam.getId());
+ continue;
+ }
+ studentTeam.setGithubRepositoryName(repositoryName);
+ this.studentTeamRepository.save(studentTeam);
+ System.out.println("Done\n");
+ }
+ });
+
+ return "redirect:/courses/{courseCode}";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/StudentTeamEntity.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/StudentTeamEntity.java
new file mode 100644
index 0000000..aeae752
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/student_teams/StudentTeamEntity.java
@@ -0,0 +1,35 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentTeamRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Optional;
+
+@Controller
+public class StudentTeamEntity {
+
+ private StudentTeamRepository studentTeamRepository;
+
+ protected StudentTeamEntity(StudentTeamRepository studentTeamRepository) {
+ this.studentTeamRepository = studentTeamRepository;
+ }
+
+ /**
+ * Gets data for a specific student team.
+ * @param courseCode The course code for the course in which the team resides.
+ * @param teamId The id of the team.
+ * @param model The view model.
+ * @return The name of the template which will be used to view the student team.
+ */
+ @GetMapping("/courses/{courseCode}/student_teams/{teamId}")
+ public String get(@PathVariable String courseCode, @PathVariable int teamId, Model model) {
+ Optional optionalStudentTeam = this.studentTeamRepository.findByCourseCodeAndId(courseCode, teamId);
+ optionalStudentTeam.ifPresent(team -> model.addAttribute("student_team", team));
+
+ return "courses/entity/student_teams/entity";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/students/InviteAllToRepository.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/students/InviteAllToRepository.java
new file mode 100644
index 0000000..4a791d3
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/students/InviteAllToRepository.java
@@ -0,0 +1,112 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.students;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import nl.andrewlalis.teaching_assistant_assistant.util.github.GithubManager;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@Controller
+public class InviteAllToRepository {
+
+ private CourseRepository courseRepository;
+
+ protected InviteAllToRepository(CourseRepository courseRepository) {
+ this.courseRepository = courseRepository;
+ }
+
+ @GetMapping("/courses/{code}/students/invite_all")
+ public String get(@PathVariable String code, Model model) {
+ Optional optionalCourse =this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> model.addAttribute("course", course));
+
+ return "courses/entity/students/invite_all";
+ }
+
+ @PostMapping("/courses/{code}/students/invite_all")
+ public String post(
+ @PathVariable String code,
+ @RequestParam(value = "repository_name") String repositoryName,
+ @RequestParam(value = "api_keys") String apiKeys,
+ @RequestParam(value = "usernames", required = false) String usernames
+ ) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> {
+
+ // Get a list of all the github usernames to invite.
+ List githubUsernames = new ArrayList<>();
+ if (usernames != null && !usernames.isEmpty()) {
+ String[] usernamesRaw = usernames.split("\n");
+ for (String username : usernamesRaw) {
+ githubUsernames.add(username.trim());
+ }
+ } else {
+ for (Student student : course.getStudents()) {
+ githubUsernames.add(student.getGithubUsername());
+ }
+ }
+
+ // Get a list of all our available keys.
+ String[] rawKeys = apiKeys.split("\n");
+ List keys = new ArrayList<>();
+ for (String rawKey : rawKeys) {
+ keys.add(rawKey.trim());
+ }
+
+ String fullRepositoryName = course.getGithubOrganizationName() + '/' + repositoryName;
+
+ int inviteCounter = 0;
+ GithubManager manager;
+ try {
+ manager = new GithubManager(keys.remove(0));
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ List failedNames = new ArrayList<>();
+
+ while (!githubUsernames.isEmpty()) {
+ if (inviteCounter == 50) {
+ System.out.println("Used up 50 invites on key.");
+ try {
+ manager = new GithubManager(keys.remove(0));
+ inviteCounter = 0;
+ } catch (IOException e) {
+ e.printStackTrace();
+ failedNames.addAll(githubUsernames);
+ return;
+ }
+ }
+
+ String username = githubUsernames.remove(0);
+ try {
+ manager.addCollaborator(fullRepositoryName, username, "pull");
+ inviteCounter++;
+ System.out.println("\tInvited " + username);
+ } catch (IOException e) {
+ //e.printStackTrace();
+ System.err.println("Could not add " + username + " to repository " + fullRepositoryName);
+ failedNames.add(username);
+ }
+ }
+
+ System.err.println("The following github usernames have not been added.");
+ for (String username : failedNames) {
+ System.out.println("\t" + username);
+ }
+ });
+
+ return "redirect:/courses/{code}/students";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/AssignToStudentTeams.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/AssignToStudentTeams.java
new file mode 100644
index 0000000..e1e23f2
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/AssignToStudentTeams.java
@@ -0,0 +1,70 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.*;
+
+@Controller
+public class AssignToStudentTeams {
+
+ private CourseRepository courseRepository;
+
+ protected AssignToStudentTeams(CourseRepository courseRepository) {
+ this.courseRepository = courseRepository;
+ }
+
+ @GetMapping("/courses/{code}/teaching_assistant_teams/assign_to_student_teams")
+ public String get(@PathVariable String code, Model model) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> model.addAttribute("course", course));
+
+ return "courses/entity/teaching_assistant_teams/assign_to_student_teams";
+ }
+
+ /**
+ * Randomly assigns a teaching assistant team to each student team in such a way that all teaching assistant teams
+ * should receive an equal number of student teams.
+ * @param code The code for the course in which to perform this action.
+ * @param seed A seed to use to determine randomness.
+ * @return The view for the list of student teams in this course, to see the results of the action.
+ */
+ @PostMapping("/courses/{code}/teaching_assistant_teams/assign_to_student_teams")
+ public String post(@PathVariable String code, @RequestParam(value = "seed") int seed) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> {
+ List studentTeams = course.getStudentTeams();
+
+ LinkedList studentTeamQueue = new LinkedList<>();
+ for (int i = 0; i < studentTeams.size(); i++) {
+ studentTeamQueue.add(i);
+ }
+ Collections.shuffle(studentTeamQueue, new Random(seed));
+
+ while (!studentTeamQueue.isEmpty()) {
+ for (TeachingAssistantTeam taTeam : course.getTeachingAssistantTeams()) {
+ if (studentTeamQueue.isEmpty()) {
+ break;
+ }
+
+ StudentTeam studentTeam = studentTeams.get(studentTeamQueue.removeFirst());
+ studentTeam.setAssignedTeachingAssistantTeam(taTeam);
+ taTeam.addAssignedStudentTeam(studentTeam);
+ }
+ }
+
+ this.courseRepository.save(course);
+ });
+
+ return "redirect:/courses/{code}/student_teams";
+ }
+
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/CreateTeachingAssistantTeam.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/CreateTeachingAssistantTeam.java
new file mode 100644
index 0000000..fef4843
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/CreateTeachingAssistantTeam.java
@@ -0,0 +1,69 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.Optional;
+
+@Controller
+public class CreateTeachingAssistantTeam {
+
+ private CourseRepository courseRepository;
+ private TeachingAssistantRepository teachingAssistantRepository;
+
+ protected CreateTeachingAssistantTeam(CourseRepository courseRepository, TeachingAssistantRepository teachingAssistantRepository) {
+ this.courseRepository = courseRepository;
+ this.teachingAssistantRepository = teachingAssistantRepository;
+ }
+
+ @GetMapping("/courses/{code}/teaching_assistant_teams/create")
+ public String get(@PathVariable String code, Model model) {
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ optionalCourse.ifPresent(course -> model.addAttribute("course", course));
+ return "courses/entity/teaching_assistant_teams/create";
+ }
+
+ @PostMapping(
+ value = "/courses/{code}/teaching_assistant_teams",
+ consumes = "application/x-www-form-urlencoded"
+ )
+ public String post(
+ @PathVariable String code,
+ @RequestParam(value = "github_team_name") String githubTeamName,
+ @RequestParam(value = "id_1") long id1,
+ Model model
+ ) {
+ TeachingAssistantTeam team = new TeachingAssistantTeam();
+ team.setGithubTeamName(githubTeamName);
+
+ Optional optionalCourse = this.courseRepository.findByCode(code);
+ Optional optionalTeachingAssistant1 = this.teachingAssistantRepository.findById(id1);
+
+ if (optionalCourse.isPresent() && optionalTeachingAssistant1.isPresent()) {
+ Course course = optionalCourse.get();
+ team.setCourse(course);
+
+ team.addMember(optionalTeachingAssistant1.get());
+
+ course.addTeachingAssistantTeam(team);
+ this.courseRepository.save(course);
+
+ model.addAttribute("course", course);
+
+ return "courses/entity/teaching_assistant_teams";
+ } else {
+ System.out.println("Missing data!");
+ }
+
+ return "redirect:/courses/entity";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/TeachingAssistantTeamEntity.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/TeachingAssistantTeamEntity.java
new file mode 100644
index 0000000..5923980
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/courses/entity/teaching_assistant_teams/TeachingAssistantTeamEntity.java
@@ -0,0 +1,55 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantTeamRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Optional;
+
+@Controller
+public class TeachingAssistantTeamEntity {
+
+ private CourseRepository courseRepository;
+ private TeachingAssistantTeamRepository teachingAssistantTeamRepository;
+
+ protected TeachingAssistantTeamEntity(CourseRepository courseRepository, TeachingAssistantTeamRepository teachingAssistantTeamRepository) {
+ this.courseRepository = courseRepository;
+ this.teachingAssistantTeamRepository = teachingAssistantTeamRepository;
+ }
+
+ @GetMapping("/courses/{courseCode}/teaching_assistant_teams/{teamId}")
+ public String get(@PathVariable String courseCode, @PathVariable long teamId, Model model) {
+ Optional optionalCourse = this.courseRepository.findByCode(courseCode);
+ Optional optionalTeachingAssistantTeam = this.teachingAssistantTeamRepository.findById(teamId);
+
+ if (optionalCourse.isPresent() && optionalTeachingAssistantTeam.isPresent()) {
+ model.addAttribute("course", optionalCourse.get());
+ model.addAttribute("teachingAssistantTeam", optionalTeachingAssistantTeam.get());
+ }
+
+ return "courses/entity/teaching_assistant_teams/entity";
+ }
+
+ @GetMapping("/courses/{courseCode}/teaching_assistant_teams/{teamId}/delete")
+ public String delete(@PathVariable String courseCode, @PathVariable long teamId) {
+ Optional optionalCourse = this.courseRepository.findByCode(courseCode);
+ Optional optionalTeachingAssistantTeam = this.teachingAssistantTeamRepository.findById(teamId);
+
+ if (optionalCourse.isPresent() && optionalTeachingAssistantTeam.isPresent()) {
+ Course course = optionalCourse.get();
+ TeachingAssistantTeam team = optionalTeachingAssistantTeam.get();
+ course.removeTeachingAssistantTeam(team);
+
+ this.teachingAssistantTeamRepository.delete(team);
+ this.courseRepository.save(course);
+ }
+
+ return "redirect:/courses/entity/teaching_assistants";
+ }
+
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/students/StudentEntity.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/students/StudentEntity.java
new file mode 100644
index 0000000..db914de
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/students/StudentEntity.java
@@ -0,0 +1,27 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.students;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Optional;
+
+@Controller
+public class StudentEntity {
+
+ private StudentRepository studentRepository;
+
+ protected StudentEntity(StudentRepository studentRepository) {
+ this.studentRepository = studentRepository;
+ }
+
+ @GetMapping("/students/{id}")
+ public String get(@PathVariable long id, Model model) {
+ Optional optionalStudent = this.studentRepository.findById(id);
+ optionalStudent.ifPresent(student -> model.addAttribute("student", student));
+ return "students/entity";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/teaching_assistants/TeachingAssistantEntity.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/teaching_assistants/TeachingAssistantEntity.java
new file mode 100644
index 0000000..99991b3
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/controllers/teaching_assistants/TeachingAssistantEntity.java
@@ -0,0 +1,41 @@
+package nl.andrewlalis.teaching_assistant_assistant.controllers.teaching_assistants;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
+import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Optional;
+
+@Controller
+public class TeachingAssistantEntity {
+
+ private TeachingAssistantRepository teachingAssistantRepository;
+
+ protected TeachingAssistantEntity(TeachingAssistantRepository teachingAssistantRepository) {
+ this.teachingAssistantRepository = teachingAssistantRepository;
+ }
+
+ @GetMapping("/teaching_assistants/{id}")
+ public String get(@PathVariable long id, Model model) {
+ Optional optionalTeachingAssistant = this.teachingAssistantRepository.findById(id);
+ optionalTeachingAssistant.ifPresent(teachingAssistant -> model.addAttribute("teachingAssistant", teachingAssistant));
+ return "teaching_assistants/entity";
+ }
+
+ @GetMapping("/teaching_assistants/{id}/delete")
+ public String delete(@PathVariable long id) {
+ Optional optionalTeachingAssistant = this.teachingAssistantRepository.findById(id);
+ optionalTeachingAssistant.ifPresent(teachingAssistant -> {
+ teachingAssistant.getCourses().forEach(course -> course.removeParticipant(teachingAssistant));
+ teachingAssistant.getTeams().forEach(team -> {
+ team.removeMember(teachingAssistant);
+ });
+ this.teachingAssistantRepository.delete(teachingAssistant);
+ });
+
+ return "redirect:/teaching_assistants";
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/Course.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/Course.java
index 25d41db..8c33111 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/Course.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/Course.java
@@ -1,6 +1,9 @@
package nl.andrewlalis.teaching_assistant_assistant.model;
import nl.andrewlalis.teaching_assistant_assistant.model.assignments.Assignment;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Person;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
@@ -29,6 +32,18 @@ public class Course extends BasicEntity {
@Column(unique = true, nullable = false)
private String code;
+ /**
+ * The github organization name for this course, if any.
+ */
+ @Column
+ private String githubOrganizationName;
+
+ /**
+ * The API key that will be used for Github interaction with this organization.
+ */
+ @Column
+ private String apiKey;
+
/**
* The list of assignments this course contains.
*/
@@ -57,6 +72,19 @@ public class Course extends BasicEntity {
)
private List teachingAssistantTeams;
+ /**
+ * The list of all participants in this course, both teaching assistants and students.
+ */
+ @ManyToMany(
+ cascade = CascadeType.ALL
+ )
+ @JoinTable(
+ name = "course_participants",
+ joinColumns = @JoinColumn(name = "course_id"),
+ inverseJoinColumns = @JoinColumn(name = "person_id")
+ )
+ private List participants;
+
/**
* Default constructor for JPA.
*/
@@ -64,6 +92,7 @@ public class Course extends BasicEntity {
this.assignments = new ArrayList<>();
this.studentTeams = new ArrayList<>();
this.teachingAssistantTeams = new ArrayList<>();
+ this.participants = new ArrayList<>();
}
/**
@@ -77,12 +106,26 @@ public class Course extends BasicEntity {
this.code = code;
}
- public void addStudentGroup(StudentTeam group) {
- this.studentTeams.add(group);
+ public void addStudentTeam(StudentTeam team) {
+ this.studentTeams.add(team);
}
- public void addTeachingAssistantGroup(TeachingAssistantTeam group) {
- this.teachingAssistantTeams.add(group);
+ public void addTeachingAssistantTeam(TeachingAssistantTeam team) {
+ this.teachingAssistantTeams.add(team);
+ }
+
+ public void removeTeachingAssistantTeam(TeachingAssistantTeam team) {
+ this.teachingAssistantTeams.remove(team);
+ }
+
+ public void addParticipant(Person person) {
+ if (!this.participants.contains(person)) {
+ this.participants.add(person);
+ }
+ }
+
+ public void removeParticipant(Person person) {
+ this.participants.remove(person);
}
/*
@@ -97,6 +140,22 @@ public class Course extends BasicEntity {
return code;
}
+ public String getGithubOrganizationName() {
+ return this.githubOrganizationName;
+ }
+
+ public void setGithubOrganizationName(String name) {
+ this.githubOrganizationName = name;
+ }
+
+ public String getApiKey() {
+ return this.apiKey;
+ }
+
+ public void setApiKey(String key) {
+ this.apiKey = key;
+ }
+
public List getAssignments() {
return assignments;
}
@@ -109,6 +168,26 @@ public class Course extends BasicEntity {
return teachingAssistantTeams;
}
+ public List getStudents() {
+ List students = new ArrayList<>();
+ this.participants.forEach(participant -> {
+ if (participant instanceof Student) {
+ students.add((Student) participant);
+ }
+ });
+ return students;
+ }
+
+ public List getTeachingAssistants() {
+ List teachingAssistants = new ArrayList<>();
+ this.participants.forEach(participant -> {
+ if (participant instanceof TeachingAssistant) {
+ teachingAssistants.add((TeachingAssistant) participant);
+ }
+ });
+ return teachingAssistants;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(this.getName()).append('\n');
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Person.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Person.java
index 47cef1d..70245af 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Person.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Person.java
@@ -1,6 +1,7 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people;
import nl.andrewlalis.teaching_assistant_assistant.model.BasicEntity;
+import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.Team;
import javax.persistence.*;
@@ -26,6 +27,9 @@ public abstract class Person extends BasicEntity {
@Column
private String emailAddress;
+ @Column
+ private String githubUsername;
+
/**
* The list of teams that this person belongs to. Because a person can belong to more than one team, it is implied
* that each person exists in only one location in the database. Therefore, if one person is enrolled in two courses
@@ -38,11 +42,21 @@ public abstract class Person extends BasicEntity {
)
private List teams;
+ /**
+ * The list of courses that this person belongs to.
+ */
+ @ManyToMany(
+ fetch = FetchType.LAZY,
+ mappedBy = "participants"
+ )
+ private List courses;
+
/**
* Default constructor for JPA.
*/
protected Person () {
this.teams = new ArrayList<>();
+ this.courses = new ArrayList<>();
}
/**
@@ -50,16 +64,26 @@ public abstract class Person extends BasicEntity {
* @param firstName The person's first name.
* @param lastName The person's last name.
* @param emailAddress The person's email address.
+ * @param githubUsername The person's github username;
*/
- public Person(String firstName, String lastName, String emailAddress) {
+ public Person(String firstName, String lastName, String emailAddress, String githubUsername) {
this();
this.firstName = firstName;
this.lastName = lastName;
this.emailAddress = emailAddress;
+ this.githubUsername = githubUsername;
}
public void assignToTeam(Team team) {
- this.teams.add(team);
+ if (!this.teams.contains(team)) {
+ this.teams.add(team);
+ }
+ }
+
+ public void assignToCourse(Course course) {
+ if (!this.courses.contains(course)) {
+ this.courses.add(course);
+ }
}
/*
@@ -82,8 +106,48 @@ public abstract class Person extends BasicEntity {
return this.emailAddress;
}
+ public String getGithubUsername() {
+ return this.githubUsername;
+ }
+
+ public List getCourses() {
+ return this.courses;
+ }
+
+ public List getTeams() {
+ return this.teams;
+ }
+
+ /**
+ * Determines if two Persons are equal. They are considered equal when all of the basic identifying information
+ * about the person is the same, regardless of case.
+ * @param o The other object.
+ * @return True if the other object is the same person, or false if not.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (o instanceof Person) {
+ Person p = (Person) o;
+ return (
+ this.getFirstName().equalsIgnoreCase(p.getFirstName())
+ && this.getLastName().equalsIgnoreCase(p.getLastName())
+ && this.getEmailAddress().equalsIgnoreCase(p.getEmailAddress())
+ && this.getGithubUsername().equalsIgnoreCase(p.getGithubUsername())
+ );
+ }
+
+ return false;
+ }
+
@Override
public String toString() {
- return this.getFirstName() + ' ' + this.getLastName() + '[' + this.getId() + ']';
+ return "First Name: " + this.getFirstName()
+ + ", Last Name: " + this.getLastName()
+ + ", Email: " + this.getEmailAddress()
+ + ", Github Username: " + this.getGithubUsername();
}
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Student.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Student.java
index e61216f..26963bf 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Student.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/Student.java
@@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people;
+import javax.persistence.Column;
import javax.persistence.Entity;
/**
@@ -8,13 +9,52 @@ import javax.persistence.Entity;
@Entity
public class Student extends Person {
+ /**
+ * The student's unique student number, as given by the university.
+ */
+ @Column(unique = true, nullable = false)
+ private int studentNumber;
+
/**
* Default constructor for JPA.
*/
protected Student() {}
- public Student(String firstName, String lastName, String emailAddress) {
- super(firstName, lastName, emailAddress);
+ /**
+ * Constructs a new student with all the properties of a Person, and any extra properties.
+ * @param firstName The student's first name.
+ * @param lastName The student's last name.
+ * @param emailAddress The student's email address.
+ * @param githubUsername The student's Github username.
+ * @param studentNumber The student's unique student number.
+ */
+ public Student(String firstName, String lastName, String emailAddress, String githubUsername, int studentNumber) {
+ super(firstName, lastName, emailAddress, githubUsername);
+
+ this.studentNumber = studentNumber;
}
+ public int getStudentNumber() {
+ return studentNumber;
+ }
+
+ public void setStudentNumber(int studentNumber) {
+ this.studentNumber = studentNumber;
+ }
+
+ /**
+ * Determines if two students are equal. They are considered equal if their person attributes are the same, or
+ * their student-specific attributes are equal.
+ * @param o The other object.
+ * @return True if the other object is the same student.
+ */
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) || this.getStudentNumber() == ((Student) o).getStudentNumber();
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", Student Number: " + this.getStudentNumber();
+ }
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/TeachingAssistant.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/TeachingAssistant.java
index 19dfcf8..dee492b 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/TeachingAssistant.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/TeachingAssistant.java
@@ -15,7 +15,7 @@ public class TeachingAssistant extends Person {
}
- public TeachingAssistant(String firstName, String lastName, String emailAddress) {
- super(firstName, lastName, emailAddress);
+ public TeachingAssistant(String firstName, String lastName, String githubUsername, String emailAddress) {
+ super(firstName, lastName, emailAddress, githubUsername);
}
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/StudentTeam.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/StudentTeam.java
index 70fddce..dad4d47 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/StudentTeam.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/StudentTeam.java
@@ -1,18 +1,32 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people.teams;
import nl.andrewlalis.teaching_assistant_assistant.model.assignments.grades.AssignmentGrade;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Person;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.OneToMany;
+import javax.persistence.*;
+import java.util.ArrayList;
import java.util.List;
/**
* A group of students.
*/
@Entity
-public class StudentTeam extends Team {
+public class StudentTeam extends Team {
+
+ /**
+ * The name of the github repository that belongs to this team.
+ */
+ @Column
+ private String githubRepositoryName;
+
+ /**
+ * The teaching assistant team to which this student team is assigned.
+ */
+ @ManyToOne(
+ fetch = FetchType.LAZY
+ )
+ private TeachingAssistantTeam assignedTeachingAssistantTeam;
/**
* The list of assignment grades which this student group has received.
@@ -27,8 +41,29 @@ public class StudentTeam extends Team {
*/
public StudentTeam() {}
- @Override
- public void addMember(Student person) {
- this.getMembers().add(person);
+ public List getStudents() {
+ List people = super.getMembers();
+ List students = new ArrayList<>();
+ people.forEach(person -> {
+ students.add((Student) person);
+ });
+ return students;
}
+
+ public String getGithubRepositoryName() {
+ return this.githubRepositoryName;
+ }
+
+ public void setGithubRepositoryName(String name) {
+ this.githubRepositoryName = name;
+ }
+
+ public TeachingAssistantTeam getAssignedTeachingAssistantTeam() {
+ return this.assignedTeachingAssistantTeam;
+ }
+
+ public void setAssignedTeachingAssistantTeam(TeachingAssistantTeam team) {
+ this.assignedTeachingAssistantTeam = team;
+ }
+
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/TeachingAssistantTeam.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/TeachingAssistantTeam.java
index 5361bc1..6cdf318 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/TeachingAssistantTeam.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/TeachingAssistantTeam.java
@@ -1,6 +1,7 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people.teams;
import nl.andrewlalis.teaching_assistant_assistant.model.assignments.grades.SectionGrade;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Person;
import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistantRole;
@@ -8,13 +9,14 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
+import java.util.ArrayList;
import java.util.List;
/**
* A group of teaching assistants.
*/
@Entity
-public class TeachingAssistantTeam extends Team {
+public class TeachingAssistantTeam extends Team {
/**
* The role that this teaching assistant team plays.
@@ -22,19 +24,53 @@ public class TeachingAssistantTeam extends Team {
@Column
private TeachingAssistantRole role;
+ /**
+ * The string name of this team.
+ */
+ @Column
+ private String githubTeamName;
+
@OneToMany
@JoinColumn(
name = "teaching_assistant_team_id"
)
private List sectionGrades;
+ @OneToMany
+ @JoinColumn(
+ name = "teaching_assistant_team_id"
+ )
+ private List assignedStudentTeams;
+
/**
* Default constructor for JPA.
*/
public TeachingAssistantTeam() {}
- @Override
- public void addMember(TeachingAssistant person) {
- this.getMembers().add(person);
+ public List getTeachingAssistants() {
+ List people = super.getMembers();
+ List teachingAssistants = new ArrayList<>(people.size());
+ people.forEach(person -> teachingAssistants.add((TeachingAssistant) person));
+ return teachingAssistants;
+ }
+
+ public String getGithubTeamName() {
+ return this.githubTeamName;
+ }
+
+ public void setGithubTeamName(String name) {
+ this.githubTeamName = name;
+ }
+
+ public List getAssignedStudentTeams() {
+ return this.assignedStudentTeams;
+ }
+
+ public void addAssignedStudentTeam(StudentTeam studentTeam) {
+ this.assignedStudentTeams.add(studentTeam);
+ }
+
+ public void removeAssignedStudentTeam(StudentTeam studentTeam) {
+ this.assignedStudentTeams.remove(studentTeam);
}
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/Team.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/Team.java
index 255883f..0e81635 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/Team.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/people/teams/Team.java
@@ -17,7 +17,7 @@ import java.util.List;
@Inheritance(
strategy = InheritanceType.JOINED
)
-public abstract class Team
extends BasicEntity {
+public abstract class Team extends BasicEntity {
/**
* The list of members in this group.
@@ -30,7 +30,7 @@ public abstract class Team
members;
+ protected List members;
/**
* The course that this team belongs to. A team cannot exist on its own, it must belong to a course.
@@ -47,23 +47,23 @@ public abstract class Team
extends BasicEntity {
this.members = new ArrayList<>();
}
- public void addMember(P person) {
+ public void addMember(Person person) {
if (!this.containsMember(person)) {
this.members.add(person);
}
}
- public void addMembers(List
people) {
- for (P person : people) {
+ public void addMembers(List people) {
+ for (Person person : people) {
this.addMember(person);
}
}
- public boolean containsMember(P person) {
+ public boolean containsMember(Person person) {
return this.members.contains(person);
}
- public void removeMember(P person) {
+ public void removeMember(Person person) {
this.members.remove(person);
}
@@ -79,7 +79,7 @@ public abstract class Team
extends BasicEntity {
* Gets a list of all members of this team.
* @return A list of all the members in this team.
*/
- public List
getMembers() {
+ public List getMembers() {
return this.members;
}
@@ -90,8 +90,8 @@ public abstract class Team
extends BasicEntity {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- for (P p : this.getMembers()) {
- sb.append(p.toString()).append(", ");
+ for (Person p : this.getMembers()) {
+ sb.append(p.getFullName()).append(", ");
}
return sb.toString();
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/StudentRepository.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/StudentRepository.java
new file mode 100644
index 0000000..3f86bb7
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/StudentRepository.java
@@ -0,0 +1,17 @@
+package nl.andrewlalis.teaching_assistant_assistant.model.repositories;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+public interface StudentRepository extends CrudRepository {
+
+ /**
+ * Tries to find a student by its unique student number.
+ * @param studentNumber The student number to search for.
+ * @return An optional Student object.
+ */
+ public Optional findByStudentNumber(int studentNumber);
+
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/StudentTeamRepository.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/StudentTeamRepository.java
new file mode 100644
index 0000000..1800848
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/StudentTeamRepository.java
@@ -0,0 +1,10 @@
+package nl.andrewlalis.teaching_assistant_assistant.model.repositories;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+public interface StudentTeamRepository extends CrudRepository {
+ Optional findByCourseCodeAndId(String courseCode, long id);
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/TeachingAssistantRepository.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/TeachingAssistantRepository.java
new file mode 100644
index 0000000..63154c9
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/TeachingAssistantRepository.java
@@ -0,0 +1,7 @@
+package nl.andrewlalis.teaching_assistant_assistant.model.repositories;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
+import org.springframework.data.repository.CrudRepository;
+
+public interface TeachingAssistantRepository extends CrudRepository {
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/TeachingAssistantTeamRepository.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/TeachingAssistantTeamRepository.java
new file mode 100644
index 0000000..a06552b
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/model/repositories/TeachingAssistantTeamRepository.java
@@ -0,0 +1,7 @@
+package nl.andrewlalis.teaching_assistant_assistant.model.repositories;
+
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
+import org.springframework.data.repository.CrudRepository;
+
+public interface TeachingAssistantTeamRepository extends CrudRepository {
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/github/GithubManager.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/github/GithubManager.java
new file mode 100644
index 0000000..98ab14a
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/github/GithubManager.java
@@ -0,0 +1,202 @@
+package nl.andrewlalis.teaching_assistant_assistant.util.github;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
+import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.kohsuke.github.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.Calendar;
+
+/**
+ * Encapsulates much of the github functionality that is needed.
+ */
+public class GithubManager {
+
+ private GitHub github;
+ private String apiKey;
+
+ /**
+ * Constructs this manager with an API key.
+ * @param apiKey The api key to use.
+ * @throws IOException if the key is invalid.
+ */
+ public GithubManager(String apiKey) throws IOException {
+ this.github = GitHub.connectUsingOAuth(apiKey);
+ this.apiKey = apiKey;
+ }
+
+ /**
+ * Constructs this manager with a username/password login combination.
+ * @param username The username to use.
+ * @param password The password for the above username.
+ * @throws IOException if the username or password are invalid.
+ */
+ public GithubManager(String username, String password) throws IOException {
+ this.github = GitHub.connectUsingPassword(username, password);
+ }
+
+ /**
+ * Generates a new Github Repository for the given student team.
+ * @param team The team to create a repository for.
+ * @return The name of the created repository.
+ */
+ public String generateStudentTeamRepository(StudentTeam team) {
+ GHOrganization organization;
+
+ try {
+ organization = this.github.getOrganization(team.getCourse().getGithubOrganizationName());
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not get Github organization with name: " + team.getCourse().getGithubOrganizationName());
+ return null;
+ }
+
+ Calendar today = Calendar.getInstance();
+ int year = today.get(Calendar.YEAR);
+ String repositoryName = year + "_Team_" + team.getId();
+
+ // Get the TA team which manages this repository.
+ GHTeam teachingAssistantGithubTeam;
+
+ try {
+ teachingAssistantGithubTeam = organization.getTeamByName(team.getAssignedTeachingAssistantTeam().getGithubTeamName());
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not get team by name: " + team.getAssignedTeachingAssistantTeam().getGithubTeamName());
+ return null;
+ }
+
+ // Create the repo
+ GHCreateRepositoryBuilder repositoryBuilder = organization.createRepository(repositoryName);
+ repositoryBuilder.wiki(false);
+ repositoryBuilder.issues(true);
+ repositoryBuilder.description("University of Groningen OOP Student Repository");
+ repositoryBuilder.private_(true);
+ repositoryBuilder.autoInit(false);
+ GHRepository repository;
+ try {
+ repository = repositoryBuilder.create();
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not create repository: " + repositoryName);
+ return null;
+ }
+
+ try {
+ this.addRepositoryToTeam(teachingAssistantGithubTeam, repository);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not add repository " + repositoryName + " to team " + teachingAssistantGithubTeam.getName());
+ return null;
+ }
+
+ try {
+ this.addStarterFile(repository, "program_resources/getting_started.md", "getting_started.md");
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not add the starter file to the repository: " + repositoryName);
+ return null;
+ }
+
+ try {
+ this.createDevelopmentBranch(repository);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not create development branch for repository: " + repositoryName);
+ return null;
+ }
+
+ try {
+ this.protectMasterBranch(repository, teachingAssistantGithubTeam);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not add protections to the master branch of " + repositoryName);
+ return null;
+ }
+
+ try {
+ this.addStudentsAsCollaborators(repository, team);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("Could not add students as collaborators to " + repositoryName);
+ return null;
+ }
+
+
+ return repositoryName;
+ }
+
+ private void addStarterFile(GHRepository repository, String filePath, String fileName) throws IOException {
+ GHContentBuilder contentBuilder = repository.createContent();
+ contentBuilder.branch("master");
+ contentBuilder.message("Initial Commit");
+
+ URL resource = getClass().getClassLoader().getResource(filePath);
+ if (resource == null) {
+ throw new IOException("Could not get resource identified by " + filePath);
+ }
+ File f = new File(resource.getFile());
+
+ contentBuilder.content(Files.readAllBytes(f.toPath()));
+ contentBuilder.path(fileName);
+ contentBuilder.commit();
+ }
+
+ private void protectMasterBranch(GHRepository repository, GHTeam adminTeam) throws IOException {
+ GHBranchProtectionBuilder protectionBuilder = repository.getBranch("master").enableProtection();
+ protectionBuilder.includeAdmins(false);
+ protectionBuilder.restrictPushAccess();
+ protectionBuilder.teamPushAccess(adminTeam);
+ protectionBuilder.addRequiredChecks("ci/circleci");
+ protectionBuilder.enable();
+ }
+
+ private void addStudentsAsCollaborators(GHRepository repository, StudentTeam studentTeam) throws IOException {
+ for (Student student : studentTeam.getStudents()) {
+ this.addCollaborator(repository.getFullName(), student.getGithubUsername(), "push");
+ }
+
+ }
+
+ private void createDevelopmentBranch(GHRepository repository) throws IOException {
+ String sha1 = repository.getBranch(repository.getDefaultBranch()).getSHA1();
+ repository.createRef("refs/heads/development", sha1);
+ }
+
+ public void addCollaborator(String repositoryName, String githubUsername, String permission) throws IOException {
+ try {
+ String url = "https://api.github.com/repos/" + repositoryName + "/collaborators/" + githubUsername + "?access_token=" + this.apiKey;
+ HttpPut put = new HttpPut(url);
+ CloseableHttpClient client = HttpClientBuilder.create().build();
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode root = mapper.createObjectNode();
+ root.put("permission", permission);
+ String json = mapper.writeValueAsString(root);
+ put.setEntity(new StringEntity(json));
+ HttpResponse response = client.execute(put);
+
+ if (response.getStatusLine().getStatusCode() != 201) {
+ throw new IOException("Error adding collaborator via url " + url + " : " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase());
+ }
+ } catch (JsonProcessingException | UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void addRepositoryToTeam(GHTeam team, GHRepository repository) throws IOException {
+ team.add(repository, GHOrganization.Permission.ADMIN);
+ }
+
+}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/CourseGenerator.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/CourseGenerator.java
index eecf759..0273c0d 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/CourseGenerator.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/CourseGenerator.java
@@ -56,11 +56,11 @@ public class CourseGenerator extends TestDataGenerator {
List studentTeams = this.generateStudentTeams();
List teachingAssistantTeams = this.generateTeachingAssistantTeams();
for (StudentTeam team : studentTeams) {
- course.addStudentGroup(team);
+ course.addStudentTeam(team);
team.setCourse(course);
}
for (TeachingAssistantTeam team : teachingAssistantTeams) {
- course.addTeachingAssistantGroup(team);
+ course.addTeachingAssistantTeam(team);
team.setCourse(course);
}
return course;
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/StudentGenerator.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/StudentGenerator.java
index 23240c4..01945b5 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/StudentGenerator.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/StudentGenerator.java
@@ -12,6 +12,6 @@ public class StudentGenerator extends PersonGenerator {
public Student generate() {
String firstName = this.getRandomFirstName();
String lastName = this.getRandomLastName();
- return new Student(firstName, lastName, this.getRandomEmailAddress(firstName, lastName));
+ return new Student(firstName, lastName, this.getRandomEmailAddress(firstName, lastName), null, this.getRandomInteger(0, 100000000));
}
}
diff --git a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/TeachingAssistantGenerator.java b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/TeachingAssistantGenerator.java
index e4c9a89..8b2c8d4 100644
--- a/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/TeachingAssistantGenerator.java
+++ b/src/main/java/nl/andrewlalis/teaching_assistant_assistant/util/sample_data/TeachingAssistantGenerator.java
@@ -12,6 +12,6 @@ public class TeachingAssistantGenerator extends PersonGenerator importFromCSV(InputStream fileInputStream, Course course) throws IOException {
+ Iterable records = CSVFormat.DEFAULT
+ .withSkipHeaderRecord()
+ .withHeader("timestamp", "email", "name", "number", "github_username", "has_partner", "partner_name", "partner_number", "partner_email", "partner_github_username")
+ .parse(new InputStreamReader(fileInputStream));
+
+ List studentEntries = extractStudentsFromRecords(records);
+
+ List studentTeams = generateTeamsFromStudentEntries(studentEntries, 0);
+
+ return studentTeams;
+ }
+
+ /**
+ * Extracts all student data from a list of records, and automatically discards outdated responses (those where the
+ * same student submitted more than once).
+ * @param records The list of records in the CSV file.
+ * @return A list of all student entries. A student entry is an intermediate object used to organize the parsed data
+ * from each record in the CSV file.
+ */
+ private static List extractStudentsFromRecords(Iterable records) {
+ List studentEntries = new ArrayList<>();
+
+ for (CSVRecord record : records) {
+ // Parse the actual student.
+ Student s = parseStudentRecordData(record.get("name"), record.get("email"), record.get("github_username"), record.get("number"));
+
+ // Parse the student's preferred partner, if they exist.
+ Student preferredPartner = null;
+ if (record.get("has_partner").equalsIgnoreCase("yes")) {
+ preferredPartner = parseStudentRecordData(record.get("partner_name"), record.get("partner_email"), record.get("partner_github_username"), record.get("partner_number"));
+ }
+
+ // Parse the timestamp.
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd h:mm:ss a O ");
+ ZonedDateTime dateTime = ZonedDateTime.parse(record.get("timestamp") + ' ', formatter);
+ // A space is added because of a java bug: https://stackoverflow.com/questions/37287103/why-does-gmt8-fail-to-parse-with-pattern-o-despite-being-copied-straight-ou
+
+ studentEntries.add(new StudentRecordEntry(dateTime, s, preferredPartner));
+ }
+
+ studentEntries = removeDuplicateEntries(studentEntries);
+
+ return studentEntries;
+ }
+
+ /**
+ * Generates a list of teams from a list of unique student entries. The algorithm is described as follows:
+ *
+ * 1. Select the next student from the list of entries.
+ * 2. If the student (A) has specified a partner (B), search for that partner (B) in the list of entries.
+ * 3. If the partner (B) also specified the student (A) as their preferred partner, a team is created from the two.
+ * a. Pop both student entries from the list of entries.
+ * 4. Else, place student (A) in a queue of single students.
+ * a. Pop student (A) from the list of entries.
+ * 5. If the list of entries is not empty, go to 1.
+ * 6. The list of entries is now empty, and zero or more entries exist in a queue of single students.
+ * 7. Pop two (or as many as possible) students randomly from the queue, and form a team from them.
+ * 8. Repeat until the queue of single students is empty.
+ * 9. There is now a list of student teams and no student should remain unprocessed.
+ *
+ * @param entries A list of record entries.
+ * @param seed A seed to use for randomization of single student teams.
+ * @return A list of student teams.
+ */
+ private static List generateTeamsFromStudentEntries(List entries, long seed) {
+ List teams = new ArrayList<>();
+ List singleStudentsEntryQueue = new ArrayList<>();
+ Random random = new Random(seed);
+
+ // Realize step 5. Loop until there are no more processed entries.
+ while (!entries.isEmpty()) {
+ // Step 1. Select the next student from the list of entries.
+ StudentRecordEntry entry = entries.remove(0);
+
+ // Step 2. If the student has a partner, search for that partner.
+ if (entry.hasPartner()) {
+ boolean partnerFound = false; // Use this to keep track of if a partner is actually found.
+ for (int i = 0; i < entries.size(); i++) {
+ StudentRecordEntry secondEntry = entries.get(i);
+ // Step 3. If the partner specifies their partner as the student (both want each other), then create a team.
+ if (
+ entry.getPartnerStudent().equals(secondEntry.getStudent())
+ && secondEntry.hasPartner()
+ && secondEntry.getPartnerStudent().equals(entry.getStudent())
+ ) {
+ partnerFound = true;
+
+ entries.remove(i); // Step 3a. The first student has been popped, so pop this one.
+
+ StudentTeam team = new StudentTeam();
+ team.addMember(entry.getStudent());
+ team.addMember(secondEntry.getStudent());
+ teams.add(team);
+
+ break;
+ }
+ }
+
+ // Step 4. If no partner was found, then add this entry to the queue of single students.
+ if (!partnerFound) {
+ singleStudentsEntryQueue.add(entry);
+ }
+ } else {
+ // Also Step 4. The student chose to have no partner, so add this entry to the queue of single students.
+ singleStudentsEntryQueue.add(entry);
+ }
+ }
+
+ // We are now at step 6.
+ while (!singleStudentsEntryQueue.isEmpty()) {
+ StudentTeam team = new StudentTeam();
+ StudentRecordEntry firstRandomEntry = singleStudentsEntryQueue.remove(random.nextInt(singleStudentsEntryQueue.size()));
+ team.addMember(firstRandomEntry.getStudent());
+
+ // Check if there's another student in the queue.
+ if (!singleStudentsEntryQueue.isEmpty()) {
+ StudentRecordEntry secondRandomEntry = singleStudentsEntryQueue.remove(random.nextInt(singleStudentsEntryQueue.size()));
+ team.addMember(secondRandomEntry.getStudent());
+ }
+
+ teams.add(team);
+ }
+
+ return teams;
+ }
+
+ /**
+ * Scans a list of student entries and removes entries which are outdated by newer entries by the same student, such
+ * that the resulting list contains only one unique entry per student.
+ * @param studentEntries The list of student entries, possibly containing duplicates.
+ * @return A list of student entries in which all outdated duplicates have been removed.
+ */
+ private static List removeDuplicateEntries(List studentEntries) {
+ List uniqueStudentEntries = new ArrayList<>();
+
+ for (StudentRecordEntry entry : studentEntries) {
+ // Check for if the current entry's student already exists.
+ boolean duplicateFound = false;
+ for (StudentRecordEntry existingEntry : uniqueStudentEntries) {
+ if (entry.getStudent().equals(existingEntry.getStudent())) {
+ duplicateFound = true;
+ // Check if the existing entry is older than the new one; it should be overwritten.
+ if (existingEntry.getDateTime().isBefore(entry.getDateTime())) {
+ uniqueStudentEntries.remove(existingEntry);
+ uniqueStudentEntries.add(entry);
+ break;
+ }
+ }
+ }
+
+ if (!duplicateFound) {
+ uniqueStudentEntries.add(entry);
+ }
+ }
+
+ return uniqueStudentEntries;
+ }
+
+ /**
+ * Creates a student object from given entries in a record obtained from a CSV file.
+ * @param name The name value.
+ * @param email The email value.
+ * @param githubUsername The github_username value.
+ * @param studentNumber The number value.
+ * @return A student object constructed from the given data.
+ */
+ private static Student parseStudentRecordData(String name, String email, String githubUsername, String studentNumber) {
+ // Extract a sensible first and last name.
+ String[] nameSegments = name.split(" ");
+ String firstName = "No First Name Given";
+ String lastName = "No Last Name Given";
+
+ if (nameSegments.length > 0) {
+ firstName = nameSegments[0];
+ }
+
+ if (nameSegments.length > 1) {
+ lastName = String.join(" ", Arrays.copyOfRange(nameSegments, 1, nameSegments.length));
+ }
+
+ // Extract a sensible github username.
+ String githubURL = "https://github.com/";
+
+ if (githubUsername.startsWith(githubURL)) {
+ githubUsername = githubUsername.substring(githubURL.length());
+ }
+
+ // Create the student.
+ return new Student(firstName, lastName, email, githubUsername, Integer.parseInt(studentNumber));
+ }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index e12669e..2541540 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,10 +1,14 @@
-spring.jpa.hibernate.ddl-auto=create
-#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
-
-#spring.jpa.properties.hibernate.show_sql=true
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
+spring.datasource.username=root
+spring.datasource.password=root
+spring.datasource.url=jdbc:mysql://localhost:3306/teaching_assistant_assistant?serverTimezone=UTC
+#
+spring.jpa.properties.hibernate.show_sql=false
#spring.jpa.properties.hibernate.use_sql_comments=true
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.properties.hibernate.type=trace
spring.security.user.name=tester
spring.security.user.password=tester
+
diff --git a/src/main/resources/program_resources/getting_started.md b/src/main/resources/program_resources/getting_started.md
new file mode 100644
index 0000000..13dd306
--- /dev/null
+++ b/src/main/resources/program_resources/getting_started.md
@@ -0,0 +1,62 @@
+# Getting Started
+Welcome to the University of Groningen's Object Oriented Programming course! We're glad to have you. Read the instructions provided here carefully so that you can have a smooth start to the course.
+
+## Setting Up
+To begin, you'll need to download this repository you're looking at right now. To do that, run the following command (where `repository_url` is the URL of this repository):
+
+```
+git clone
+```
+
+Now you should have a local directory with the same name as this repository. Checkout your `development` branch, since you do not have permission as a student to make changes to the master branch.
+
+From here, we need to copy the files from the assignments repository into ours. To do this, we first add a new 'remote' to our local git repository, and then pull from that repository's `master` branch.
+
+> Note that if you have not received an invitation to the 2019_assignments repository yet, you'll have to wait until you can complete this part. Thank you for your patience.
+
+```
+git checkout development
+
+git remote add assignments https://github.com/rug-oop/2019_assignments
+
+git pull assignments master --allow-unrelated-histories
+```
+
+Now you should see all the files just as they appear in the assignments repository.
+
+## Developing Your Code and Submitting
+While previous knowledge of git may be useful, it should not really be needed for you to do well in this course, as long as you know how to submit each assignment.
+
+When you have finished working on your code, you should first add any new files which you have created (`-A` means "all files". Be careful, since you should not upload compiled class files).
+
+```
+git add -A
+```
+
+Now, commit all changes that have been made (`-a` means "all changed files", and `-m` allows you to supply a commit message. If you do not add `-m`, git will try to open a text editor for you to write a message in).
+
+```
+git commit -a -m "This is a short message about what I did."
+```
+
+To make these committed changes public, you need to upload them to the remote repository. This is done by 'pushing'.
+
+```
+git push
+```
+
+If you refresh the page for your repository on github.com, you should now see changes in whatever branch you pushed to (most likely `development`).
+
+### Submission
+Once you've committed and push a few times, and you feel that your code is ready to be submitted, create a new pull request from your `development` branch onto the `master` branch. When you do this, a continuous integration service will compile your code and run `mvn integration-test` to see that all tests (if any) pass. If your code does not compile or pass all tests, don't bother making a submission; we won't grade it. Fix it and submit a working version.
+
+If you have made a pull request, but then would like to make some last-minute changes, you can do this by simply adding more commits to the `development` branch. Any pushed commits will simply be added to an existing pull request.
+
+## Questions, Comments, Concerns
+Should you have any questions about Github, the above workflow, or other technical questions, please first see if Google can help, and then if you still cannot figure out what to do, make an issue in your repository that adheres to the following guidelines:
+
+* What is the problem?
+* What have you already tried to do to fix it?
+* How can my teaching assistant replicate the problem?
+
+By giving us this information, you make it much easier for us to give meaningful feedback as quickly as possible.
diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css
index da877e4..e1d6b8e 100644
--- a/src/main/resources/static/css/style.css
+++ b/src/main/resources/static/css/style.css
@@ -34,4 +34,9 @@ body {
.sidebar_block a:hover {
color: lightgreen;
+}
+
+table, th, td {
+ border: 1px solid black;
+ border-collapse: collapse;
}
\ No newline at end of file
diff --git a/src/main/resources/templates/courses.html b/src/main/resources/templates/courses.html
index d6f8562..42b41e7 100644
--- a/src/main/resources/templates/courses.html
+++ b/src/main/resources/templates/courses.html
@@ -11,6 +11,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/courses/entity/teaching_assistant_teams/assign_to_student_teams.html b/src/main/resources/templates/courses/entity/teaching_assistant_teams/assign_to_student_teams.html
new file mode 100644
index 0000000..fd3b330
--- /dev/null
+++ b/src/main/resources/templates/courses/entity/teaching_assistant_teams/assign_to_student_teams.html
@@ -0,0 +1,21 @@
+
+
+
+ Assign To Student Teams
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/courses/entity/teaching_assistant_teams/create.html b/src/main/resources/templates/courses/entity/teaching_assistant_teams/create.html
new file mode 100644
index 0000000..29af127
--- /dev/null
+++ b/src/main/resources/templates/courses/entity/teaching_assistant_teams/create.html
@@ -0,0 +1,27 @@
+
+
+
+ Create Teaching Assistant Team
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/courses/entity/teaching_assistant_teams/entity.html b/src/main/resources/templates/courses/entity/teaching_assistant_teams/entity.html
new file mode 100644
index 0000000..4f5153c
--- /dev/null
+++ b/src/main/resources/templates/courses/entity/teaching_assistant_teams/entity.html
@@ -0,0 +1,43 @@
+
+
+
+ Teaching Assistant Team
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/courses/entity/teaching_assistants/create.html b/src/main/resources/templates/courses/entity/teaching_assistants/create.html
new file mode 100644
index 0000000..88de95a
--- /dev/null
+++ b/src/main/resources/templates/courses/entity/teaching_assistants/create.html
@@ -0,0 +1,36 @@
+
+
+
+ Create a Course
+
+
+
+
+
+ Create your course here
+
+
+
+
+
+
+ block
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/fragments/header.html b/src/main/resources/templates/fragments/header.html
index 2cd3521..5b1b3f6 100644
--- a/src/main/resources/templates/fragments/header.html
+++ b/src/main/resources/templates/fragments/header.html
@@ -13,6 +13,8 @@