Merge pull request #1 from andrewlalis/data_import

Data import
This commit is contained in:
Andrew Lalis 2019-04-21 20:56:37 +02:00 committed by GitHub
commit 5928bb4463
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 2375 additions and 155 deletions

18
pom.xml
View File

@ -79,6 +79,24 @@
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version> <version>8.0.15</version>
</dependency> </dependency>
<!-- CSV Format dependency -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.5</version>
</dependency>
<!-- Apache HTTP Library -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<!-- Github API Object Library -->
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.95</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -27,6 +27,5 @@ public class TeachingAssistantAssistantApplication implements CommandLineRunner
@Override @Override
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
System.out.println("Running startup..."); System.out.println("Running startup...");
} }
} }

View File

@ -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();
}
}

View File

@ -41,7 +41,6 @@ public class Courses {
consumes = "application/x-www-form-urlencoded" consumes = "application/x-www-form-urlencoded"
) )
public String post(@ModelAttribute Course course) { public String post(@ModelAttribute Course course) {
System.out.println("Object submitted: " + course);
this.courseRepository.save(course); this.courseRepository.save(course);
return "courses/entity"; return "courses/entity";
} }

View File

@ -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";
}
}

View File

@ -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<Course> 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<Course> optionalCourse = this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> {
course.addParticipant(teachingAssistant);
this.courseRepository.save(course);
});
return "redirect:/teaching_assistants";
}
}

View File

@ -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<Course> 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<Course> 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<Course> 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<Course> 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<Course> courseOptional = this.courseRepository.findByCode(code);
courseOptional.ifPresent(course -> model.addAttribute("course", course));
return "courses/entity/teaching_assistants";
}
}

View File

@ -7,11 +7,11 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
public class Create { public class CreateCourse {
private CourseRepository courseRepository; private CourseRepository courseRepository;
protected Create(CourseRepository courseRepository) { protected CreateCourse(CourseRepository courseRepository) {
this.courseRepository = courseRepository; this.courseRepository = courseRepository;
} }

View File

@ -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<Course> courseOptional = this.courseRepository.findByCode(code);
if (courseOptional.isPresent()) {
Course course = courseOptional.get();
model.addAttribute("course", course);
}
return "courses/entity";
}
}

View File

@ -26,7 +26,7 @@ public class Generate {
this.courseRepository.saveAll(courses); this.courseRepository.saveAll(courses);
model.addAttribute("courses", courseRepository.findAll()); model.addAttribute("courses", courseRepository.findAll());
return "courses"; return "redirect:/courses";
} }
} }

View File

@ -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<Course> 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<Course> optionalCourse = this.courseRepository.findByCode(code);
if (!optionalCourse.isPresent()) {
System.out.println("No course found.");
return "redirect:/courses";
}
Course course = optionalCourse.get();
try {
List<StudentTeam> 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}";
}
}

View File

@ -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<Course> 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<Course> 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<StudentTeam> 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();
}
}

View File

@ -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<Course> 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<Course> 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}";
}
}

View File

@ -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<StudentTeam> optionalStudentTeam = this.studentTeamRepository.findByCourseCodeAndId(courseCode, teamId);
optionalStudentTeam.ifPresent(team -> model.addAttribute("student_team", team));
return "courses/entity/student_teams/entity";
}
}

View File

@ -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<Course> 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<Course> optionalCourse = this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> {
// Get a list of all the github usernames to invite.
List<String> 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<String> 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<String> 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";
}
}

View File

@ -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<Course> 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<Course> optionalCourse = this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> {
List<StudentTeam> studentTeams = course.getStudentTeams();
LinkedList<Integer> 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";
}
}

View File

@ -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<Course> 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<Course> optionalCourse = this.courseRepository.findByCode(code);
Optional<TeachingAssistant> 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";
}
}

View File

@ -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<Course> optionalCourse = this.courseRepository.findByCode(courseCode);
Optional<TeachingAssistantTeam> 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<Course> optionalCourse = this.courseRepository.findByCode(courseCode);
Optional<TeachingAssistantTeam> 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";
}
}

View File

@ -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<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> model.addAttribute("student", student));
return "students/entity";
}
}

View File

@ -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<TeachingAssistant> 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<TeachingAssistant> 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";
}
}

View File

@ -1,6 +1,9 @@
package nl.andrewlalis.teaching_assistant_assistant.model; package nl.andrewlalis.teaching_assistant_assistant.model;
import nl.andrewlalis.teaching_assistant_assistant.model.assignments.Assignment; 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.StudentTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam; import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
@ -29,6 +32,18 @@ public class Course extends BasicEntity {
@Column(unique = true, nullable = false) @Column(unique = true, nullable = false)
private String code; 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. * The list of assignments this course contains.
*/ */
@ -57,6 +72,19 @@ public class Course extends BasicEntity {
) )
private List<TeachingAssistantTeam> teachingAssistantTeams; private List<TeachingAssistantTeam> 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<Person> participants;
/** /**
* Default constructor for JPA. * Default constructor for JPA.
*/ */
@ -64,6 +92,7 @@ public class Course extends BasicEntity {
this.assignments = new ArrayList<>(); this.assignments = new ArrayList<>();
this.studentTeams = new ArrayList<>(); this.studentTeams = new ArrayList<>();
this.teachingAssistantTeams = new ArrayList<>(); this.teachingAssistantTeams = new ArrayList<>();
this.participants = new ArrayList<>();
} }
/** /**
@ -77,12 +106,26 @@ public class Course extends BasicEntity {
this.code = code; this.code = code;
} }
public void addStudentGroup(StudentTeam group) { public void addStudentTeam(StudentTeam team) {
this.studentTeams.add(group); this.studentTeams.add(team);
} }
public void addTeachingAssistantGroup(TeachingAssistantTeam group) { public void addTeachingAssistantTeam(TeachingAssistantTeam team) {
this.teachingAssistantTeams.add(group); 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; 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<Assignment> getAssignments() { public List<Assignment> getAssignments() {
return assignments; return assignments;
} }
@ -109,6 +168,26 @@ public class Course extends BasicEntity {
return teachingAssistantTeams; return teachingAssistantTeams;
} }
public List<Student> getStudents() {
List<Student> students = new ArrayList<>();
this.participants.forEach(participant -> {
if (participant instanceof Student) {
students.add((Student) participant);
}
});
return students;
}
public List<TeachingAssistant> getTeachingAssistants() {
List<TeachingAssistant> teachingAssistants = new ArrayList<>();
this.participants.forEach(participant -> {
if (participant instanceof TeachingAssistant) {
teachingAssistants.add((TeachingAssistant) participant);
}
});
return teachingAssistants;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(this.getName()).append('\n'); StringBuilder sb = new StringBuilder(this.getName()).append('\n');

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people; package nl.andrewlalis.teaching_assistant_assistant.model.people;
import nl.andrewlalis.teaching_assistant_assistant.model.BasicEntity; 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 nl.andrewlalis.teaching_assistant_assistant.model.people.teams.Team;
import javax.persistence.*; import javax.persistence.*;
@ -26,6 +27,9 @@ public abstract class Person extends BasicEntity {
@Column @Column
private String emailAddress; 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 * 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 * 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<Team> teams; private List<Team> teams;
/**
* The list of courses that this person belongs to.
*/
@ManyToMany(
fetch = FetchType.LAZY,
mappedBy = "participants"
)
private List<Course> courses;
/** /**
* Default constructor for JPA. * Default constructor for JPA.
*/ */
protected Person () { protected Person () {
this.teams = new ArrayList<>(); 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 firstName The person's first name.
* @param lastName The person's last name. * @param lastName The person's last name.
* @param emailAddress The person's email address. * @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();
this.firstName = firstName; this.firstName = firstName;
this.lastName = lastName; this.lastName = lastName;
this.emailAddress = emailAddress; this.emailAddress = emailAddress;
this.githubUsername = githubUsername;
} }
public void assignToTeam(Team team) { 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; return this.emailAddress;
} }
public String getGithubUsername() {
return this.githubUsername;
}
public List<Course> getCourses() {
return this.courses;
}
public List<Team> 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 @Override
public String toString() { 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();
} }
} }

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people; package nl.andrewlalis.teaching_assistant_assistant.model.people;
import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
/** /**
@ -8,13 +9,52 @@ import javax.persistence.Entity;
@Entity @Entity
public class Student extends Person { 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. * Default constructor for JPA.
*/ */
protected Student() {} 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();
}
} }

View File

@ -15,7 +15,7 @@ public class TeachingAssistant extends Person {
} }
public TeachingAssistant(String firstName, String lastName, String emailAddress) { public TeachingAssistant(String firstName, String lastName, String githubUsername, String emailAddress) {
super(firstName, lastName, emailAddress); super(firstName, lastName, emailAddress, githubUsername);
} }
} }

View File

@ -1,18 +1,32 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people.teams; 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.assignments.grades.AssignmentGrade;
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.Student;
import javax.persistence.Entity; import javax.persistence.*;
import javax.persistence.FetchType; import java.util.ArrayList;
import javax.persistence.OneToMany;
import java.util.List; import java.util.List;
/** /**
* A group of students. * A group of students.
*/ */
@Entity @Entity
public class StudentTeam extends Team<Student> { 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. * The list of assignment grades which this student group has received.
@ -27,8 +41,29 @@ public class StudentTeam extends Team<Student> {
*/ */
public StudentTeam() {} public StudentTeam() {}
@Override public List<Student> getStudents() {
public void addMember(Student person) { List<Person> people = super.getMembers();
this.getMembers().add(person); List<Student> 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;
}
} }

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.teaching_assistant_assistant.model.people.teams; 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.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.TeachingAssistant;
import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistantRole; import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistantRole;
@ -8,13 +9,14 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* A group of teaching assistants. * A group of teaching assistants.
*/ */
@Entity @Entity
public class TeachingAssistantTeam extends Team<TeachingAssistant> { public class TeachingAssistantTeam extends Team {
/** /**
* The role that this teaching assistant team plays. * The role that this teaching assistant team plays.
@ -22,19 +24,53 @@ public class TeachingAssistantTeam extends Team<TeachingAssistant> {
@Column @Column
private TeachingAssistantRole role; private TeachingAssistantRole role;
/**
* The string name of this team.
*/
@Column
private String githubTeamName;
@OneToMany @OneToMany
@JoinColumn( @JoinColumn(
name = "teaching_assistant_team_id" name = "teaching_assistant_team_id"
) )
private List<SectionGrade> sectionGrades; private List<SectionGrade> sectionGrades;
@OneToMany
@JoinColumn(
name = "teaching_assistant_team_id"
)
private List<StudentTeam> assignedStudentTeams;
/** /**
* Default constructor for JPA. * Default constructor for JPA.
*/ */
public TeachingAssistantTeam() {} public TeachingAssistantTeam() {}
@Override public List<TeachingAssistant> getTeachingAssistants() {
public void addMember(TeachingAssistant person) { List<Person> people = super.getMembers();
this.getMembers().add(person); List<TeachingAssistant> 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<StudentTeam> getAssignedStudentTeams() {
return this.assignedStudentTeams;
}
public void addAssignedStudentTeam(StudentTeam studentTeam) {
this.assignedStudentTeams.add(studentTeam);
}
public void removeAssignedStudentTeam(StudentTeam studentTeam) {
this.assignedStudentTeams.remove(studentTeam);
} }
} }

View File

@ -17,7 +17,7 @@ import java.util.List;
@Inheritance( @Inheritance(
strategy = InheritanceType.JOINED strategy = InheritanceType.JOINED
) )
public abstract class Team<P extends Person> extends BasicEntity { public abstract class Team extends BasicEntity {
/** /**
* The list of members in this group. * The list of members in this group.
@ -30,7 +30,7 @@ public abstract class Team<P extends Person> extends BasicEntity {
joinColumns = @JoinColumn(name = "team_id"), joinColumns = @JoinColumn(name = "team_id"),
inverseJoinColumns = @JoinColumn(name = "person_id") inverseJoinColumns = @JoinColumn(name = "person_id")
) )
protected List<P> members; protected List<Person> members;
/** /**
* The course that this team belongs to. A team cannot exist on its own, it must belong to a course. * 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<P extends Person> extends BasicEntity {
this.members = new ArrayList<>(); this.members = new ArrayList<>();
} }
public void addMember(P person) { public void addMember(Person person) {
if (!this.containsMember(person)) { if (!this.containsMember(person)) {
this.members.add(person); this.members.add(person);
} }
} }
public void addMembers(List<P> people) { public void addMembers(List<Person> people) {
for (P person : people) { for (Person person : people) {
this.addMember(person); this.addMember(person);
} }
} }
public boolean containsMember(P person) { public boolean containsMember(Person person) {
return this.members.contains(person); return this.members.contains(person);
} }
public void removeMember(P person) { public void removeMember(Person person) {
this.members.remove(person); this.members.remove(person);
} }
@ -79,7 +79,7 @@ public abstract class Team<P extends Person> extends BasicEntity {
* Gets a list of all members of this team. * Gets a list of all members of this team.
* @return A list of all the members in this team. * @return A list of all the members in this team.
*/ */
public List<P> getMembers() { public List<Person> getMembers() {
return this.members; return this.members;
} }
@ -90,8 +90,8 @@ public abstract class Team<P extends Person> extends BasicEntity {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (P p : this.getMembers()) { for (Person p : this.getMembers()) {
sb.append(p.toString()).append(", "); sb.append(p.getFullName()).append(", ");
} }
return sb.toString(); return sb.toString();
} }

View File

@ -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<Student, Long> {
/**
* 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<Student> findByStudentNumber(int studentNumber);
}

View File

@ -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<StudentTeam, Long> {
Optional<StudentTeam> findByCourseCodeAndId(String courseCode, long id);
}

View File

@ -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<TeachingAssistant, Long> {
}

View File

@ -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<TeachingAssistantTeam, Long> {
}

View File

@ -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);
}
}

View File

@ -56,11 +56,11 @@ public class CourseGenerator extends TestDataGenerator<Course> {
List<StudentTeam> studentTeams = this.generateStudentTeams(); List<StudentTeam> studentTeams = this.generateStudentTeams();
List<TeachingAssistantTeam> teachingAssistantTeams = this.generateTeachingAssistantTeams(); List<TeachingAssistantTeam> teachingAssistantTeams = this.generateTeachingAssistantTeams();
for (StudentTeam team : studentTeams) { for (StudentTeam team : studentTeams) {
course.addStudentGroup(team); course.addStudentTeam(team);
team.setCourse(course); team.setCourse(course);
} }
for (TeachingAssistantTeam team : teachingAssistantTeams) { for (TeachingAssistantTeam team : teachingAssistantTeams) {
course.addTeachingAssistantGroup(team); course.addTeachingAssistantTeam(team);
team.setCourse(course); team.setCourse(course);
} }
return course; return course;

View File

@ -12,6 +12,6 @@ public class StudentGenerator extends PersonGenerator<Student> {
public Student generate() { public Student generate() {
String firstName = this.getRandomFirstName(); String firstName = this.getRandomFirstName();
String lastName = this.getRandomLastName(); 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));
} }
} }

View File

@ -12,6 +12,6 @@ public class TeachingAssistantGenerator extends PersonGenerator<TeachingAssistan
public TeachingAssistant generate() { public TeachingAssistant generate() {
String firstName = this.getRandomFirstName(); String firstName = this.getRandomFirstName();
String lastName = this.getRandomLastName(); String lastName = this.getRandomLastName();
return new TeachingAssistant(firstName, lastName, this.getRandomEmailAddress(firstName, lastName)); return new TeachingAssistant(firstName, lastName, null, this.getRandomEmailAddress(firstName, lastName));
} }
} }

View File

@ -0,0 +1,44 @@
package nl.andrewlalis.teaching_assistant_assistant.util.team_importing;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import java.time.ZonedDateTime;
/**
* Represents a pair of students, one of which has indicated that the other is their partner.
*/
public class StudentRecordEntry {
private Student student;
private Student partnerStudent;
private ZonedDateTime dateTime;
public StudentRecordEntry(ZonedDateTime dateTime, Student student, Student partnerStudent) {
this.student = student;
this.partnerStudent = partnerStudent;
this.dateTime = dateTime;
}
public Student getStudent() {
return this.student;
}
public Student getPartnerStudent() {
return this.partnerStudent;
}
public ZonedDateTime getDateTime() {
return this.dateTime;
}
public boolean hasPartner() {
return this.partnerStudent != null;
}
@Override
public String toString() {
return "Entry at: " + this.dateTime.toString() + "\n\tStudent: " + this.getStudent() + "\n\tPreferred partner: " + this.getPartnerStudent();
}
}

View File

@ -0,0 +1,223 @@
package nl.andrewlalis.teaching_assistant_assistant.util.team_importing;
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 org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* Provides some methods to streamline the process of transforming a CSV file of student data into a list of valid teams
* which can be used for a course.
*/
public class StudentTeamImporter {
/**
* Imports data from the given File Input Stream into the course.
* @param fileInputStream An input stream representing the contents of a CSV file.
* @param course The course to add the students to.
* @return A list of valid teams that the given file describes.
*/
public static List<StudentTeam> importFromCSV(InputStream fileInputStream, Course course) throws IOException {
Iterable<CSVRecord> 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<StudentRecordEntry> studentEntries = extractStudentsFromRecords(records);
List<StudentTeam> 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<StudentRecordEntry> extractStudentsFromRecords(Iterable<CSVRecord> records) {
List<StudentRecordEntry> 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<StudentTeam> generateTeamsFromStudentEntries(List<StudentRecordEntry> entries, long seed) {
List<StudentTeam> teams = new ArrayList<>();
List<StudentRecordEntry> 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<StudentRecordEntry> removeDuplicateEntries(List<StudentRecordEntry> studentEntries) {
List<StudentRecordEntry> 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));
}
}

View File

@ -1,10 +1,14 @@
spring.jpa.hibernate.ddl-auto=create spring.jpa.hibernate.ddl-auto=update
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.datasource.username=root
#spring.jpa.properties.hibernate.show_sql=true 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.use_sql_comments=true
#spring.jpa.properties.hibernate.format_sql=true #spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.properties.hibernate.type=trace #spring.jpa.properties.hibernate.type=trace
spring.security.user.name=tester spring.security.user.name=tester
spring.security.user.password=tester spring.security.user.password=tester

View File

@ -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 <repository_url>
```
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.

View File

@ -34,4 +34,9 @@ body {
.sidebar_block a:hover { .sidebar_block a:hover {
color: lightgreen; color: lightgreen;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
} }

View File

@ -11,6 +11,7 @@
<th>Name</th> <th>Name</th>
<th>Code</th> <th>Code</th>
<th>Created at</th> <th>Created at</th>
<th>Students</th>
</tr> </tr>
<tr th:each="course: ${courses}"> <tr th:each="course: ${courses}">
<td> <td>
@ -18,6 +19,7 @@
</td> </td>
<td th:text="${course.getCode()}"></td> <td th:text="${course.getCode()}"></td>
<td th:text="${course.getCreatedOn()}"></td> <td th:text="${course.getCreatedOn()}"></td>
<td th:text="${course.getStudents().size()}"></td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -11,9 +11,17 @@
</p> </p>
<form action="#" th:action="@{/courses}" th:object="${course}" method="post"> <form action="#" th:action="@{/courses}" th:object="${course}" method="post">
<label for="course_name_input">Name:</label> <label for="course_name_input">Name:</label>
<input id="course_name_input" type="text" th:field="*{name}"/> <input id="course_name_input" type="text" th:field="*{name}" required/>
<label for="course_code_input">Code:</label> <label for="course_code_input">Code:</label>
<input id="course_code_input" type="text" th:field="*{code}"/> <input id="course_code_input" type="text" th:field="*{code}" required/>
<label for="course_github_organization_name_input">Github Organization:</label>
<input id="course_github_organization_name_input" type="text" th:field="*{githubOrganizationName}" required/>
<label for="course_github_api_key_input">Github API Key:</label>
<input id="course_github_api_key_input" type="text" th:field="*{apiKey}" required/>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</div> </div>

View File

@ -10,37 +10,23 @@
<h1><span th:text="${course.getName()}"></span> (Code: <span th:text="${course.getCode()}"></span>)</h1> <h1><span th:text="${course.getName()}"></span> (Code: <span th:text="${course.getCode()}"></span>)</h1>
<hr> <hr>
<h3>Teaching Assistant Groups (<span th:text="${course.getTeachingAssistantTeams().size()}"></span>):</h3> <ul>
<table> <li>
<tr th:each="teachingAssistantTeam: ${course.getTeachingAssistantTeams()}"> Github Organization: <a th:href="@{${'https://www.github.com/' + course.getGithubOrganizationName()}}" th:text="${course.getGithubOrganizationName()}"></a>
<td> </li>
<a <li>
th:href="@{/courses/{code}/ta_teams/{team_id} <a th:href="@{/courses/{code}/teaching_assistant_teams(code=${course.getCode()})}">Teaching Assistant Teams</a>
(code=${course.getCode()}, team_id=${teachingAssistantTeam.getId()})}"> </li>
TA Team <span th:text="${teachingAssistantTeam.getId()}"></span> <li>
</a> <a th:href="@{/courses/{code}/student_teams(code=${course.getCode()})}">Student Teams</a>
</td> </li>
<td th:each="teachingAssistant: ${teachingAssistantTeam.getMembers()}"> <li>
<span th:text="${teachingAssistant.getFullName()}"></span> <a th:href="@{/courses/{code}/students(code=${course.getCode()})}">All Students</a>
</td> </li>
</tr> <li>
</table> <a th:href="@{/courses/{code}/teaching_assistants(code=${course.getCode()})}">All Teaching Assistants</a>
</li>
<h3>Student Groups (<span th:text="${course.getStudentTeams().size()}"></span>):</h3> </ul>
<table>
<tr th:each="studentTeam: ${course.getStudentTeams()}">
<td>
<a
th:href="@{/courses/{code}/student_teams/{team_id}
(code=${course.getCode()}, team_id=${studentTeam.getId()})}">
Student Team <span th:text="${studentTeam.getId()}"></span>
</a>
</td>
<td th:each="student: ${studentTeam.getMembers()}">
<span th:text="${student.getFullName()}"></span>
</td>
</tr>
</table>
</div> </div>
<div id="sidebar"> <div id="sidebar">
@ -48,7 +34,7 @@
<a th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import students from CSV</a> <a th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import students from CSV</a>
</div> </div>
<div class="sidebar_block"> <div class="sidebar_block">
<a th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import teaching assistants from CSV</a> <a th:disabled="true" th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import teaching assistants from CSV</a>
</div> </div>
</div> </div>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar_content})}">
<head>
<title>Import Students via CSV</title>
</head>
<body>
<div id="content">
<p>
Please select a CSV file to import.
</p>
<form method="post" action="#" enctype="multipart/form-data" th:action="@{/courses/{code}/import_students(code=${course.getCode()})}">
<label for="file_input">File:</label>
<input id="file_input" type="file" name="file" accept="text/csv"/>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar_content">
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Student Teams</title>
</head>
<body>
<div id="content">
<h1>Student Teams for <span th:text="${course.getName()}"></span></h1>
<div th:if="${course.getStudentTeams().isEmpty()}">
<p>No student teams.</p>
</div>
<table>
<tr>
<th>Team</th>
<th>Students</th>
<th>Repository Name</th>
<th>Assigned TA Team</th>
</tr>
<tr th:each="studentTeam: ${course.getStudentTeams()}">
<td>
<a
th:href="@{/courses/{code}/student_teams/{team_id}
(code=${course.getCode()}, team_id=${studentTeam.getId()})}">
Student Team <span th:text="${studentTeam.getId()}"></span>
</a>
</td>
<td>
<div th:each="student: ${studentTeam.getStudents()}">
<a th:href="@{/students/{id}(id=${student.getId()})}" th:text="${student.getFullName()}"></a><br>
</div>
</td>
<td>
<span th:if="${studentTeam.getGithubRepositoryName() == null}">None</span>
<a
target="_blank"
th:if="${studentTeam.getGithubRepositoryName() != null}"
th:href="${'https://github.com/' + studentTeam.getCourse().getGithubOrganizationName() + '/' + studentTeam.getGithubRepositoryName()}"
th:text="${studentTeam.getGithubRepositoryName()}"></a>
</td>
<td>
<span th:if="${studentTeam.getAssignedTeachingAssistantTeam() == null}">None</span>
<a
th:if="${studentTeam.getAssignedTeachingAssistantTeam() != null}"
th:href="@{/courses/{code}/teaching_assistant_teams/{teamId}
(code=${course.getCode()}, teamId=${studentTeam.getAssignedTeachingAssistantTeam().getId()})}"
th:text="${studentTeam.getAssignedTeachingAssistantTeam().getId()}"
></a>
</td>
</tr>
</table>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/generate_repositories(code=${course.getCode()})}">Generate Repositories</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/export(code=${course.getCode()})}">Export</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/export_contact_info(code=${course.getCode()})}">Export Team Contact Details</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Student Team</title>
</head>
<body>
<div id="content">
<h1>Student Team <span th:text="${student_team.getId()}"></span></h1>
<ul>
<li>
Github Repository:
<a th:href="@{${'https://www.github.com/' + student_team.getCourse().getGithubOrganizationName() + '/' + student_team.getGithubRepositoryName()}}">
<span th:text="${student_team.getGithubRepositoryName()}"></span>
</a>
</li>
<li>
Assigned Teaching Assistant Team:
<a
th:if="${student_team.getAssignedTeachingAssistantTeam() != null}"
th:href="@{/courses/{code}/teaching_assistant_teams/{id}(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
th:text="${student_team.getAssignedTeachingAssistantTeam().getId()}"
></a>
</li>
<li>
Members:
<ul>
<li th:each="student: ${student_team.getStudents()}">
<a th:href="@{/students/{id}(id=${student.getId()})}">
<span th:text="${student.getFullName()}"></span>
</a>
</li>
</ul>
</li>
</ul>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a
th:if="${student_team.getGithubRepositoryName() == null}"
th:href="@{/courses/{code}/student_teams/{id}/generate_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
>Generate Repository</a>
<a
th:if="${student_team.getGithubRepositoryName() != null}"
th:href="@{/courses/{code}/student_teams/{id}/delete_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
>Delete Repository</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Generate Repositories</title>
</head>
<body>
<div id="content">
<h1>Generate Repositories for all Student Teams in <span th:text="${course.getName()}"></span></h1>
<p>
Be careful, this may take a little while...
</p>
<form
method="post"
action="#"
enctype="application/x-www-form-urlencoded"
th:action="@{/courses/{code}/student_teams/generate_repositories(code=${course.getCode()})}"
>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Students</title>
</head>
<body>
<div id="content">
<h1>Students in <span th:text="${course.getName()}"></span></h1>
<table>
<tr th:each="student: ${course.getStudents()}">
<td>
<a th:href="@{/students/{id}(id=${student.getId()})}"><span th:text="${student.getFullName()}"></span></a>
</td>
<td th:text="${student.getStudentNumber()}"></td>
<td th:text="${student.getEmailAddress()}"></td>
</tr>
</table>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/courses/{code}/students/invite_all(code=${course.getCode()})}">Invite All Students To Repository</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Generate Repositories</title>
</head>
<body>
<div id="content">
<h1>Invite All Students in <span th:text="${course.getName()}"></span> to Repository</h1>
<p>
Be careful, this may take a little while...
</p>
<form
method="post"
action="#"
enctype="application/x-www-form-urlencoded"
th:action="@{/courses/{code}/students/invite_all(code=${course.getCode()})}"
>
<label for="repository_name_input">Repository Name:</label>
<input id="repository_name_input" type="text" name="repository_name"/>
<label for="keys_input">Api Keys (separate by newline):</label>
<textarea id="keys_input" name="api_keys"></textarea>
<label for="usernames_input">Usernames (separate by newline):</label>
<textarea id="usernames_input" name="usernames"></textarea>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Teaching Assistant Teams</title>
</head>
<body>
<div id="content">
<h1>Teaching Assistant Teams for <span th:text="${course.getName()}"></span></h1>
<div th:if="${course.getTeachingAssistantTeams().isEmpty()}">
<p>No teaching assistant teams.</p>
</div>
<table>
<tr th:each="taTeam: ${course.getTeachingAssistantTeams()}">
<td>
<a
th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}
(code=${course.getCode()}, team_id=${taTeam.getId()})}">
Teaching Assistant Team <span th:text="${taTeam.getId()}"></span>
</a>
</td>
<td th:each="ta: ${taTeam.getTeachingAssistants()}">
<span th:text="${ta.getFullName()}"></span>
</td>
<td th:text="${taTeam.getGithubTeamName()}"></td>
<td>
<a th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}/delete
(code=${course.getCode()}, team_id=${taTeam.getId()})}">
Delete
</a>
</td>
</tr>
</table>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/courses/{code}/teaching_assistant_teams/create(code=${course.getCode()})}">Create Teaching Assistant Team</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/teaching_assistant_teams/assign_to_student_teams(code=${course.getCode()})}">Assign Teams to Student Teams</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Assign To Student Teams</title>
</head>
<body>
<div id="content">
<form action="#" th:action="@{/courses/{code}/teaching_assistant_teams/assign_to_student_teams(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<label for="seed_input">Random Seed:</label>
<input id="seed_input" type="number" name="seed" value="0" required/>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Create Teaching Assistant Team</title>
</head>
<body>
<div id="content">
<form action="#" th:action="@{/courses/{code}/teaching_assistant_teams(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<label for="ta_team_github_team_name">Github team name:</label>
<input id="ta_team_github_team_name" type="text" name="github_team_name"/>
<label for="ta_team_member_1">Select the first Team Member:</label>
<select id="ta_team_member_1" name="id_1">
<option th:each="ta: ${course.getTeachingAssistants()}" th:value="${ta.getId()}" th:text="${ta.getFullName()}"></option>
</select>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Teaching Assistant Team</title>
</head>
<body>
<div id="content">
<h1>Teaching Assistant Team <span th:text="${teachingAssistantTeam.getId()}"></span> for <span th:text="${course.getName()}"></span></h1>
<ul>
<li>
Github Team Name: <code th:text="${teachingAssistantTeam.getGithubTeamName()}"></code>
</li>
<li>
Teaching Assistants:
<ul>
<li th:each="teachingAssistant: ${teachingAssistantTeam.getTeachingAssistants()}">
<a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}">
<span th:text="${teachingAssistant.getFullName()}"></span>
</a>
</li>
</ul>
</li>
<li>
Assigned Student Teams:
<ul>
<li th:each="studentTeam: ${teachingAssistantTeam.getAssignedStudentTeams()}">
<a th:href="@{/courses/{code}/student_teams/{id}(code=${course.getCode()}, id=${studentTeam.getId()})}">
<span th:text="${studentTeam.getId()}"></span>
</a>
</li>
</ul>
</li>
</ul>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Teaching Assistants</title>
</head>
<body>
<div id="content">
<h1>Teaching Assistants for <span th:text="${course.getName()}"></span></h1>
<table>
<tr th:each="teachingAssistant: ${course.getTeachingAssistants()}">
<td>
<a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a>
</td>
<td th:text="${teachingAssistant.getEmailAddress()}"></td>
<td th:text="${teachingAssistant.getId()}"></td>
</tr>
</table>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/courses/{code}/teaching_assistants/create(code=${course.getCode()})}">Add Teaching Assistant</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Create a Course</title>
</head>
<body>
<div id="content">
<p>
Create your course here
</p>
<form action="#" th:action="@{/courses/{code}/teaching_assistants(code=${course.getCode()})}" th:object="${teachingAssistant}" method="post">
<label for="ta_first_name_input">First Name:</label>
<input id="ta_first_name_input" type="text" th:field="*{firstName}"/>
<label for="ta_last_name_input">Last Name:</label>
<input id="ta_last_name_input" type="text" th:field="*{lastName}"/>
<label for="ta_github_username_input">Github Username:</label>
<input id="ta_github_username_input" type="text" th:field="*{githubUsername}"/>
<label for="ta_email_input">Email Address:</label>
<input id="ta_email_input" type="email" th:field="*{emailAddress}"/>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
<div class="sidebar_block">
block
</div>
</div>
</body>
</html>

View File

@ -13,6 +13,8 @@
<ul class="header_link_list"> <ul class="header_link_list">
<li><a href="/" th:href="@{/}">Home</a> <li><a href="/" th:href="@{/}">Home</a>
<li><a href="/courses" th:href="@{/courses}">Courses</a> <li><a href="/courses" th:href="@{/courses}">Courses</a>
<li><a href="/students" th:href="@{/students}">Students</a></li>
<li><a href="/teaching_assistants" th:href="@{/teaching_assistants}">Teaching Assistants</a></li>
</ul> </ul>
</nav> </nav>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Students</title>
</head>
<body>
<div id="content">
<h1>All Students</h1>
<table>
<tr>
<th>Student</th>
<th>Student Number</th>
<th>Email</th>
<th>Github Username</th>
<th>Teams</th>
</tr>
<tr th:each="student: ${students}">
<td>
<a th:href="@{/students/{id}(id=${student.getId()})}"><span th:text="${student.getFullName()}"></span></a>
</td>
<td th:text="${student.getStudentNumber()}"></td>
<td th:text="${student.getEmailAddress()}"></td>
<td th:text="${student.getGithubUsername()}"></td>
<td>
<div th:each="team: ${student.getTeams()}">
<a
th:href="@{/courses/{code}/student_teams/{id}(code=${team.getCourse().getCode()}, id=${team.getId()})}"
th:text="${'Student Team ' + team.getId()}"
></a>
<span> from </span>
<a
th:href="@{/courses/{code}(code=${team.getCourse().getCode()})}"
th:text="${team.getCourse().getName()}"
></a>
</div>
</td>
</tr>
</table>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Student</title>
</head>
<body>
<div id="content">
<h1>Student: <span th:text="${student.getFullName()}"></span></h1>
<ul>
<li>
Student Number: <code th:text="${student.getStudentNumber()}"></code>
</li>
<li>
Email: <code th:text="${student.getEmailAddress()}"></code>
</li>
<li>
Github Username: <code th:text="${student.getGithubUsername()}"></code>
</li>
<li>
Courses:
<ul>
<li th:each="course: ${student.getCourses()}">
<a th:href="@{/courses/{code}(code=${course.getCode()})}"><span th:text="${course.getName()}"></span></a>
</li>
</ul>
</li>
</ul>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Teaching Assistants</title>
</head>
<body>
<div id="content">
<h1>All Teaching Assistants</h1>
<table>
<tr th:each="teachingAssistant: ${teaching_assistants}">
<td>
<a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a>
</td>
<td th:text="${teachingAssistant.getGithubUsername()}"></td>
<td th:text="${teachingAssistant.getEmailAddress()}"></td>
<td th:text="${teachingAssistant.getId()}"></td>
<td>
<a th:href="@{/teaching_assistants/{id}/delete(id=${teachingAssistant.getId()})}">Delete</a>
</td>
</tr>
</table>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Teaching Assistant</title>
</head>
<body>
<div id="content">
<h1>Teaching Assistant: <span th:text="${teachingAssistant.getFullName()}"></span></h1>
<ul>
<li>
Email: <code th:text="${teachingAssistant.getEmailAddress()}"></code>
</li>
<li>
Github Username: <code th:text="${teachingAssistant.getGithubUsername()}"></code>
</li>
<li>
Courses:
<ul>
<li th:each="course: ${teachingAssistant.getCourses()}">
<a th:href="@{/courses/{code}(code=${course.getCode()})}"><span th:text="${course.getName()}"></span></a>
</li>
</ul>
</li>
</ul>
</div>
<div id="sidebar">
</div>
</body>
</html>