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/* .idea/*
*.iml *.iml
target/* 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; 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.CommandLineRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootApplication @SpringBootApplication
public class TeachingAssistantAssistantApplication implements CommandLineRunner { public class TeachingAssistantAssistantApplication implements CommandLineRunner {
@Autowired
CourseRepository courseRepository;
@Autowired
TeamRepository teamRepository;
@Autowired
PersonRepository personRepository;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(TeachingAssistantAssistantApplication.class, args); SpringApplication.run(TeachingAssistantAssistantApplication.class, args);
} }
@ -27,5 +15,6 @@ public class TeachingAssistantAssistantApplication implements CommandLineRunner
@Override @Override
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
System.out.println("Running startup..."); System.out.println("Running startup...");
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 for the list of courses in the system.
*/ */
@Controller @Controller
public class Courses { public class CoursesController extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;
protected Courses(CourseRepository courseRepository) { protected CoursesController(CourseRepository courseRepository) {
this.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.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
public class RootController { public class RootController extends UserPageController {
@RequestMapping( @GetMapping("/")
path = "/",
produces = "text/html"
)
public String index(Model model) { public String index(Model model) {
model.addAttribute("name", "JOHN"); return "index";
return "index.html";
} }
} }

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; import java.util.Optional;
/**
* Controller for the list of teaching assistants in the system.
*/
@Controller @Controller
public class TeachingAssistants { public class TeachingAssistantsController extends UserPageController {
private TeachingAssistantRepository teachingAssistantRepository; private TeachingAssistantRepository teachingAssistantRepository;
private CourseRepository courseRepository; private CourseRepository courseRepository;
protected TeachingAssistants(TeachingAssistantRepository teachingAssistantRepository, CourseRepository courseRepository) { protected TeachingAssistantsController(TeachingAssistantRepository teachingAssistantRepository, CourseRepository courseRepository) {
this.teachingAssistantRepository = teachingAssistantRepository; this.teachingAssistantRepository = teachingAssistantRepository;
this.courseRepository = courseRepository; 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; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository; import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -13,7 +14,7 @@ import java.util.Optional;
* Controller for the course entity, that is, one individual course. * Controller for the course entity, that is, one individual course.
*/ */
@Controller @Controller
public class CourseEntity { public class CourseEntity extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;

View File

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

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository; import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import nl.andrewlalis.teaching_assistant_assistant.util.sample_data.CourseGenerator; 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; import java.util.List;
@Controller @Controller
public class Generate { public class Generate extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam; import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository; 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 for importing students from a CSV sheet.
*/ */
@Controller @Controller
public class ImportStudents { public class ImportStudents extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student; import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam; import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
@ -18,7 +19,7 @@ import java.util.Optional;
* Controller for exporting team information into readable files. * Controller for exporting team information into readable files.
*/ */
@Controller @Controller
public class ExportStudentTeams { public class ExportStudentTeams extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;
@ -79,10 +80,17 @@ public class ExportStudentTeams {
private byte[] getContactInfo(Course course) { private byte[] getContactInfo(Course course) {
StringBuilder sb = new StringBuilder("Student Team Contact Details\n"); 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()) { 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()) { 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"); sb.append("\n");
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant; import nl.andrewlalis.teaching_assistant_assistant.model.people.TeachingAssistant;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam; 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; import java.util.Optional;
@Controller @Controller
public class CreateTeachingAssistantTeam { public class CreateTeachingAssistantTeam extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;
private TeachingAssistantRepository teachingAssistantRepository; private TeachingAssistantRepository teachingAssistantRepository;

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam; import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository; import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
@ -12,7 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import java.util.Optional; import java.util.Optional;
@Controller @Controller
public class TeachingAssistantTeamEntity { public class TeachingAssistantTeamEntity extends UserPageController {
private CourseRepository courseRepository; private CourseRepository courseRepository;
private TeachingAssistantTeamRepository teachingAssistantTeamRepository; 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.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student; 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.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.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -14,30 +15,28 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.Optional; 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 @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 static final String NO_COURSE = "NO_COURSE_SELECTED";
private StudentRepository studentRepository;
private CourseRepository courseRepository; private CourseRepository courseRepository;
private StudentService studentService;
protected Students(StudentRepository studentRepository, CourseRepository courseRepository) { protected StudentCreateController(CourseRepository courseRepository, StudentService studentService) {
this.studentRepository = studentRepository;
this.courseRepository = courseRepository; this.courseRepository = courseRepository;
} this.studentService = studentService;
@GetMapping("/students")
public String get(Model model) {
model.addAttribute("students", this.studentRepository.findAll());
return "students";
} }
@GetMapping("/students/create") @GetMapping("/students/create")
public String getCreate(Model model) { 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()); model.addAttribute("courses", this.courseRepository.findAll());
return "students/create"; return "students/create";
@ -49,20 +48,16 @@ public class Students {
) )
public String postCreate( public String postCreate(
@ModelAttribute Student newStudent, @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); Optional<Course> optionalCourse = this.courseRepository.findByCode(courseCode);
optionalCourse.ifPresent(course -> { Course course = null;
course.addParticipant(newStudent); if (optionalCourse.isPresent()) {
newStudent.assignToCourse(course); course = optionalCourse.get();
this.courseRepository.save(course);
this.studentRepository.save(newStudent);
});
} }
this.studentService.createStudent(newStudent, course);
return "redirect:/students"; 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; 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.people.TeachingAssistant;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantRepository; import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeachingAssistantRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -9,12 +10,15 @@ import org.springframework.web.bind.annotation.PathVariable;
import java.util.Optional; import java.util.Optional;
/**
* Controller for a single teaching assistant entity.
*/
@Controller @Controller
public class TeachingAssistantEntity { public class TeachingAssistantEntityController extends UserPageController {
private TeachingAssistantRepository teachingAssistantRepository; private TeachingAssistantRepository teachingAssistantRepository;
protected TeachingAssistantEntity(TeachingAssistantRepository teachingAssistantRepository) { protected TeachingAssistantEntityController(TeachingAssistantRepository teachingAssistantRepository) {
this.teachingAssistantRepository = teachingAssistantRepository; this.teachingAssistantRepository = teachingAssistantRepository;
} }

View File

@ -6,23 +6,39 @@ import javax.persistence.*;
import java.util.Date; import java.util.Date;
/** /**
* The basic entity properties which any entity in this system should have: an id and a creation timestamp. Every entity * An abstract object from which all persistent objects in this application should extend.
* in this system should extend from BasicEntity. * <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 @MappedSuperclass
public abstract class BasicEntity { public abstract class BasicEntity {
/**
* The primary key for any basic entity.
*/
@Id @Id
@GeneratedValue( @GeneratedValue(
strategy = GenerationType.IDENTITY strategy = GenerationType.IDENTITY
) )
private Long id; private Long id;
/**
* The date at which this basic entity was created.
*/
@Temporal( @Temporal(
value = TemporalType.TIMESTAMP value = TemporalType.TIMESTAMP
) )
@CreationTimestamp @CreationTimestamp
@Column @Column(nullable = false)
private Date createdOn; private Date createdOn;
protected BasicEntity() {} 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.BasicEntity;
import nl.andrewlalis.teaching_assistant_assistant.model.Course; import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.Team; import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.Team;
import nl.andrewlalis.teaching_assistant_assistant.model.security.User;
import javax.persistence.*; import javax.persistence.*;
import java.util.ArrayList; 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. * 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 @Entity
@Table(name = "people") @Table(name = "people")
@ -18,15 +24,27 @@ import java.util.List;
) )
public abstract class Person extends BasicEntity { public abstract class Person extends BasicEntity {
/**
* The person's first name.
*/
@Column(nullable = false) @Column(nullable = false)
private String firstName; private String firstName;
/**
* The person's last name.
*/
@Column(nullable = false) @Column(nullable = false)
private String lastName; private String lastName;
/**
* The person's email address.
*/
@Column @Column
private String emailAddress; private String emailAddress;
/**
* The person's github username.
*/
@Column @Column
private String githubUsername; private String githubUsername;
@ -51,6 +69,16 @@ public abstract class Person extends BasicEntity {
) )
private List<Course> courses; 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. * Default constructor for JPA.
*/ */
@ -74,12 +102,20 @@ public abstract class Person extends BasicEntity {
this.githubUsername = githubUsername; this.githubUsername = githubUsername;
} }
/**
* Assigns this person to a team.
* @param team The team to assign to.
*/
public void assignToTeam(Team team) { public void assignToTeam(Team team) {
if (!this.teams.contains(team)) { if (!this.teams.contains(team)) {
this.teams.add(team); this.teams.add(team);
} }
} }
/**
* Removes this person from a team.
* @param team The team to remove from.
*/
public void removeFromAssignedTeam(Team team) { public void removeFromAssignedTeam(Team team) {
this.teams.remove(team); this.teams.remove(team);
} }
@ -142,6 +178,14 @@ public abstract class Person extends BasicEntity {
return this.teams; 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 * 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. * about the person is the same, regardless of case.

View File

@ -9,9 +9,11 @@ import java.util.ArrayList;
import java.util.List; 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 * A group consisting of one or more members that belongs to a {@link Course}.
* behavior if needed. *
* @param <P> The type of members this group contains. * <p>
* Any Team contains a collection of {@link Person} objects, each representing a member of the team.
* </p>
*/ */
@Entity @Entity
@Inheritance( @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); StudentTeam newTeam = new StudentTeam(course);
course.addStudentTeam(newTeam); course.addStudentTeam(newTeam);
this.courseRepository.save(course); this.courseRepository.save(course);
logger.info("Created new team: " + newTeam.getId()); logger.info("Created new team.");
return newTeam; return newTeam;
} }
@ -185,13 +185,19 @@ public class StudentTeamService {
s.removeFromAssignedTeam(team); s.removeFromAssignedTeam(team);
team.removeMember(s); team.removeMember(s);
this.studentRepository.save(s); this.studentRepository.save(s);
logger.debug("Removed student " + s.getFullName() + " from team " + team.getId());
} }
// Remove the TA team assignment. // Remove the TA team assignment.
TeachingAssistantTeam teachingAssistantTeam = team.getAssignedTeachingAssistantTeam(); TeachingAssistantTeam teachingAssistantTeam = team.getAssignedTeachingAssistantTeam();
if (teachingAssistantTeam != null) {
teachingAssistantTeam.removeAssignedStudentTeam(team); teachingAssistantTeam.removeAssignedStudentTeam(team);
team.setAssignedTeachingAssistantTeam(null);
this.teachingAssistantTeamRepository.save(teachingAssistantTeam); 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. // Remove the repository from the course and delete it.
course.removeStudentTeam(team); course.removeStudentTeam(team);

File diff suppressed because one or more lines are too long

View File

@ -1,17 +1,9 @@
.footer_bar { body {
background-color: darkslategray; padding-bottom: 110px;
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. */
} }
.third { .footer {
width: 33%; background-color: darkslategray;
display: inline-block; color: whitesmoke;
text-align: center; height: 100px;
} }

View File

@ -1,45 +1,13 @@
body { .header {
padding: 0;
margin: 0;
background-color: whitesmoke;
}
.header_bar {
background-color: green; background-color: green;
width: 100%;
color: whitesmoke; color: whitesmoke;
padding-bottom: 20px;
padding-top: 20px;
height: 30px;
} }
.header_title { .header a {
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 {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
background-color: darkgreen;
padding: 10px;
} }
.header_link_list a:hover { .header a:hover {
background-color: darkgray; 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. */ /*.btn-primary {*/
body { /* background-color: green;*/
font-family: sans-serif; /*}*/
}
.content_container { /*.btn-primary:hover {*/
width: 50%; /* background-color: darkgreen;*/
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;
}

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> <!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='Courses', content=~{::#content}, actions=~{::#actions})}" lang="en">
<head>
<title>Courses</title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1>Courses</h1> <a class="btn btn-primary m-1" th:href="@{/courses/create}">Create New Course</a>
<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> </div>
<div id="sidebar"> <div id="content" class="container-fluid">
<div class="sidebar_block"> <div class="col-sm-12 col-md-6 offset-3">
<a href="/courses/create">Create new course</a> <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>
<div class="sidebar_block"> <ul>
Do something else <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>
<div class="sidebar_block">
Click this link!
</div> </div>
</div> </div>

View File

@ -1,43 +1,35 @@
<!DOCTYPE html> <!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='Create New Course', content=~{::#content})}" lang="en">
<head>
<title>Create a Course</title>
</head>
<body> <body>
<div id="content"> <div id="content" class="row justify-content-center">
<h1>Create New Course</h1> <form th:action="@{/courses}" th:object="${course}" method="post">
<div class="form-group">
<form action="#" th:action="@{/courses}" th:object="${course}" method="post"> <label for="course_name_input">Name</label>
<div class="page_row"> <input id="course_name_input" type="text" class="form-control" th:field="*{name}" required/>
<label for="course_name_input">Name:</label>
<input id="course_name_input" type="text" th:field="*{name}" required/>
</div> </div>
<div class="page_row"> <div class="form-group">
<label for="course_code_input">Code:</label> <label for="course_code_input">Code</label>
<input id="course_code_input" type="text" th:field="*{code}" required/> <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>
<div class="page_row"> <div class="form-group">
<label for="course_github_organization_name_input">Github Organization:</label> <label for="course_github_organization_name_input">Github Organization</label>
<input id="course_github_organization_name_input" type="text" th:field="*{githubOrganizationName}" required/> <input id="course_github_organization_name_input" type="text" class="form-control" th:field="*{githubOrganizationName}" required/>
</div> </div>
<div class="page_row"> <div class="form-group">
<label for="course_github_api_key_input">Github API Key:</label> <label for="course_github_api_key_input">Github API Key</label>
<input id="course_github_api_key_input" type="text" th:field="*{apiKey}" required/> <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>
<div class="page_row"> <input class="btn btn-primary" type="submit" value="Create"/>
<button type="submit">Submit</button>
</div>
</form> </form>
</div> </div>
<div id="sidebar">
</div>
</body> </body>
</html> </html>

View File

@ -1,40 +1,35 @@
<!DOCTYPE html> <!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=${course.getName()}, content=~{::#content}, actions=~{::#actions})}">
<head>
<meta charset="UTF-8">
<title th:text="${course.getName()}"></title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1><span th:text="${course.getName()}"></span> (Code: <span th:text="${course.getCode()}"></span>)</h1> <a class="btn btn-primary m-1" th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import Students From CSV</a>
<hr> <a class="btn btn-primary m-1" th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import Teaching Assistants From CSV</a>
<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> </div>
<div id="sidebar"> <div id="content" class="row justify-content-center">
<div class="sidebar_block"> <div class="col-sm-12 col-md-6">
<a th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import students from CSV</a> <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>
<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>
</div> </div>

View File

@ -1,24 +1,22 @@
<!DOCTYPE html> <!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> <head>
<title>Import Students via CSV</title> <title>Import Students via CSV</title>
</head> </head>
<body> <body>
<div id="content"> <div id="content" class="row justify-content-center">
<h1>Import Students from CSV File</h1>
<p> <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. 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> </p>
<form method="post" action="#" enctype="multipart/form-data" th:action="@{/courses/{code}/import_students(code=${course.getCode()})}"> <form method="post" enctype="multipart/form-data" th:action="@{/courses/{code}/import_students(code=${course.getCode()})}">
<div class="page_row"> <div class="form-row">
<label for="file_input">File:</label> <label for="file_input">File:</label>
<input id="file_input" type="file" name="file" accept="text/csv"/> <input id="file_input" type="file" name="file" accept="text/csv"/>
</div> </div>
<div class="page_row"> <div class="form-row">
<button type="submit">Submit</button> <button type="submit">Submit</button>
</div> </div>
@ -26,9 +24,5 @@
</div> </div>
<div id="sidebar_content">
</div>
</body> </body>
</html> </html>

View File

@ -1,18 +1,33 @@
<!DOCTYPE html> <!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='Student Teams for ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<head>
<meta charset="UTF-8">
<title>Student Teams</title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1>Student Teams for <span th:text="${course.getName()}"></span></h1> <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()}"> <div th:if="${course.getStudentTeams().isEmpty()}">
<p>No student teams.</p> <p>No student teams.</p>
</div> </div>
<table> <table class="table">
<tr> <tr>
<th>Team</th> <th>Team</th>
<th>Students</th> <th>Students</th>
@ -53,26 +68,5 @@
</table> </table>
</div> </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> </body>
</html> </html>

View File

@ -1,28 +1,19 @@
<!DOCTYPE html> <!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='Create Student Team', content=~{::#content})}">
<head>
<title>Create Student Team</title>
</head>
<body> <body>
<div id="content"> <div id="content" class="row justify-content-center">
<h1>Create a New Student Team</h1>
<p> <p>
Creates a new student team for the <span th:text="${course.getName()}"></span> course, without any assigned TA team or student members. Creates a new student team for the <span th:text="${course.getName()}"></span> course, without any assigned TA team or student members.
</p> </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"> <div class="form-row">
<button type="submit">Submit</button> <input type="submit" value="Submit" class="btn btn-primary"/>
</div> </div>
</form> </form>
</div> </div>
<div id="sidebar">
</div>
</body> </body>
</html> </html>

View File

@ -1,14 +1,39 @@
<!DOCTYPE html> <!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='Student Team ' + ${student_team.getId()}, content=~{::#content}, actions=~{::#actions})}">
<head>
<meta charset="UTF-8">
<title>Student Team</title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1>Student Team <span th:text="${student_team.getId()}"></span></h1> <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> <ul>
<li> <li>
Github Repository: Github Repository:
@ -43,39 +68,13 @@
</div> </div>
<div id="sidebar"> <div id="sidebar">
<div class="sidebar_block" th:if="${student_team.getGithubRepositoryName() == null && student_team.getAssignedTeachingAssistantTeam() != null}"> <div class="sidebar_block" >
<a
th:href="@{/courses/{code}/student_teams/{id}/generate_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getId()})}"
>Generate Repository</a>
</div> </div>
<div class="sidebar_block" th:if="${student_team.getGithubRepositoryName() != null}"> <div class="sidebar_block" >
<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>
<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> </body>
</html> </html>

View File

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

View File

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

View File

@ -1,16 +1,11 @@
<!DOCTYPE html> <!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='Generate Repositories', content=~{::#content})}">
<head>
<meta charset="UTF-8">
<title>Generate Repositories</title>
</head>
<body> <body>
<div id="content"> <div id="content" class="row justify-content-center">
<h1>Generate Repositories for all Student Teams in <span th:text="${course.getName()}"></span></h1>
<p> <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> </p>
<form <form
@ -23,9 +18,5 @@
</form> </form>
</div> </div>
<div id="sidebar">
</div>
</body> </body>
</html> </html>

View File

@ -1,15 +1,16 @@
<!DOCTYPE html> <!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 in ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<head>
<meta charset="UTF-8">
<title>Students</title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1>Students in <span th:text="${course.getName()}"></span></h1> <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> <tr>
<th>Student</th> <th>Student</th>
<th>Student Number</th> <th>Student Number</th>
@ -25,11 +26,5 @@
</table> </table>
</div> </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> </body>
</html> </html>

View File

@ -8,7 +8,7 @@
<div id="content"> <div id="content">
<h1>Invite All Students in <span th:text="${course.getName()}"></span> to Repository</h1> <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> <p>
Be careful, this may take a little while... Be careful, this may take a little while...
</p> </p>

View File

@ -1,30 +1,53 @@
<!DOCTYPE html> <!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='Teaching Assistants for ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<head>
<meta charset="UTF-8">
<title>Teaching Assistant Teams</title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1>Teaching Assistant Teams for <span th:text="${course.getName()}"></span></h1> <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()}"> <div th:if="${course.getTeachingAssistantTeams().isEmpty()}">
<p>No teaching assistant teams.</p> <p>No teaching assistant teams.</p>
</div> </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()}"> <tr th:each="taTeam: ${course.getTeachingAssistantTeams()}">
<td> <td>
<a <a th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}
th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}
(code=${course.getCode()}, team_id=${taTeam.getId()})}"> (code=${course.getCode()}, team_id=${taTeam.getId()})}">
Teaching Assistant Team <span th:text="${taTeam.getId()}"></span> Teaching Assistant Team <span th:text="${taTeam.getId()}"></span>
</a> </a>
</td> </td>
<td th:each="ta: ${taTeam.getTeachingAssistants()}"> <td>
<span th:text="${ta.getFullName()}"></span> <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>
<td th:text="${taTeam.getGithubTeamName()}"></td>
<td> <td>
<a th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}/delete <a th:href="@{/courses/{code}/teaching_assistant_teams/{team_id}/delete
(code=${course.getCode()}, team_id=${taTeam.getId()})}"> (code=${course.getCode()}, team_id=${taTeam.getId()})}">
@ -35,14 +58,5 @@
</table> </table>
</div> </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> </body>
</html> </html>

View File

@ -1,11 +1,8 @@
<!DOCTYPE html> <!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='Assign To Student Teams', content=~{::#content})}">
<head>
<title>Assign To Student Teams</title>
</head>
<body> <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"> <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> <label for="seed_input">Random Seed:</label>
<input id="seed_input" type="number" name="seed" value="0" required/> <input id="seed_input" type="number" name="seed" value="0" required/>
@ -13,9 +10,5 @@
</form> </form>
</div> </div>
<div id="sidebar">
</div>
</body> </body>
</html> </html>

View File

@ -1,25 +1,25 @@
<!DOCTYPE html> <!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='Create Teaching Assistant Team', content=~{::#content})}">
<head>
<title>Create Teaching Assistant Team</title>
</head>
<body> <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"> <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> <label for="ta_team_github_team_name">Github team name:</label>
<input id="ta_team_github_team_name" type="text" name="github_team_name"/> <input id="ta_team_github_team_name" type="text" name="github_team_name" class="form-control"/>
</div>
<div class="form-row">
<label for="ta_team_member_1">Select the first Team Member:</label> <label for="ta_team_member_1">Select the first Team Member:</label>
<select id="ta_team_member_1" name="id_1"> <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> <option th:each="ta: ${course.getTeachingAssistants()}" th:value="${ta.getId()}" th:text="${ta.getFullName()}"></option>
</select> </select>
</div>
<button type="submit">Submit</button> <input type="submit" value="Submit" class="btn btn-primary"/>
</form> </form>
</div> </div>
<div id="sidebar">
</div> </div>

View File

@ -1,13 +1,11 @@
<!DOCTYPE html> <!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> <head>
<title>Teaching Assistant Team</title> <title>Teaching Assistant Team</title>
</head> </head>
<body> <body>
<div id="content"> <div id="content" class="row justify-content-center">
<h1>Teaching Assistant Team <span th:text="${teachingAssistantTeam.getId()}"></span> for <span th:text="${course.getName()}"></span></h1>
<ul> <ul>
<li> <li>
Github Team Name: <code th:text="${teachingAssistantTeam.getGithubTeamName()}"></code> Github Team Name: <code th:text="${teachingAssistantTeam.getGithubTeamName()}"></code>
@ -35,9 +33,5 @@
</ul> </ul>
</div> </div>
<div id="sidebar">
</div>
</body> </body>
</html> </html>

View File

@ -1,14 +1,18 @@
<!DOCTYPE html> <!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='Teaching Assistants for ' + ${course.getCode()}, content=~{::#content}, actions=~{::#actions})}">
<head>
<title>Teaching Assistants</title>
</head>
<body> <body>
<div id="content"> <div id="actions">
<h1>Teaching Assistants for <span th:text="${course.getName()}"></span></h1> <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()}"> <tr th:each="teachingAssistant: ${course.getTeachingAssistants()}">
<td> <td>
<a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a> <a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a>
@ -19,11 +23,5 @@
</table> </table>
</div> </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> </body>
</html> </html>

View File

@ -5,19 +5,32 @@
</head> </head>
<body> <body>
<footer th:fragment="footer" class="footer_bar"> <footer th:fragment="footer" class="container-fluid footer fixed-bottom">
<!-- <link rel="stylesheet" href="../../../resources/static/css/footer.css" th:href="@{/css/footer.css}"/>--> <link rel="stylesheet" th:href="@{/css/footer.css}" type="text/css"/>
<!-- <div class="third">-->
<!-- First third of footer-->
<!-- </div>-->
<!-- <div class="third">-->
<!-- Middle of footer-->
<!-- </div>-->
<!-- <div class="third">-->
<!-- Right side of footer.-->
<!-- </div>-->
<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> </footer>
</body> </body>

View File

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

View File

@ -1,25 +1,22 @@
<!DOCTYPE html> <!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='Home', content=~{::#content})}" lang="en">
<head>
<title>Homepage</title>
</head>
<body> <body>
<div id="content"> <div id="content" class="container-fluid">
<p> <div class="row justify-content-center">
Welcome to the home page. To find the courses in this application please follow the link to <a th:href="@{/courses}">courses</a>. <div class="col-6">
</p> <h3>Welcome to the Teaching Assistant Assistant, <span th:text="${user.getPerson().getFullName()}"></span>.</h3>
</div>
<div id="sidebar"> <p>
<div class="sidebar_block"> To find the courses in this application please follow the link to <a th:href="@{/courses}">courses</a>.
This is an example sidebar block. </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>
<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>
</div> </div>

View File

@ -1,30 +1,45 @@
<!DOCTYPE html> <!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> <head>
<meta charset="UTF-8"> <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}" /> <link rel="stylesheet" href="../../../resources/static/css/style.css" th:href="@{/css/style.css}" />
</head> </head>
<body> <body>
<div th:replace="~{fragments/header :: header}"></div> <!-- 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 class="content_container container-fluid">
<div th:replace="${content}"> <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> <p>
Basic page content block. Basic page content block.
</p> </p>
</div> </div>
</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> <div th:replace="~{fragments/footer :: footer}"></div>
</body> </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> <!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> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Students</title> <title>Students</title>
</head> </head>
<body> <body>
<div id="content"> <div id="actions">
<h1>All Students</h1> <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> <tr>
<th>Student</th> <th>Student</th>
<th>Student Number</th> <th>Student Number</th>
@ -44,11 +46,5 @@
</table> </table>
</div> </div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/students/create}">Create Student</a>
</div>
</div>
</body> </body>
</html> </html>

View File

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

View File

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

View File

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

View File

@ -1,22 +1,28 @@
<!DOCTYPE html> <!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='Teaching Assistants', content=~{::#content})}">
<head>
<meta charset="UTF-8">
<title>Teaching Assistants</title>
</head>
<body> <body>
<div id="content"> <div id="content">
<h1>All Teaching Assistants</h1> <table class="table">
<tr>
<table> <th>Teaching Assistant</th>
<th>Github Username</th>
<th>Email</th>
<th>Courses</th>
<th>Delete</th>
</tr>
<tr th:each="teachingAssistant: ${teaching_assistants}"> <tr th:each="teachingAssistant: ${teaching_assistants}">
<td> <td>
<a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a> <a th:href="@{/teaching_assistants/{id}(id=${teachingAssistant.getId()})}"><span th:text="${teachingAssistant.getFullName()}"></span></a>
</td> </td>
<td th:text="${teachingAssistant.getGithubUsername()}"></td> <td th:text="${teachingAssistant.getGithubUsername()}"></td>
<td th:text="${teachingAssistant.getEmailAddress()}"></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> <td>
<a th:href="@{/teaching_assistants/{id}/delete(id=${teachingAssistant.getId()})}">Delete</a> <a th:href="@{/teaching_assistants/{id}/delete(id=${teachingAssistant.getId()})}">Delete</a>
</td> </td>
@ -24,9 +30,5 @@
</table> </table>
</div> </div>
<div id="sidebar">
</div>
</body> </body>
</html> </html>

View File

@ -1,33 +1,21 @@
<!DOCTYPE html> <!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=${teachingAssistant.getFullName()}, content=~{::#content})}">
<head>
<title>Teaching Assistant</title>
</head>
<body> <body>
<div id="content"> <div id="content" class="row justify-content-center">
<h1>Teaching Assistant: <span th:text="${teachingAssistant.getFullName()}"></span></h1> <div class="col-sm-12 col-md-6">
<div class="list-group">
<ul> <a class="list-group-item list-group-item-action" th:text="'Email: ' + ${teachingAssistant.getEmailAddress()}" th:href="'mailto: ' + ${teachingAssistant.getEmailAddress()}"></a>
<li> <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>
Email: <code th:text="${teachingAssistant.getEmailAddress()}"></code> <div class="list-group-item">
</li> <h5>Courses</h5>
<li> <div th:each="course: ${teachingAssistant.getCourses()}">
Github Username: <code th:text="${teachingAssistant.getGithubUsername()}"></code> <a class="list-group-item-action" th:text="${course.getName()}" th:href="@{/courses/{code}(code=${course.getCode()})}"></a><br>
</li> </div>
<li> </div>
Courses: </div>
<ul> </div>
<li th:each="course: ${teachingAssistant.getCourses()}">
<a th:href="@{/courses/{code}(code=${course.getCode()})}"><span th:text="${course.getName()}"></span></a>
</li>
</ul>
</li>
</ul>
</div>
<div id="sidebar">
</div> </div>
</body> </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>