Merge pull request #3 from andrewlalis/data_import

Merge current working branch to master
This commit is contained in:
Andrew Lalis 2019-08-28 09:46:11 +02:00 committed by GitHub
commit 2a205b5dc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 1711 additions and 796 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/*
*.iml
target/*
javadoc/*

105
design/TAA.svg Normal file
View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
height="64"
viewBox="0 0 64.000001 64.000001"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="TAA.svg"
inkscape:export-filename="/home/andrew/Programming/Projects/Teaching_Assistant_Assistant/src/main/resources/static/images/logo.png"
inkscape:export-xdpi="360"
inkscape:export-ydpi="360">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="24.365199"
inkscape:cy="32.206088"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1030"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-988.36216)">
<rect
style="opacity:1;fill:#008000;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4136"
width="60"
height="60"
x="1"
y="991.36218"
ry="16" />
<flowRoot
xml:space="preserve"
id="flowRoot4138"
style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="matrix(2.0532963,0,0,2.0532963,-37.008,965.44742)"><flowRegion
id="flowRegion4140"><rect
id="rect4142"
width="18.125"
height="26.249994"
x="27.5"
y="9.8928566" /></flowRegion><flowPara
id="flowPara4144"
style="fill:#ffffff;fill-opacity:1">T</flowPara></flowRoot> <flowRoot
xml:space="preserve"
id="flowRoot4154"
style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="matrix(1.3900147,0,0,1.3900147,-2.1586653,998.49896)"><flowRegion
id="flowRegion4156"><rect
id="rect4158"
width="18.125"
height="26.249994"
x="27.5"
y="9.8928566" /></flowRegion><flowPara
id="flowPara4160"
style="fill:#ffffff;fill-opacity:1">A</flowPara></flowRoot> <flowRoot
transform="matrix(1.3900147,0,0,1.3900147,-29.448313,998.49896)"
style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="flowRoot4162"
xml:space="preserve"><flowRegion
id="flowRegion4164"><rect
y="9.8928566"
x="27.5"
height="26.249994"
width="18.125"
id="rect4166" /></flowRegion><flowPara
style="fill:#ffffff;fill-opacity:1"
id="flowPara4168">A</flowPara></flowRoot> </g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,25 +1,13 @@
package nl.andrewlalis.teaching_assistant_assistant;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.PersonRepository;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeamRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootApplication
public class TeachingAssistantAssistantApplication implements CommandLineRunner {
@Autowired
CourseRepository courseRepository;
@Autowired
TeamRepository teamRepository;
@Autowired
PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(TeachingAssistantAssistantApplication.class, args);
}
@ -27,5 +15,6 @@ public class TeachingAssistantAssistantApplication implements CommandLineRunner
@Override
public void run(String... args) throws Exception {
System.out.println("Running startup...");
System.out.println(new BCryptPasswordEncoder().encode("test"));
}
}

View File

@ -0,0 +1,83 @@
package nl.andrewlalis.teaching_assistant_assistant.config;
import nl.andrewlalis.teaching_assistant_assistant.model.security.UserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Security configuration for the TAA application.
*
* This configuration makes use of the custom user details service provided by the application for database-persistent
* user accounts.
*
* Login, logout, and registration pages are set so that all users, authenticated and unauthenticated, may access them,
* while actual site content is only visible to authenticated users.
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
protected WebSecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable() // So that we can GET the logout page.
.authorizeRequests() // Let anyone view the login and logout pages, as well as various registration pages.
.antMatchers("/login*", "/logout*", "/register*", "/register/**")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/css/**", "/images/**", "/javascript/**")
.permitAll()
.and()
.authorizeRequests() // Only logged in users should be able to see site content.
.antMatchers("/**").hasRole("user")
.anyRequest().hasRole("user")
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.loginProcessingUrl("/login")
.defaultSuccessUrl("/", true)
.failureUrl("/login?error")
.and()
.logout()
.permitAll()
.clearAuthentication(true)
.invalidateHttpSession(true)
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.deleteCookies("JSESSIONID");
}
/**
* Configures Spring Security to use a specific password encoder.
* @return The password encoder to use.
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -12,11 +12,11 @@ import org.springframework.web.bind.annotation.PostMapping;
* Controller for the list of courses in the system.
*/
@Controller
public class Courses {
public class CoursesController extends UserPageController {
private CourseRepository courseRepository;
protected Courses(CourseRepository courseRepository) {
protected CoursesController(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}

View File

@ -0,0 +1,20 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Controller for the login page, visible to all users.
*/
@Controller
public class LoginController {
private final Logger logger = LogManager.getLogger(LoginController.class);
@GetMapping("/login")
public String get() {
return "login";
}
}

View File

@ -0,0 +1,48 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers;
import nl.andrewlalis.teaching_assistant_assistant.model.dto.NewUserRegistrationDTO;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.PostMapping;
import java.util.Arrays;
/**
* Controller for the registration page which is used for the creation of new accounts.
*/
@Controller
public class RegisterController {
private final Logger logger = LogManager.getLogger(RegisterController.class);
private CourseRepository courseRepository;
private static final String[] personTypes = {
"Student",
"Teaching Assistant",
"Professor",
"Administrator"
};
protected RegisterController(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
@GetMapping("/register")
public String get(Model model) {
model.addAttribute("newUserRegistration", new NewUserRegistrationDTO(Arrays.asList(personTypes), this.courseRepository.findAll()));
return "register";
}
@PostMapping("/register")
public String post(@ModelAttribute NewUserRegistrationDTO newUserRegistration) {
logger.info("Received new registration: " + newUserRegistration);
return "redirect:/login";
}
}

View File

@ -2,18 +2,13 @@ package nl.andrewlalis.teaching_assistant_assistant.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class RootController {
public class RootController extends UserPageController {
@RequestMapping(
path = "/",
produces = "text/html"
)
@GetMapping("/")
public String index(Model model) {
model.addAttribute("name", "JOHN");
return "index.html";
return "index";
}
}

View File

@ -0,0 +1,30 @@
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 for operations dealing with the global collection of students, not particular to one course.
*/
@Controller
public class StudentsController extends UserPageController {
private StudentRepository studentRepository;
protected StudentsController(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
/**
* Gets a list of all students.
* @param model The view model.
* @return The template for displaying a list of students.
*/
@GetMapping("/students")
public String get(Model model) {
model.addAttribute("students", this.studentRepository.findAll());
return "students";
}
}

View File

@ -13,13 +13,16 @@ import org.springframework.web.bind.annotation.PostMapping;
import java.util.Optional;
/**
* Controller for the list of teaching assistants in the system.
*/
@Controller
public class TeachingAssistants {
public class TeachingAssistantsController extends UserPageController {
private TeachingAssistantRepository teachingAssistantRepository;
private CourseRepository courseRepository;
protected TeachingAssistants(TeachingAssistantRepository teachingAssistantRepository, CourseRepository courseRepository) {
protected TeachingAssistantsController(TeachingAssistantRepository teachingAssistantRepository, CourseRepository courseRepository) {
this.teachingAssistantRepository = teachingAssistantRepository;
this.courseRepository = courseRepository;
}

View File

@ -1,17 +0,0 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class TestController {
@GetMapping("/test")
public String test (@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "test";
}
}

View File

@ -0,0 +1,23 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers;
import nl.andrewlalis.teaching_assistant_assistant.model.security.User;
import nl.andrewlalis.teaching_assistant_assistant.model.security.UserDetails;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.ModelAttribute;
/**
* An abstract controller which simplifies the definition of controllers for pages that require a user to be signed in.
*/
public abstract class UserPageController {
/**
* A shortcut to get the current authenticated user.
* @param auth The spring authentication.
* @return The user that's logged in.
*/
@ModelAttribute("user")
protected User getUser(Authentication auth) {
UserDetails userDetails = (UserDetails) auth.getPrincipal();
return userDetails.getUser();
}
}

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import org.springframework.stereotype.Controller;
@ -13,7 +14,7 @@ import java.util.Optional;
* Controller for the course entity, that is, one individual course.
*/
@Controller
public class CourseEntity {
public class CourseEntity extends UserPageController {
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import org.springframework.stereotype.Controller;
@ -7,7 +8,7 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class CreateCourse {
public class CreateCourse extends UserPageController {
private CourseRepository courseRepository;
@ -17,7 +18,7 @@ public class CreateCourse {
@GetMapping("/courses/create")
public String get(Model model) {
Course course = new Course("no name", "no code");
Course course = new Course(null, null);
model.addAttribute("course", course);
return "courses/create";
}

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import nl.andrewlalis.teaching_assistant_assistant.util.sample_data.CourseGenerator;
@ -10,7 +11,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class Generate {
public class Generate extends UserPageController {
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -22,7 +23,7 @@ import java.util.Optional;
* Controller for importing students from a CSV sheet.
*/
@Controller
public class ImportStudents {
public class ImportStudents extends UserPageController {
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -18,7 +19,7 @@ import java.util.Optional;
* Controller for exporting team information into readable files.
*/
@Controller
public class ExportStudentTeams {
public class ExportStudentTeams extends UserPageController {
private CourseRepository courseRepository;
@ -79,10 +80,17 @@ public class ExportStudentTeams {
private byte[] getContactInfo(Course course) {
StringBuilder sb = new StringBuilder("Student Team Contact Details\n");
sb.append("--------------------------------------------\n");
sb.append("Student Number, Name, Email, Github Username\n");
sb.append("--------------------------------------------\n");
for (StudentTeam team : course.getStudentTeams()) {
sb.append("2019_Team_").append(team.getId()).append(": ");
sb.append("Team ").append(team.getId()).append(":\n");
for (Student student : team.getStudents()) {
sb.append(student.getFullName()).append(" (").append(student.getEmailAddress()).append("), ");
sb.append("\t - s").append(student.getStudentNumber())
.append(", ").append(student.getFullName())
.append(", ").append(student.getEmailAddress())
.append(", ").append(student.getGithubUsername())
.append('\n');
}
sb.append("\n");
}

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -15,7 +16,7 @@ import java.io.IOException;
import java.util.Optional;
@Controller
public class GenerateRepositories {
public class GenerateRepositories extends UserPageController {
private CourseRepository courseRepository;
private StudentTeamRepository studentTeamRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -20,7 +21,7 @@ import java.util.Optional;
* TODO: Implement this functionality automatically.
*/
@Controller
public class MergeSingleTeams {
public class MergeSingleTeams extends UserPageController {
private Logger logger = LogManager.getLogger(MergeSingleTeams.class);

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -20,7 +21,7 @@ import java.util.List;
import java.util.Optional;
@Controller
public class StudentTeamEntity {
public class StudentTeamEntity extends UserPageController {
private StudentTeamRepository studentTeamRepository;
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -15,7 +16,7 @@ import java.util.Optional;
* Updates branch protection for all student repositories in a given course.
*/
@Controller
public class UpdateBranchProtection {
public class UpdateBranchProtection extends UserPageController {
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.students;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -17,7 +18,7 @@ import java.util.List;
import java.util.Optional;
@Controller
public class InviteAllToRepository {
public class InviteAllToRepository extends UserPageController {
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -14,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.*;
@Controller
public class AssignToStudentTeams {
public class AssignToStudentTeams extends UserPageController {
private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -15,7 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.Optional;
@Controller
public class CreateTeachingAssistantTeam {
public class CreateTeachingAssistantTeam extends UserPageController {
private CourseRepository courseRepository;
private TeachingAssistantRepository teachingAssistantRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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;
@ -12,7 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import java.util.Optional;
@Controller
public class TeachingAssistantTeamEntity {
public class TeachingAssistantTeamEntity extends UserPageController {
private CourseRepository courseRepository;
private TeachingAssistantTeamRepository teachingAssistantTeamRepository;

View File

@ -0,0 +1,10 @@
/**
* Contains all page and action controllers which respond to requests on the website.
*
* <p>
* Each class defined here should therefore be annotated with Spring's <code>@Controller</code> annotation, and
* define one or more methods annotated with a <code>@RequestMapping</code> (or any convenience shortcut) to respond
* to requests to the controller.
* </p>
*/
package nl.andrewlalis.teaching_assistant_assistant.controllers;

View File

@ -0,0 +1,19 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.register;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Controller for the registration form for new students.
*/
@Controller
public class StudentRegisterController {
@GetMapping("/register/student")
public String get() {
return "/register/student";
}
}

View File

@ -1,9 +1,10 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers;
package nl.andrewlalis.teaching_assistant_assistant.controllers.students;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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.model.repositories.StudentRepository;
import nl.andrewlalis.teaching_assistant_assistant.services.StudentService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@ -14,30 +15,28 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.Optional;
/**
* Controller for operations dealing with the global collection of students, not particular to one course.
* Controller for creating a new student.
*/
@Controller
public class Students {
public class StudentCreateController extends UserPageController {
/**
* A constant which defines what value is returned if the user says that the newly created student should not be
* part of a course.
*/
private static final String NO_COURSE = "NO_COURSE_SELECTED";
private StudentRepository studentRepository;
private CourseRepository courseRepository;
private StudentService studentService;
protected Students(StudentRepository studentRepository, CourseRepository courseRepository) {
this.studentRepository = studentRepository;
protected StudentCreateController(CourseRepository courseRepository, StudentService studentService) {
this.courseRepository = courseRepository;
}
@GetMapping("/students")
public String get(Model model) {
model.addAttribute("students", this.studentRepository.findAll());
return "students";
this.studentService = studentService;
}
@GetMapping("/students/create")
public String getCreate(Model model) {
model.addAttribute("student", new Student("First Name", "Last Name", "Email Address", "Github Username", 1234567));
model.addAttribute("student", new Student(null, null, null, null, 1234567));
model.addAttribute("courses", this.courseRepository.findAll());
return "students/create";
@ -49,20 +48,16 @@ public class Students {
)
public String postCreate(
@ModelAttribute Student newStudent,
@RequestParam(value = "course_code", required = false) String courseCode
@RequestParam(value = "course_code", required = false, defaultValue = NO_COURSE) String courseCode
) {
this.studentRepository.save(newStudent);
if (courseCode != null && !courseCode.equals(NO_COURSE)) {
Optional<Course> optionalCourse = this.courseRepository.findByCode(courseCode);
optionalCourse.ifPresent(course -> {
course.addParticipant(newStudent);
newStudent.assignToCourse(course);
this.courseRepository.save(course);
this.studentRepository.save(newStudent);
});
Course course = null;
if (optionalCourse.isPresent()) {
course = optionalCourse.get();
}
this.studentService.createStudent(newStudent, course);
return "redirect:/students";
}
}

View File

@ -1,85 +0,0 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.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.people.teams.Team;
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.TeamRepository;
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 StudentEntity {
private StudentRepository studentRepository;
private TeamRepository teamRepository;
private CourseRepository courseRepository;
protected StudentEntity(StudentRepository studentRepository, TeamRepository teamRepository, CourseRepository courseRepository) {
this.studentRepository = studentRepository;
this.teamRepository = teamRepository;
this.courseRepository = courseRepository;
}
@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";
}
@GetMapping("/students/{id}/edit")
public String getEdit(@PathVariable long id, Model model) {
Optional<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> model.addAttribute("student", student));
return "students/entity/edit";
}
@PostMapping(
value = "/students/{id}/edit",
consumes = "application/x-www-form-urlencoded"
)
public String post(@ModelAttribute Student editedStudent, @PathVariable long id) {
Optional<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> {
student.setFirstName(editedStudent.getFirstName());
student.setLastName(editedStudent.getLastName());
student.setEmailAddress(editedStudent.getEmailAddress());
student.setGithubUsername(editedStudent.getGithubUsername());
student.setStudentNumber(editedStudent.getStudentNumber());
this.studentRepository.save(student);
});
return "redirect:/students/{id}";
}
@GetMapping("/students/{id}/remove")
public String getRemove(@PathVariable long id) {
Optional<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> {
for (Team team : student.getTeams()) {
team.removeMember(student);
student.removeFromAssignedTeam(team);
this.teamRepository.save(team);
}
for (Course course : student.getCourses()) {
course.removeParticipant(student);
student.removeFromAssignedCourse(course);
this.courseRepository.save(course);
}
this.studentRepository.delete(student);
});
return "redirect:/students";
}
}

View File

@ -0,0 +1,31 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.students;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
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 for a single student entity.
*/
@Controller
public class StudentEntityController extends UserPageController {
private StudentRepository studentRepository;
protected StudentEntityController(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,59 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.students.entity;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentRepository;
import nl.andrewlalis.teaching_assistant_assistant.services.StudentService;
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 for editing a student entity.
*/
@Controller("/students/{id}/edit")
public class StudentEntityEditController extends UserPageController {
private StudentRepository studentRepository;
private StudentService studentService;
protected StudentEntityEditController(StudentRepository studentRepository, StudentService studentService) {
this.studentRepository = studentRepository;
this.studentService = studentService;
}
/**
* Get the data of the student whose information is going to be edited, and add that to the model to be rendered.
* @param id The id of the student to edit.
* @param model The view model.
* @return The edit template which will be populated with the student's data.
*/
@GetMapping("/students/{id}/edit")
public String getEdit(@PathVariable long id, Model model) {
Optional<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> model.addAttribute("student", student));
return "students/entity/edit";
}
/**
* Receives edited data about a student and saves it.
* @param editedStudent A temporary <code>Student</code> object containing the edited information.
* @param id The id of the student to edit the information of.
* @return A redirect to the entity page for the student whose information was just edited.
*/
@PostMapping(
value = "/students/{id}/edit",
consumes = "application/x-www-form-urlencoded"
)
public String post(@ModelAttribute Student editedStudent, @PathVariable long id) {
Optional<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> this.studentService.editStudent(student, editedStudent));
return "redirect:/students/{id}";
}
}

View File

@ -0,0 +1,34 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.students.entity;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentRepository;
import nl.andrewlalis.teaching_assistant_assistant.services.StudentService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Optional;
/**
* Controller for removing a student from the application.
*/
@Controller
public class StudentEntityRemoveController {
private StudentService studentService;
private StudentRepository studentRepository;
protected StudentEntityRemoveController(StudentService studentService, StudentRepository studentRepository) {
this.studentService = studentService;
this.studentRepository = studentRepository;
}
@GetMapping("/students/{id}/remove")
public String getRemove(@PathVariable long id) {
Optional<Student> optionalStudent = this.studentRepository.findById(id);
optionalStudent.ifPresent(student -> this.studentService.removeStudent(student));
return "redirect:/students";
}
}

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.teaching_assistants;
import nl.andrewlalis.teaching_assistant_assistant.controllers.UserPageController;
import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantRepository;
import org.springframework.stereotype.Controller;
@ -9,12 +10,15 @@ import org.springframework.web.bind.annotation.PathVariable;
import java.util.Optional;
/**
* Controller for a single teaching assistant entity.
*/
@Controller
public class TeachingAssistantEntity {
public class TeachingAssistantEntityController extends UserPageController {
private TeachingAssistantRepository teachingAssistantRepository;
protected TeachingAssistantEntity(TeachingAssistantRepository teachingAssistantRepository) {
protected TeachingAssistantEntityController(TeachingAssistantRepository teachingAssistantRepository) {
this.teachingAssistantRepository = teachingAssistantRepository;
}

View File

@ -6,23 +6,39 @@ import javax.persistence.*;
import java.util.Date;
/**
* The basic entity properties which any entity in this system should have: an id and a creation timestamp. Every entity
* in this system should extend from BasicEntity.
* An abstract object from which all persistent objects in this application should extend.
* <p>
* The basic entity properties which any entity in this system should have: an {@code id} and a creation timestamp.
* Every entity in this system should extend from BasicEntity.
* </p>
*
* <p>
* Every single entity in this system therefore is identified uniquely by a Long primary key, although it may also
* be identified by other codes or attributes defined in such an entity. For example, the {@link Course} object has
* a unique code, which can also be used to select a course.
* </p>
*
*/
@MappedSuperclass
public abstract class BasicEntity {
/**
* The primary key for any basic entity.
*/
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
)
private Long id;
/**
* The date at which this basic entity was created.
*/
@Temporal(
value = TemporalType.TIMESTAMP
)
@CreationTimestamp
@Column
@Column(nullable = false)
private Date createdOn;
protected BasicEntity() {}

View File

@ -0,0 +1,95 @@
package nl.andrewlalis.teaching_assistant_assistant.model.dto;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import java.util.List;
/**
* A data transfer object to aid in the registration of a new authenticated user. This object therefore contains all the
* data that potential users enter prior to the actual creation of a new user.
*/
public class NewUserRegistrationDTO {
private String firstName;
private String lastName;
private String emailAddress;
private String username;
private String password;
private String passwordConfirm;
private String selectedPersonType;
private List<String> availablePersonTypes;
private Iterable<Course> availableCourses;
public NewUserRegistrationDTO(List<String> availablePersonTypes, Iterable<Course> availableCourses) {
this.availablePersonTypes = availablePersonTypes;
this.availableCourses = availableCourses;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public List<String> getAvailablePersonTypes() {
return availablePersonTypes;
}
public Iterable<Course> getAvailableCourses() {
return availableCourses;
}
public String getSelectedPersonType() {
return this.selectedPersonType;
}
}

View File

@ -0,0 +1,14 @@
/**
* Contains all persistent entities that make up the core data model for the application.
*
* <p>
* All model objects should ultimately extend from {@link nl.andrewlalis.teaching_assistant_assistant.model.BasicEntity}
* and define new attributes as necessary for use.
* </p>
*
* <p>
* From BasicEntity it is implied that all entities contained herein can be identified uniquely by at least a
* <code>Long id</code>.
* </p>
*/
package nl.andrewlalis.teaching_assistant_assistant.model;

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.teaching_assistant_assistant.model.people;
import nl.andrewlalis.teaching_assistant_assistant.model.BasicEntity;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.Team;
import nl.andrewlalis.teaching_assistant_assistant.model.security.User;
import javax.persistence.*;
import java.util.ArrayList;
@ -10,6 +11,11 @@ import java.util.List;
/**
* Represents any person (teaching assistant, student, or other) that exists in this application.
*
* <p>
* A Person may belong to many {@link Team}s and may also belong to many {@link Course}s, irrespective of Team
* involvement.
* </p>
*/
@Entity
@Table(name = "people")
@ -18,15 +24,27 @@ import java.util.List;
)
public abstract class Person extends BasicEntity {
/**
* The person's first name.
*/
@Column(nullable = false)
private String firstName;
/**
* The person's last name.
*/
@Column(nullable = false)
private String lastName;
/**
* The person's email address.
*/
@Column
private String emailAddress;
/**
* The person's github username.
*/
@Column
private String githubUsername;
@ -51,6 +69,16 @@ public abstract class Person extends BasicEntity {
)
private List<Course> courses;
/**
* The authenticated user belonging to this person.
*/
@OneToOne(
fetch = FetchType.LAZY,
optional = true,
mappedBy = "person"
)
private User user;
/**
* Default constructor for JPA.
*/
@ -74,12 +102,20 @@ public abstract class Person extends BasicEntity {
this.githubUsername = githubUsername;
}
/**
* Assigns this person to a team.
* @param team The team to assign to.
*/
public void assignToTeam(Team team) {
if (!this.teams.contains(team)) {
this.teams.add(team);
}
}
/**
* Removes this person from a team.
* @param team The team to remove from.
*/
public void removeFromAssignedTeam(Team team) {
this.teams.remove(team);
}
@ -142,6 +178,14 @@ public abstract class Person extends BasicEntity {
return this.teams;
}
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
/**
* 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.

View File

@ -9,9 +9,11 @@ import java.util.ArrayList;
import java.util.List;
/**
* A group consisting of one or more members. Child classes should define P as a sub class of Person to define custom
* behavior if needed.
* @param <P> The type of members this group contains.
* A group consisting of one or more members that belongs to a {@link Course}.
*
* <p>
* Any Team contains a collection of {@link Person} objects, each representing a member of the team.
* </p>
*/
@Entity
@Inheritance(

View File

@ -0,0 +1,18 @@
package nl.andrewlalis.teaching_assistant_assistant.model.repositories;
import nl.andrewlalis.teaching_assistant_assistant.model.security.Role;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
/**
* The repository for all roles to which users may be assigned.
*/
public interface RoleRepository extends CrudRepository<Role, Long> {
/**
* Try to find a role by the given name.
* @return An optional Role that has the given name.
*/
public Optional<Role> findByName(String name);
}

View File

@ -0,0 +1,10 @@
package nl.andrewlalis.teaching_assistant_assistant.model.repositories;
import nl.andrewlalis.teaching_assistant_assistant.model.security.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}

View File

@ -0,0 +1,5 @@
/**
* A collection of {@link org.springframework.data.repository.CrudRepository} repositories that are used to access key
* components of the {@code nl.andrewlalis.teaching_assistant_assistant.model} package.
*/
package nl.andrewlalis.teaching_assistant_assistant.model.repositories;

View File

@ -0,0 +1,43 @@
package nl.andrewlalis.teaching_assistant_assistant.model.security;
import nl.andrewlalis.teaching_assistant_assistant.model.BasicEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import java.util.List;
/**
* Represents a role that a user has, which gives the user access to certain resources.
*/
@Entity
public class Role extends BasicEntity {
/**
* The name of this role.
*/
@Column(nullable = false, unique = true)
private String name;
/**
* The list of users with this role.
*/
@ManyToMany(mappedBy = "roles")
private List<User> users;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}

View File

@ -0,0 +1,92 @@
package nl.andrewlalis.teaching_assistant_assistant.model.security;
import nl.andrewlalis.teaching_assistant_assistant.model.BasicEntity;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Person;
import javax.persistence.*;
import java.util.List;
/**
* Represents a user of the website with some credentials.
*/
@Entity
public class User extends BasicEntity {
/**
* A unique username for the user.
*/
@Column(nullable = false, unique = true)
private String username;
/**
* The password for this user.
*/
@Column(nullable = false)
private String password;
/**
* Whether or not this user has been activated.
*/
@Column(nullable = false)
private boolean activated = false;
/**
* Whether or not this user has been locked (no more access).
*/
@Column(nullable = false)
private boolean locked = false;
@ManyToMany(
fetch = FetchType.EAGER
)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")
)
private List<Role> roles;
@OneToOne
@JoinColumn(
name = "person_id",
nullable = true,
referencedColumnName = "id"
)
private Person person;
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public Person getPerson() {
return this.person;
}
public boolean isActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}

View File

@ -0,0 +1,64 @@
package nl.andrewlalis.teaching_assistant_assistant.model.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* TAA-specific implementation of Spring's UserDetails interface to supply user authentication data.
*/
public class UserDetails implements org.springframework.security.core.userdetails.UserDetails {
private User user;
protected UserDetails(User user) {
this.user = user;
}
public User getUser() {
return this.user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<Role> roles = this.user.getRoles();
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return this.user.getPassword();
}
@Override
public String getUsername() {
return this.user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !this.user.isLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.user.isActivated();
}
}

View File

@ -0,0 +1,29 @@
package nl.andrewlalis.teaching_assistant_assistant.model.security;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
* A custom user details service to supply database persistent user information to Spring Security.
*/
@Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
private UserRepository userRepository;
protected UserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> optionalUser = this.userRepository.findByUsername(username);
optionalUser.orElseThrow(() -> new UsernameNotFoundException("Username not found."));
return new nl.andrewlalis.teaching_assistant_assistant.model.security.UserDetails(optionalUser.get());
}
}

View File

@ -0,0 +1,79 @@
package nl.andrewlalis.teaching_assistant_assistant.services;
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.Team;
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.TeamRepository;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Helps with manipulation and various operations on individual students or groups of students (not teams).
*/
@Service
public class StudentService {
private StudentRepository studentRepository;
private CourseRepository courseRepository;
private TeamRepository teamRepository;
protected StudentService (StudentRepository studentRepository, CourseRepository courseRepository, TeamRepository teamRepository) {
this.studentRepository = studentRepository;
this.courseRepository = courseRepository;
this.teamRepository = teamRepository;
}
/**
* Creates a new student and assigns them to a course, if provided.
* @param student An unsaved student model.
* @param course The course to assign the student to. This may be null.
*/
public void createStudent(Student student, Course course) {
if (course != null) {
course.addParticipant(student);
student.assignToCourse(course);
this.courseRepository.save(course); // This cascades to save the student as well.
}
}
/**
* Edits a students' data. More specifically, updates the provided <code>student</code> with the attributes found
* in the provided <code>editedStudent</code> object.
* @param student The student to update.
* @param editedStudent A model containing updated attributes to assign to the given student.
*/
public void editStudent(Student student, Student editedStudent) {
student.setFirstName(editedStudent.getFirstName());
student.setLastName(editedStudent.getLastName());
student.setStudentNumber(editedStudent.getStudentNumber());
student.setEmailAddress(editedStudent.getEmailAddress());
student.setGithubUsername(editedStudent.getGithubUsername());
this.studentRepository.save(student);
}
/**
* Removes a student from the application completely.
* @param student The student to remove.
*/
public void removeStudent(Student student) {
List<Team> teams = student.getTeams();
for (Team team : teams) {
team.removeMember(student);
student.removeFromAssignedTeam(team);
this.teamRepository.save(team);
}
List<Course> courses = student.getCourses();
for (Course course : courses) {
course.removeParticipant(student);
student.removeFromAssignedCourse(course);
this.courseRepository.save(course);
}
this.studentRepository.delete(student);
}
}

View File

@ -50,7 +50,7 @@ public class StudentTeamService {
StudentTeam newTeam = new StudentTeam(course);
course.addStudentTeam(newTeam);
this.courseRepository.save(course);
logger.info("Created new team: " + newTeam.getId());
logger.info("Created new team.");
return newTeam;
}
@ -185,13 +185,19 @@ public class StudentTeamService {
s.removeFromAssignedTeam(team);
team.removeMember(s);
this.studentRepository.save(s);
logger.debug("Removed student " + s.getFullName() + " from team " + team.getId());
}
// Remove the TA team assignment.
TeachingAssistantTeam teachingAssistantTeam = team.getAssignedTeachingAssistantTeam();
if (teachingAssistantTeam != null) {
teachingAssistantTeam.removeAssignedStudentTeam(team);
team.setAssignedTeachingAssistantTeam(null);
this.teachingAssistantTeamRepository.save(teachingAssistantTeam);
logger.debug("Removed team " + team.getId() + " from Teaching Assistant Team " + teachingAssistantTeam.getId() + " assigned teams list.");
}
team.setAssignedTeachingAssistantTeam(null);
// Remove the repository from the course and delete it.
course.removeStudentTeam(team);

File diff suppressed because one or more lines are too long

View File

@ -1,17 +1,9 @@
.footer_bar {
background-color: darkslategray;
color: whitesmoke;
height: 30px;
padding-top: 20px;
padding-bottom: 20px;
position: fixed;
width: 100%;
bottom: 0;
z-index: -1; /* Make this footer appear above all other things. */
body {
padding-bottom: 110px;
}
.third {
width: 33%;
display: inline-block;
text-align: center;
.footer {
background-color: darkslategray;
color: whitesmoke;
height: 100px;
}

View File

@ -1,45 +1,13 @@
body {
padding: 0;
margin: 0;
background-color: whitesmoke;
}
.header_bar {
.header {
background-color: green;
width: 100%;
color: whitesmoke;
padding-bottom: 20px;
padding-top: 20px;
height: 30px;
}
.header_title {
margin-top: auto;
margin-bottom: auto;
width: 25%;
display: inline;
font-size: 30px;
}
.header_link_list {
list-style-type: none;
margin: 0;
padding: 0;
width: 75%;
display: inline;
}
.header_link_list li {
display: inline-block;
}
.header_link_list a {
.header a {
text-decoration: none;
color: inherit;
background-color: darkgreen;
padding: 10px;
}
.header_link_list a:hover {
background-color: darkgray;
.header a:hover {
color: lightgray;
}

View File

@ -0,0 +1,35 @@
body {
text-align: center;
}
.page_row {
text-align: center;
}
.login_form {
background-color: green;
color: white;
width: 50%;
text-align: center;
margin-left: auto;
margin-right: auto;
padding: 10px;
}
.login_form_row {
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
.login_form label {
font-size: 18px;
}
.login_form input {
font-family: inherit;
}
.login_form input[type=submit]:hover {
background-color: lightgray;
}

View File

@ -1,52 +1,7 @@
/* Set the font for the whole website here. */
body {
font-family: sans-serif;
}
/*.btn-primary {*/
/* background-color: green;*/
/*}*/
.content_container {
width: 50%;
margin-left: auto;
margin-right: auto;
text-align: left;
font-size: 16px;
color: black;
}
.sidebar_container {
width: 20%;
height: 100%;
position: fixed;
top: 70px;
right: 0;
}
.sidebar_block {
margin: 20px 0 20px 0;
padding: 20px;
background-color: green;
color: whitesmoke;
}
.sidebar_block a {
text-decoration: underline;
color: inherit;
}
.sidebar_block a:hover {
color: lightgreen;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
.page_row {
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
label {
font-weight: bold;
}
/*.btn-primary:hover {*/
/* background-color: darkgreen;*/
/*}*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,39 +1,27 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Courses</title>
</head>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Courses', content=~{::#content}, actions=~{::#actions})}" lang="en">
<body>
<div id="content">
<h1>Courses</h1>
<hr>
<ul>
<li th:each="course: ${courses}">
<h2><a th:href="@{/courses/{code}(code=${course.getCode()})}" th:text="${course.getName()}"></a></h2>
<ul>
<li>Code: <span th:text="${course.getCode()}"></span></li>
<li>Created on: <span th:text="${course.getCreatedOn()}"></span></li>
<li>Students: <span th:text="${course.getStudents().size()}"></span></li>
<li>Teaching Assistants: <span th:text="${course.getTeachingAssistants().size()}"></span></li>
<li>Student Teams: <span th:text="${course.getStudentTeams().size()}"></span></li>
<li>Teaching Assistant Teams: <span th:text="${course.getTeachingAssistantTeams().size()}"></span></li>
<li>Number of Active Students (in a team): <span th:text="${course.getNumberOfStudentsInTeams()}"></span></li>
</ul>
</li>
</ul>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/courses/create}">Create New Course</a>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a href="/courses/create">Create new course</a>
<div id="content" class="container-fluid">
<div class="col-sm-12 col-md-6 offset-3">
<div class="list-group">
<a th:each="course: ${courses}" class="list-group-item list-group-item-action flex-column align-items-start" th:href="@{/courses/{code}(code=${course.getCode()})}">
<div class="d-flex w-100 justify-content-between">
<h4 class="mb-1" th:text="${course.getName()}"></h4>
<small th:text="${course.getCode()}"></small>
</div>
<div class="sidebar_block">
Do something else
<ul>
<li><span class="badge badge-primary" th:text="${course.getStudentTeams().size()}"></span> Student Teams</li>
<li><span class="badge badge-primary" th:text="${course.getNumberOfStudentsInTeams()}"></span> Active Students</li>
<li><span class="badge badge-primary" th:text="${course.getTeachingAssistants().size()}"></span> Teaching Assistants</li>
</ul>
<small>Created on <span th:text="${course.getCreatedOn()}"></span></small>
</a>
</div>
<div class="sidebar_block">
Click this link!
</div>
</div>

View File

@ -1,43 +1,35 @@
<!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>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Create New Course', content=~{::#content})}" lang="en">
<body>
<div id="content">
<h1>Create New Course</h1>
<form action="#" th:action="@{/courses}" th:object="${course}" method="post">
<div class="page_row">
<label for="course_name_input">Name:</label>
<input id="course_name_input" type="text" th:field="*{name}" required/>
<div id="content" class="row justify-content-center">
<form th:action="@{/courses}" th:object="${course}" method="post">
<div class="form-group">
<label for="course_name_input">Name</label>
<input id="course_name_input" type="text" class="form-control" th:field="*{name}" required/>
</div>
<div class="page_row">
<label for="course_code_input">Code:</label>
<input id="course_code_input" type="text" th:field="*{code}" required/>
<div class="form-group">
<label for="course_code_input">Code</label>
<input id="course_code_input" type="text" class="form-control" th:field="*{code}" required/>
<small class="form-text text-muted">Unique identifying code for this course.</small>
</div>
<div class="page_row">
<label for="course_github_organization_name_input">Github Organization:</label>
<input id="course_github_organization_name_input" type="text" th:field="*{githubOrganizationName}" required/>
<div class="form-group">
<label for="course_github_organization_name_input">Github Organization</label>
<input id="course_github_organization_name_input" type="text" class="form-control" th:field="*{githubOrganizationName}" required/>
</div>
<div class="page_row">
<label for="course_github_api_key_input">Github API Key:</label>
<input id="course_github_api_key_input" type="text" th:field="*{apiKey}" required/>
<div class="form-group">
<label for="course_github_api_key_input">Github API Key</label>
<input id="course_github_api_key_input" type="text" class="form-control" th:field="*{apiKey}" required/>
<small class="form-text text-muted">Needed to manage student repositories.</small>
</div>
<div class="page_row">
<button type="submit">Submit</button>
</div>
<input class="btn btn-primary" type="submit" value="Create"/>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -1,40 +1,35 @@
<!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 th:text="${course.getName()}"></title>
</head>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title=${course.getName()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1><span th:text="${course.getName()}"></span> (Code: <span th:text="${course.getCode()}"></span>)</h1>
<hr>
<ul>
<li>
Github Organization: <a th:href="@{${'https://www.github.com/' + course.getGithubOrganizationName()}}" th:text="${course.getGithubOrganizationName()}"></a>
</li>
<li>
<a th:href="@{/courses/{code}/teaching_assistant_teams(code=${course.getCode()})}">Teaching Assistant Teams</a>
</li>
<li>
<a th:href="@{/courses/{code}/student_teams(code=${course.getCode()})}">Student Teams</a>
</li>
<li>
<a th:href="@{/courses/{code}/students(code=${course.getCode()})}">All Students</a>
</li>
<li>
<a th:href="@{/courses/{code}/teaching_assistants(code=${course.getCode()})}">All Teaching Assistants</a>
</li>
</ul>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import Students From CSV</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import Teaching Assistants From CSV</a>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import students from CSV</a>
<div id="content" class="row justify-content-center">
<div class="col-sm-12 col-md-6">
<div class="list-group">
<a class="list-group-item list-group-item-action" th:href="@{${'https://www.github.com/' + course.getGithubOrganizationName()}}">
Github Organization: <span th:text="${course.getGithubOrganizationName()}"></span>
</a>
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" th:href="@{/courses/{code}/teaching_assistant_teams(code=${course.getCode()})}">
Teaching Assistant Teams
<span class="badge badge-primary" th:text="${course.getTeachingAssistantTeams().size()}"></span>
</a>
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" th:href="@{/courses/{code}/student_teams(code=${course.getCode()})}">
Student Teams
<span class="badge badge-primary" th:text="${course.getStudentTeams().size()}"></span>
</a>
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" th:href="@{/courses/{code}/students(code=${course.getCode()})}">
Students
<span class="badge badge-primary" th:text="${course.getStudents().size()}"></span>
</a>
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" th:href="@{/courses/{code}/teaching_assistants(code=${course.getCode()})}">
Teaching Assistants
<span class="badge badge-primary" th:text="${course.getTeachingAssistants().size()}"></span>
</a>
</div>
<div class="sidebar_block">
<a th:disabled="true" th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import teaching assistants from CSV</a>
</div>
</div>

View File

@ -1,24 +1,22 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar_content})}">
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Import Students From CSV', content=~{::#content})}">
<head>
<title>Import Students via CSV</title>
</head>
<body>
<div id="content">
<h1>Import Students from CSV File</h1>
<div id="content" class="row justify-content-center">
<p>
Use this form to import student data from a CSV which has been generated by a Google form. As of right now, the column headers are hard-coded, but in the future it will be possible to dynamically import data from any CSV file by custom header format definitions.
</p>
<form method="post" action="#" enctype="multipart/form-data" th:action="@{/courses/{code}/import_students(code=${course.getCode()})}">
<div class="page_row">
<form method="post" enctype="multipart/form-data" th:action="@{/courses/{code}/import_students(code=${course.getCode()})}">
<div class="form-row">
<label for="file_input">File:</label>
<input id="file_input" type="file" name="file" accept="text/csv"/>
</div>
<div class="page_row">
<div class="form-row">
<button type="submit">Submit</button>
</div>
@ -26,9 +24,5 @@
</div>
<div id="sidebar_content">
</div>
</body>
</html>

View File

@ -1,18 +1,33 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Student Teams for ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1>Student Teams for <span th:text="${course.getName()}"></span></h1>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/student_teams/generate_repositories(code=${course.getCode()})}">
Generate Repositories
</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/student_teams/branch_protection_update(code=${course.getCode()})}">
Update Branch Protection
</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/student_teams/export(code=${course.getCode()})}">
Export
</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/student_teams/export_contact_info(code=${course.getCode()})}">
Export Team Contact Details
</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/student_teams/create(code=${course.getCode()})}">
Create New Student Team
</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/student_teams/merge_single_teams(code=${course.getCode()})}">
Merge Single Teams
</a>
</div>
<div id="content" class="row justify-content-center">
<div th:if="${course.getStudentTeams().isEmpty()}">
<p>No student teams.</p>
</div>
<table>
<table class="table">
<tr>
<th>Team</th>
<th>Students</th>
@ -53,26 +68,5 @@
</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/branch_protection_update(code=${course.getCode()})}">Update Branch Protection</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 class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/create(code=${course.getCode()})}">Create New Student Team</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/merge_single_teams(code=${course.getCode()})}">Merge Single Teams</a>
</div>
</div>
</body>
</html>

View File

@ -1,28 +1,19 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Create Student Team</title>
</head>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Create Student Team', content=~{::#content})}">
<body>
<div id="content">
<h1>Create a New Student Team</h1>
<div id="content" class="row justify-content-center">
<p>
Creates a new student team for the <span th:text="${course.getName()}"></span> course, without any assigned TA team or student members.
</p>
<form action="#" th:action="@{/courses/{code}/student_teams/create(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<form th:action="@{/courses/{code}/student_teams/create(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<div class="page_row">
<button type="submit">Submit</button>
<div class="form-row">
<input type="submit" value="Submit" class="btn btn-primary"/>
</div>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -1,14 +1,39 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Student Team ' + ${student_team.getId()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1>Student Team <span th:text="${student_team.getId()}"></span></h1>
<div id="actions">
<th:block th:if="${student_team.getGithubRepositoryName() == null && student_team.getAssignedTeachingAssistantTeam() != null}">
<a
class="btn btn-primary m-1"
th:href="@{/courses/{code}/student_teams/{id}/generate_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}"
>Generate Repository</a>
</th:block>
<th:block th:if="${student_team.getGithubRepositoryName() != null}">
<a
class="btn btn-primary m-1"
th:href="@{/courses/{code}/student_teams/{id}/delete_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
>Archive Repository</a>
</th:block>
<a
class="btn btn-primary m-1"
th:href="@{/courses/{code}/student_teams/{id}/add_student(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}">
Add Student To Team
</a>
<a
class="btn btn-primary m-1"
th:href="@{/courses/{code}/student_teams/{id}/assign_teaching_assistant_team(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}">
Assign Teaching Assistant Team
</a>
<a
class="btn btn-primary m-1"
th:href="@{/courses/{code}/student_teams/{id}/remove(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}">Remove This Team</a>
</div>
<div id="content" class="row justify-content-center">
<ul>
<li>
Github Repository:
@ -43,38 +68,12 @@
</div>
<div id="sidebar">
<div class="sidebar_block" th:if="${student_team.getGithubRepositoryName() == null && student_team.getAssignedTeachingAssistantTeam() != null}">
<a
th:href="@{/courses/{code}/student_teams/{id}/generate_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}"
>Generate Repository</a>
</div>
<div class="sidebar_block" >
<div class="sidebar_block" th:if="${student_team.getGithubRepositoryName() != null}">
<a
th:href="@{/courses/{code}/student_teams/{id}/delete_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
>Archive Repository</a>
</div>
<div class="sidebar_block" >
<a th:href="@{/courses/{code}/student_teams/{id}/add_student(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}">
Add Student To Team
</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/{id}/assign_teaching_assistant_team(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}">
Assign Teaching Assistant Team
</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/{id}/remove(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}">Remove This Team</a>
<p>
Removes this team permanently, and archives the repository.
</p>
</div>
</div>
</body>

View File

@ -1,28 +1,22 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Add Student To Team</title>
</head>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Add Student To Team', content=~{::#content})}" lang="en">
<body>
<div id="content">
<h1>Add a Student to Team <span th:text="${student_team.getId()}"></span></h1>
<div id="content" class="row justify-content-center">
<form
action="#"
th:action="@{/courses/{code}/student_teams/{id}/add_student(code=${course.getCode()}, id=${student_team.getId()})}"
enctype="application/x-www-form-urlencoded"
method="post"
>
<div class="page_row">
<div class="form-row">
<label for="student_select">Select a Student to Add:</label>
<select id="student_select" name="student_id" required>
<select id="student_select" name="student_id" class="form-control" required>
<option th:each="student: ${eligible_students}" th:value="${student.getId()}" th:text="${student.getFullName()}"></option>
</select>
</div>
<div class="page_row">
<button type="submit">Submit</button>
<div class="form-row">
<input type="submit" class="btn btn-primary" value="Submit"/>
</div>
</form>
</div>

View File

@ -1,22 +1,16 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Assign Teaching Assistant Team</title>
</head>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Assign Teaching Assistant Team', content=~{::#content})}">
<body>
<div id="content">
<h1>Assign a Teaching Assistant Team to Student Team <span th:text="${student_team.getId()}"></span></h1>
<div id="content" class="row justify-content-center">
<form
action="#"
th:action="@{/courses/{code}/student_teams/{id}/assign_teaching_assistant_team(code=${course.getCode()}, id=${student_team.getId()})}"
enctype="application/x-www-form-urlencoded"
method="post"
>
<div class="page_row">
<div class="form-row">
<label for="ta_team_select">Select a Team:</label>
<select id="ta_team_select" name="teaching_assistant_team_id">
<select id="ta_team_select" name="teaching_assistant_team_id" class="form-control">
<option value="-1">None</option>
<option
th:each="team: ${course.getTeachingAssistantTeams()}" th:value="${team.getId()}"
@ -25,8 +19,8 @@
</select>
</div>
<div class="page_row">
<button type="submit">Submit</button>
<div class="form-row">
<input type="submit" value="Submit" class="btn btn-primary"/>
</div>
</form>
</div>

View File

@ -1,16 +1,11 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Generate Repositories', content=~{::#content})}">
<body>
<div id="content">
<h1>Generate Repositories for all Student Teams in <span th:text="${course.getName()}"></span></h1>
<div id="content" class="row justify-content-center">
<p>
Be careful, this may take a little while...
Generate Repositories for all Student Teams in <span th:text="${course.getName()}"></span>. Be careful, this may take a little while...
</p>
<form
@ -23,9 +18,5 @@
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -1,15 +1,16 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Students in ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1>Students in <span th:text="${course.getName()}"></span></h1>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/students/invite_all(code=${course.getCode()})}">
Invite All Students To Repository
</a>
</div>
<table>
<div id="content">
<table class="table table-striped">
<tr>
<th>Student</th>
<th>Student Number</th>
@ -25,11 +26,5 @@
</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

@ -8,7 +8,7 @@
<div id="content">
<h1>Invite All Students in <span th:text="${course.getName()}"></span> to Repository</h1>
<!-- TODO: Upgrade the basic method by which students gain access to the repository. Follow instructions from Github. -->
<p>
Be careful, this may take a little while...
</p>

View File

@ -1,30 +1,53 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Teaching Assistants for ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1>Teaching Assistant Teams for <span th:text="${course.getName()}"></span></h1>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/teaching_assistant_teams/create(code=${course.getCode()})}">
Create Teaching Assistant Team
</a>
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/teaching_assistant_teams/assign_to_student_teams(code=${course.getCode()})}">
Assign Teams to Student Teams
</a>
</div>
<div id="content" class="row justify-content-center">
<div th:if="${course.getTeachingAssistantTeams().isEmpty()}">
<p>No teaching assistant teams.</p>
</div>
<table>
<table class="table table-striped">
<tr>
<th>Team</th>
<th>Teaching Assistants</th>
<th>Github Team</th>
<th>Assigned Student Teams</th>
<th>Delete</th>
</tr>
<tr th:each="taTeam: ${course.getTeachingAssistantTeams()}">
<td>
<a
th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}
<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>
<div th:each="ta: ${taTeam.getTeachingAssistants()}">
<a th:href="@{/teaching_assistants/{id}(id=${ta.getId()})}" th:text="${ta.getFullName()}"></a><br>
</div>
</td>
<td>
<a
th:href="'https://www.github.com/orgs/' + ${course.getGithubOrganizationName()} + '/teams/' + ${taTeam.getGithubTeamName().toLowerCase()}"
th:text="${taTeam.getGithubTeamName()}"
target="_blank"
></a>
</td>
<td>
<div th:each="studentTeam: ${taTeam.getAssignedStudentTeams()}">
<a th:href="@{/courses/{code}/student_teams/{id}(code=${course.getCode()}, id=${studentTeam.getId()})}" th:text="'Student Team ' + ${studentTeam.getId()}"></a>
</div>
</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()})}">
@ -35,14 +58,5 @@
</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

@ -1,11 +1,8 @@
<!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>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Assign To Student Teams', content=~{::#content})}">
<body>
<div id="content">
<div id="content" class="row justify-content-center">
<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/>
@ -13,9 +10,5 @@
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -1,25 +1,25 @@
<!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>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Create Teaching Assistant Team', content=~{::#content})}">
<body>
<div id="content">
<div id="content" class="row justify-content-center">
<div class="col-sm-12 col-md-6">
<form action="#" th:action="@{/courses/{code}/teaching_assistant_teams(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<div class="form-row">
<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>
<input id="ta_team_github_team_name" type="text" name="github_team_name" class="form-control"/>
</div>
<div id="sidebar">
<div class="form-row">
<label for="ta_team_member_1">Select the first Team Member:</label>
<select id="ta_team_member_1" name="id_1" class="form-control">
<option th:each="ta: ${course.getTeachingAssistants()}" th:value="${ta.getId()}" th:text="${ta.getFullName()}"></option>
</select>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
</div>
</div>

View File

@ -1,13 +1,11 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Teaching Assistant Team ' + ${teachingAssistantTeam.getId()}, content=~{::#content})}">
<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>
<div id="content" class="row justify-content-center">
<ul>
<li>
Github Team Name: <code th:text="${teachingAssistantTeam.getGithubTeamName()}"></code>
@ -35,9 +33,5 @@
</ul>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -1,14 +1,18 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Teaching Assistants for ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1>Teaching Assistants for <span th:text="${course.getName()}"></span></h1>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/courses/{code}/teaching_assistants/create(code=${course.getCode()})}">Add Teaching Assistant</a>
</div>
<table>
<div id="content">
<table class="table table-striped">
<tr>
<th>Teaching Assistant</th>
<th>Email</th>
<th>Id</th>
</tr>
<tr th:each="teachingAssistant: ${course.getTeachingAssistants()}">
<td>
<a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a>
@ -19,11 +23,5 @@
</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

@ -5,19 +5,32 @@
</head>
<body>
<footer th:fragment="footer" class="footer_bar">
<!-- <link rel="stylesheet" href="../../../resources/static/css/footer.css" th:href="@{/css/footer.css}"/>-->
<!-- <div class="third">-->
<!-- First third of footer-->
<!-- </div>-->
<!-- <div class="third">-->
<!-- Middle of footer-->
<!-- </div>-->
<!-- <div class="third">-->
<!-- Right side of footer.-->
<!-- </div>-->
<footer th:fragment="footer" class="container-fluid footer fixed-bottom">
<link rel="stylesheet" th:href="@{/css/footer.css}" type="text/css"/>
<div class="row text-center">
<div class="col-sm-12 col-md-4">
<h5>Quick Links</h5>
</div>
<div class="col-sm-12 col-md-4">
<h5>More links</h5>
</div>
<div class="col-sm-12 col-md-4">
<h5>Yet more links</h5>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
Social media icons go here.
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="h6">
&copy; 2019 Andrew Lalis, all rights reserved.
</p>
</div>
</div>
</footer>
</body>

View File

@ -7,15 +7,36 @@
<body>
<nav th:fragment="header" class="header_bar">
<link rel="stylesheet" href="../../../resources/static/css/header.css" th:href="@{/css/header.css}"/>
<h1 class="header_title">Teaching Assistant Assistant</h1>
<ul class="header_link_list">
<li><a href="/" th:href="@{/}">Home</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>
<nav th:fragment="header" class="navbar navbar-expand navbar- header">
<link rel="stylesheet" th:href="@{/css/header.css}"/>
<a class="navbar-brand" href="/">
<img src="/../resources/static/images/logo.png" th:src="@{/images/logo.png}" class="d-inline-block align-top" alt="" width="30" height="30"/>
Teaching Assistant Assistant
</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" th:href="@{/}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/courses}">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/students}">Students</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/teaching_assistants}">
Teaching Assistants
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/logout}">
Log out (Logged in as <span th:text="${user.getPerson().getFullName()}"></span>)
</a>
</li>
</ul>
</div>
</nav>
</body>

View File

@ -1,25 +1,22 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Homepage</title>
</head>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Home', content=~{::#content})}" lang="en">
<body>
<div id="content">
<div id="content" class="container-fluid">
<div class="row justify-content-center">
<div class="col-6">
<h3>Welcome to the Teaching Assistant Assistant, <span th:text="${user.getPerson().getFullName()}"></span>.</h3>
<p>
Welcome to the home page. To find the courses in this application please follow the link to <a th:href="@{/courses}">courses</a>.
To find the courses in this application please follow the link to <a th:href="@{/courses}">courses</a>.
</p>
<p>
This application makes it easy to sift through lots of student groups that are usually handled manually in some
spreadsheet. By integrating all data into one web application it is possible to access the information you need
faster, and make coordinated changes without fear of introducing inconsistencies or errors.
</p>
</div>
<div id="sidebar">
<div class="sidebar_block">
This is an example sidebar block.
</div>
<div id="generate_courses_block" class="sidebar_block">
<a href="/courses/generate">Click here to generate test data.</a>
</div>
<div class="sidebar_block">
Another sidebar block with a lot more text in it, so that the text should properly wrap to the next lines if needed.
</div>
</div>

View File

@ -1,30 +1,45 @@
<!DOCTYPE html>
<html lang="en" th:fragment="layout (title, content, sidebar_content)" xmlns:th="http://www.thymeleaf.org">
<html lang="en" th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:replace="${title}">basic_page</title>
<title th:text="${title}">basic_page</title>
<link rel="icon" th:href="@{/images/favicon.ico}"/>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"/>
<script th:src="@{/javascript/bootstrap.bundle.min.js}"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<link rel="stylesheet" href="../../../resources/static/css/style.css" th:href="@{/css/style.css}" />
</head>
<body>
<!-- Only show the header if the user is authenticated. -->
<header th:if="${user != null}">
<div th:replace="~{fragments/header :: header}"></div>
</header>
<div class="content_container">
<div th:replace="${content}">
<div class="content_container container-fluid">
<div class="row">
<div class="col">
<h1 class="text-center" th:text="${title}"></h1>
<hr>
</div>
</div>
<!-- If the page controller specifies some actions, add them here as cards. -->
<th:block th:if="${actions != null}">
<div th:include="${actions}" class="row justify-content-center mb-2">
</div>
</th:block>
<div th:replace="${content}" class="row">
<p>
Basic page content block.
</p>
</div>
</div>
<div class="sidebar_container">
<div th:replace="${sidebar_content}">
<p>
Basic page sidebar block.
</p>
</div>
</div>
<div th:replace="~{fragments/footer :: footer}"></div>
</body>

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Login', content=~{::#content})}" lang="en">
<body>
<section id="content" class="container">
<div class="row justify-content-center">
<div class="alert alert-danger" th:if="${param.error}">
Invalid username or password!
</div>
<div class="alert alert-success" th:if="${param.logout}">
You have been logged out!
</div>
</div>
<div class="row justify-content-center">
<p>
Please log in to access to the world's most advanced course organization tool ever created. With the ability to manipulate large groups of students with ease, you might actually enjoy teaching a course.
</p>
</div>
<div class="row justify-content-center">
<form th:action="@{/login}" method="post">
<div class="form-group">
<label for="username_input">Username</label>
<input id="username_input" class="form-control" type="text" name="username"/>
</div>
<div class="form-group">
<label for="password_input">Password</label>
<input id="password_input" class="form-control" type="password" name="password"/>
</div>
<input class="btn btn-primary" type="submit" value="Login"/>
<a class="btn btn-secondary btn-sm" th:href="@{/register}">Don't have an account? Sign up here!</a>
</form>
</div>
</section>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Register', content=~{::#content})}" lang="en">
<body>
<section id="content" class="container">
<div class="row">
<div class="col">
<p>
Here you can register to create a new account to access the information provided by this application, whether you are a professor, teaching assistant, or student. After filling out this submission form, you'll receive an email with a link to verify that you are who you say you are, after which your account will be activated.
</p>
</div>
</div>
<div class="row justify-content-center">
<h3>I am a ...</h3>
</div>
<div class="row justify-content-center">
<div class="col text-center">
<a class="btn btn-primary" th:href="@{/register/student}">Student</a>
</div>
<div class="col text-center">
<a class="btn btn-primary" th:href="@{/register/teaching_assistant}">Teaching Assistant</a>
</div>
<div class="col text-center">
<a class="btn btn-primary" th:href="@{/register/administrator}">Administrator</a>
</div>
</div>
</section>
</body>
</html>

View File

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

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Register as a New Student', content=~{::#content})}" lang="en">
<body>
<section id="content" class="container">
<div class="row">
<div class="col">
<p>
To register as a student, please fill out the form below. Make sure to double-check that all information is entered is accurate.
</p>
<p>
Where the form asks for a <em>Registration Code</em>, please provide the code that has been given to you by your lecturers. This code ensures that you'll join the right course.
</p>
</div>
</div>
<div class="row justify-content-center">
<div class="col">
<form action="/register/student" method="post" enctype="application/x-www-form-urlencoded">
<div class="form-group">
<label>
Registration Code:
<input type="text" name="registration_code" class="form-control">
</label>
</div>
</form>
</div>
</div>
</section>
</body>
</html>

View File

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

View File

@ -1,15 +1,17 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Students', content=~{::#content}, actions=~{::#actions})}">
<head>
<meta charset="UTF-8">
<title>Students</title>
</head>
<body>
<div id="content">
<h1>All Students</h1>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/students/create}">Create Student</a>
</div>
<table>
<div id="content" class="row">
<table class="table">
<tr>
<th>Student</th>
<th>Student Number</th>
@ -44,11 +46,5 @@
</table>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/students/create}">Create Student</a>
</div>
</div>
</body>
</html>

View File

@ -1,42 +1,47 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Create a Student</title>
</head>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Create New Student', content=~{::#content})}">
<body>
<div id="content">
<h1>Create New Student</h1>
<form action="#" th:action="@{/students/create}" th:object="${student}" method="post" enctype="application/x-www-form-urlencoded">
<div class="page_row">
<label for="student_first_name_input">First Name:</label>
<input id="student_first_name_input" type="text" th:field="*{firstName}" required />
<div id="content" class="row justify-content-center">
<div class="col-sm-12 col-md-6">
<form th:action="@{/students/create}" th:object="${student}" method="post" enctype="application/x-www-form-urlencoded">
<div class="form-row">
<div class="col">
<label for="student_first_name_input">First Name</label>
<input id="student_first_name_input" class="form-control" type="text" th:field="*{firstName}" placeholder="Elon" required />
</div>
<div class="col">
<label for="student_last_name_input">Last Name</label>
<input id="student_last_name_input" class="form-control" type="text" th:field="*{lastName}" placeholder="Musk" required />
</div>
</div>
<div class="page_row">
<label for="student_last_name_input">Last Name:</label>
<input id="student_last_name_input" type="text" th:field="*{lastName}" required />
<div class="form-row">
<div class="col">
<label for="student_email_input">Email Address</label>
<input id="student_email_input" class="form-control" type="email" th:field="*{emailAddress}" placeholder="me@example.com" required />
</div>
</div>
<div class="page_row">
<label for="student_email_input">Email Address:</label>
<input id="student_email_input" type="email" th:field="*{emailAddress}" required />
<div class="form-row">
<div class="col">
<label for="student_github_username_input">Github Username</label>
<input id="student_github_username_input" class="form-control" type="text" th:field="*{githubUsername}" placeholder="username" required />
</div>
</div>
<div class="page_row">
<label for="student_github_username_input">Github Username:</label>
<input id="student_github_username_input" type="text" th:field="*{githubUsername}" required />
<div class="form-row">
<div class="col">
<label for="student_number_input">Student Number</label>
<input id="student_number_input" class="form-control" type="number" th:field="*{studentNumber}" value="" placeholder="1234567" required />
</div>
</div>
<div class="page_row">
<label for="student_number_input">Student Number:</label>
<input id="student_number_input" type="number" th:field="*{studentNumber}" required />
</div>
<div class="page_row">
<label for="student_course_select">Select a course to add the student to:</label>
<select id="student_course_select" name="course_code">
<div class="form-row">
<div class="col">
<label for="student_course_select">Add this student to a course?</label>
<select id="student_course_select" class="form-control" name="course_code">
<option value="NO_COURSE_SELECTED">None</option>
<option
th:each="course: ${courses}"
@ -44,16 +49,18 @@
th:text="${course.getName()}"
></option>
</select>
<small class="form-text text-muted">This can also be done later at this student's page.</small>
</div>
</div>
<div class="page_row">
<button type="submit">Submit</button>
<div class="form-row mt-1">
<div class="col">
<input class="btn btn-primary" type="submit" value="Submit" />
</div>
</div>
</form>
</div>
<div id="sidebar">
</div>
</body>

View File

@ -1,13 +1,13 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title=${student.getFullName()}, content=~{::#content}, actions=~{::#actions})}">
<body>
<div id="content">
<h1>Student: <span th:text="${student.getFullName()}"></span></h1>
<div id="actions">
<a class="btn btn-primary m-1" th:href="@{/students/{id}/edit(id=${student.getId()})}">Edit</a>
</div>
<div id="content" class="row">
<ul>
<li>
Student Number: <code th:text="${student.getStudentNumber()}"></code>
@ -29,11 +29,5 @@
</ul>
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/students/{id}/edit(id=${student.getId()})}">Edit</a>
</div>
</div>
</body>
</html>

View File

@ -1,53 +1,44 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Edit Student</title>
</head>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Edit Student', content=~{::#content})}">
<body>
<div id="content">
<h1>Edit Student: <span th:text="${student.getFullName()}"></span></h1>
<div id="content" class="row justify-content-center">
<div class="col-sm-12 col-md-6">
<form
action="#"
th:action="@{/students/{id}/edit(id=${student.getId()})}" th:object="${student}"
method="post"
>
<div class="page_row">
<div class="form-row">
<label for="first_name_input">First Name:</label>
<input id="first_name_input" type="text" th:field="*{firstName}" required />
</div>
<div class="page_row">
<div class="form-row">
<label for="last_name_input">Last Name:</label>
<input id="last_name_input" type="text" th:field="*{lastName}" required />
</div>
<div class="page_row">
<div class="form-row">
<label for="email_input">Email Address:</label>
<input id="email_input" type="email" th:field="*{emailAddress}" required />
</div>
<div class="page_row">
<div class="form-row">
<label for="github_username_input">Github Username:</label>
<input id="github_username_input" type="text" th:field="*{githubUsername}" required />
</div>
<div class="page_row">
<div class="form-row">
<label for="student_number_input">Student Number:</label>
<input id="student_number_input" type="number" th:field="*{studentNumber}" required />
</div>
<div class="page_row">
<div class="form-row">
<button type="submit">Submit</button>
</div>
</form>
</div>
<div id="sidebar">
</div>
</body>

View File

@ -1,22 +1,28 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title='Teaching Assistants', content=~{::#content})}">
<body>
<div id="content">
<h1>All Teaching Assistants</h1>
<table>
<table class="table">
<tr>
<th>Teaching Assistant</th>
<th>Github Username</th>
<th>Email</th>
<th>Courses</th>
<th>Delete</th>
</tr>
<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>
<div th:each="course: ${teachingAssistant.getCourses()}">
<a th:href="@{/courses/{code}(code=${course.getCode()})}" th:text="${course.getName()}"></a><br>
</div>
</td>
<td>
<a th:href="@{/teaching_assistants/{id}/delete(id=${teachingAssistant.getId()})}">Delete</a>
</td>
@ -24,9 +30,5 @@
</table>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -1,33 +1,21 @@
<!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>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (title=${teachingAssistant.getFullName()}, content=~{::#content})}">
<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 id="content" class="row justify-content-center">
<div class="col-sm-12 col-md-6">
<div class="list-group">
<a class="list-group-item list-group-item-action" th:text="'Email: ' + ${teachingAssistant.getEmailAddress()}" th:href="'mailto: ' + ${teachingAssistant.getEmailAddress()}"></a>
<a class="list-group-item list-group-item-action" th:text="'Github Username: ' + ${teachingAssistant.getGithubUsername()}" th:href="'https://www.github.com/' + ${teachingAssistant.getGithubUsername()}" target="_blank"></a>
<div class="list-group-item">
<h5>Courses</h5>
<div th:each="course: ${teachingAssistant.getCourses()}">
<a class="list-group-item-action" th:text="${course.getName()}" th:href="@{/courses/{code}(code=${course.getCode()})}"></a><br>
</div>
</div>
</div>
</div>
<div id="sidebar">
</div>
</body>

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Test Controller Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'"></p>
</body>
</html>