Merge pull request #2 from rug-advoop/repo_creation
Half of features implemented
This commit is contained in:
commit
c14ba00107
36
pom.xml
36
pom.xml
|
@ -21,6 +21,10 @@
|
|||
</build>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<jackson.version>2.9.6</jackson.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
@ -43,6 +47,38 @@
|
|||
<version>16.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<!-- Github API Object Library -->
|
||||
<dependency>
|
||||
<groupId>org.kohsuke</groupId>
|
||||
<artifactId>github-api</artifactId>
|
||||
<version>1.93</version>
|
||||
</dependency>
|
||||
<!-- SQLite JDBC Driver -->
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.23.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,16 +1,26 @@
|
|||
package nl.andrewlalis;
|
||||
|
||||
import nl.andrewlalis.model.Team;
|
||||
import nl.andrewlalis.model.database.Database;
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
import nl.andrewlalis.ui.control.command.Executable;
|
||||
import nl.andrewlalis.ui.control.command.executables.ArchiveRepos;
|
||||
import nl.andrewlalis.ui.control.command.executables.GenerateAssignmentsRepo;
|
||||
import nl.andrewlalis.ui.control.command.executables.ReadStudentsFileToDB;
|
||||
import nl.andrewlalis.ui.view.InitializerApp;
|
||||
import nl.andrewlalis.util.CommandLine;
|
||||
import nl.andrewlalis.util.Logging;
|
||||
import nl.andrewlalis.util.TeamGenerator;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import nl.andrewlalis.util.CommandLine;
|
||||
|
||||
/**
|
||||
* Main program entry point.
|
||||
*/
|
||||
|
@ -26,26 +36,48 @@ public class Main {
|
|||
// Initialize logger.
|
||||
try {
|
||||
Logging.setup(true); // TODO: Replace true with command line arg.
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.severe("Unable to save log to file.");
|
||||
}
|
||||
|
||||
logger.info("Initializer for Github Repositories in Educational Organizations.");
|
||||
// Command executor which will be used by all actions the user can do.
|
||||
CommandExecutor executor = new CommandExecutor();
|
||||
|
||||
// Initialize User Interface.
|
||||
InitializerApp app = new InitializerApp(executor);
|
||||
app.begin();
|
||||
|
||||
Database db = new Database("database/initializer.sqlite");
|
||||
db.initialize();
|
||||
|
||||
executor.registerCommand("readstudents", new ReadStudentsFileToDB(db));
|
||||
executor.registerCommand("archiveall", new ArchiveRepos());
|
||||
executor.registerCommand("generateassignments", new GenerateAssignmentsRepo());
|
||||
|
||||
logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized.");
|
||||
|
||||
|
||||
|
||||
// Get studentTeams from CSV file.
|
||||
// List<StudentTeam> studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize")));
|
||||
//
|
||||
// GithubManager githubManager = new GithubManager(
|
||||
// userOptions.get("organization"),
|
||||
// userOptions.get("token"),
|
||||
// "assignments_2018",
|
||||
// "teaching-assistants",
|
||||
// "advoop_2018"
|
||||
// );
|
||||
|
||||
// Get teams from CSV file.
|
||||
List<Team> teams;
|
||||
try {
|
||||
teams = TeamGenerator.generateFromCSV(
|
||||
userOptions.get("input"),
|
||||
Integer.parseInt(userOptions.get("teamsize"))
|
||||
);
|
||||
logger.info("Teams created: " + teams);
|
||||
} catch (IOException | ArrayIndexOutOfBoundsException e) {
|
||||
logger.severe("Unable to generate teams from CSV file, exiting.");
|
||||
System.exit(1);
|
||||
//githubManager.initializeGithubRepos(studentTeams);
|
||||
//githubManager.archiveAllRepositories("team");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
package nl.andrewlalis.git_api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.TATeam;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpPatch;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.kohsuke.github.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class is responsible for initializing the Github repositories and setting permissions, adding teams, etc.
|
||||
*/
|
||||
public class GithubManager {
|
||||
|
||||
/**
|
||||
* The assignments repository where students will get assignments from.
|
||||
*/
|
||||
private GHRepository assignmentsRepo;
|
||||
|
||||
/**
|
||||
* Github object for API interactions.
|
||||
*/
|
||||
private GitHub github;
|
||||
private GHOrganization organization;
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* The logger for outputting debug info.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(GithubManager.class.getName());
|
||||
static {
|
||||
logger.setParent(Logger.getGlobal());
|
||||
}
|
||||
|
||||
public GithubManager(String organizationName, String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
try {
|
||||
this.github = GitHub.connectUsingOAuth(accessToken);
|
||||
this.organization = this.github.getOrganization(organizationName);
|
||||
} catch (IOException e) {
|
||||
logger.severe("Unable to make a GithubManager with organization name: " + organizationName + " and access token: " + accessToken);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the github repository for all studentTeams given.
|
||||
*
|
||||
* Creates for the entire organization:
|
||||
* - an assignments repository with protected master branch and TA permissions.
|
||||
* Creates for each team:
|
||||
* - a repository
|
||||
* - protected master branch
|
||||
* - development branch
|
||||
* - adds students to repository
|
||||
* - adds all students to assignments repository.
|
||||
* @param studentTeams The list of student studentTeams.
|
||||
* @param teamAll The team of all teaching assistants.
|
||||
* @param assignmentsRepoName The name of the assignments repo.
|
||||
* @throws Exception If an error occurs while initializing the github repositories.
|
||||
*/
|
||||
public void initializeGithubRepos(List<StudentTeam> studentTeams, TATeam teamAll, String assignmentsRepoName) throws Exception {
|
||||
this.setupAssignmentsRepo(assignmentsRepoName, "fuck the police", teamAll.getName());
|
||||
|
||||
StudentTeam t = new StudentTeam();
|
||||
Student s = new Student(3050831, "Andrew Lalis", "andrewlalisofficial@gmail.com", "andrewlalis", null);
|
||||
t.addMember(s);
|
||||
t.setId(42);
|
||||
|
||||
this.setupStudentTeam(t, teamAll.getGithubTeam(), "advoop_2018");
|
||||
// TODO: Finish this method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the organization's assignments repository, and grants permissions to all teaching assistants.
|
||||
* @param assignmentsRepoName The name of the assignments repository.
|
||||
* @param description The description of the repository.
|
||||
* @param allTeachingAssistants The name of the team consisting of all teaching assistants.
|
||||
* @throws IOException If an HTTP request failed.
|
||||
*/
|
||||
public void setupAssignmentsRepo(String assignmentsRepoName, String description, String allTeachingAssistants) throws IOException {
|
||||
GHTeam team = this.organization.getTeamByName(allTeachingAssistants);
|
||||
// Check if the repository already exists.
|
||||
GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName);
|
||||
if (existingRepo != null) {
|
||||
existingRepo.delete();
|
||||
logger.fine("Deleted pre-existing assignments repository.");
|
||||
}
|
||||
|
||||
this.assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, false);
|
||||
|
||||
if (this.assignmentsRepo == null) {
|
||||
logger.severe("Could not create assignments repository.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.protectMasterBranch(this.assignmentsRepo, team);
|
||||
|
||||
// Grant all teaching assistants write access.
|
||||
team.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN);
|
||||
logger.fine("Gave admin rights to all teaching assistants in team: " + team.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and sets up a student team's repository, and invites those students to the organization's assignments
|
||||
* repository as well.
|
||||
* @param team The student team to set up.
|
||||
* @param taTeam The team of teaching assistants that is responsible for these students.
|
||||
* @param prefix The prefix to append to the front of the repo name.
|
||||
* @throws IOException If an HTTP request fails.
|
||||
*/
|
||||
public void setupStudentTeam(StudentTeam team, GHTeam taTeam, String prefix) throws IOException {
|
||||
// First check that the assignments repo exists, otherwise no invitations can be sent.
|
||||
if (this.assignmentsRepo == null) {
|
||||
logger.warning("Assignments repository must be created before student repositories.");
|
||||
return;
|
||||
}
|
||||
|
||||
GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam, team.generateRepoDescription(), false, true, false);
|
||||
|
||||
if (repo == null) {
|
||||
logger.severe("Repository for student team " + team.getId() + " could not be created.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.protectMasterBranch(repo, taTeam);
|
||||
this.createDevelopmentBranch(repo);
|
||||
|
||||
taTeam.add(repo, GHOrganization.Permission.ADMIN);
|
||||
logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + repo.getName());
|
||||
|
||||
List<GHUser> users = new ArrayList<>();
|
||||
for (Student student : team.getStudents()) {
|
||||
GHUser user = this.github.getUser(student.getGithubUsername());
|
||||
users.add(user);
|
||||
}
|
||||
|
||||
repo.addCollaborators(users);
|
||||
this.assignmentsRepo.addCollaborators(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all repositories in the organization.
|
||||
* @throws IOException if an error occurs with sending requests.
|
||||
*/
|
||||
public void deleteAllRepositories() throws IOException {
|
||||
List<GHRepository> repositories = this.organization.listRepositories().asList();
|
||||
for (GHRepository repo : repositories) {
|
||||
repo.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives all repositories whose name contains the given substring.
|
||||
* @param sub Any repository containing this substring will be archived.
|
||||
*/
|
||||
public void archiveAllRepositories(String sub) throws IOException {
|
||||
List<GHRepository> repositories = this.organization.listRepositories().asList();
|
||||
for (GHRepository repo : repositories) {
|
||||
if (repo.getName().contains(sub)) {
|
||||
archiveRepository(repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives a repository so that it can no longer be manipulated.
|
||||
* TODO: Change to using Github API instead of Apache HttpUtils.
|
||||
* @param repo The repository to archive.
|
||||
* @throws IOException If an error occurs with the HTTP request.
|
||||
*/
|
||||
public void archiveRepository(GHRepository repo) throws IOException {
|
||||
HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken);
|
||||
CloseableHttpClient client = HttpClientBuilder.create().build();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ObjectNode root = mapper.createObjectNode();
|
||||
root.put("archived", true);
|
||||
String json = mapper.writeValueAsString(root);
|
||||
patch.setEntity(new StringEntity(json));
|
||||
HttpResponse response = client.execute(patch);
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode());
|
||||
}
|
||||
logger.info("Archived repository: " + repo.getFullName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Protects the master branch of a given repository, and gives admin rights to the given team.
|
||||
* @param repo The repository to protect the master branch of.
|
||||
* @param team The team which gets admin rights to the master branch.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void protectMasterBranch(GHRepository repo, GHTeam team) {
|
||||
try {
|
||||
GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection();
|
||||
protectionBuilder.includeAdmins(false);
|
||||
protectionBuilder.restrictPushAccess();
|
||||
protectionBuilder.teamPushAccess(team);
|
||||
protectionBuilder.addRequiredChecks("ci/circleci");
|
||||
protectionBuilder.enable();
|
||||
logger.fine("Protected master branch of repository: " + repo.getName());
|
||||
} catch (IOException e) {
|
||||
logger.severe("Could not protect master branch of repository: " + repo.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a development branch for the given repository.
|
||||
* @param repo The repository to create a development branch for.
|
||||
*/
|
||||
private void createDevelopmentBranch(GHRepository repo) {
|
||||
try {
|
||||
String sha1 = repo.getBranch(repo.getDefaultBranch()).getSHA1();
|
||||
repo.createRef("refs/heads/development", sha1);
|
||||
logger.fine("Created development branch of repository: " + repo.getName());
|
||||
} catch (IOException e) {
|
||||
logger.severe("Could not create development branch for repository: " + repo.getName() + '\n' + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new github repository.
|
||||
* @param name The name of the repository.
|
||||
* @param taTeam The team to give admin rights.
|
||||
* @param description The description of the repository.
|
||||
* @param hasWiki Whether the repo has a wiki enabled.
|
||||
* @param hasIssues Whether the repo has issues enabled.
|
||||
* @param isPrivate Whether or not the repository is private.
|
||||
* @return The repository that was created, or
|
||||
*/
|
||||
private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){
|
||||
try {
|
||||
GHCreateRepositoryBuilder builder = this.organization.createRepository(name);
|
||||
builder.team(taTeam);
|
||||
builder.wiki(hasWiki);
|
||||
builder.issues(hasIssues);
|
||||
builder.description(description);
|
||||
builder.gitignoreTemplate("Java");
|
||||
builder.private_(isPrivate); // TODO: Change this to true for production
|
||||
GHRepository repo = builder.create();
|
||||
logger.fine("Created repository: " + repo.getName());
|
||||
return repo;
|
||||
} catch (IOException e) {
|
||||
logger.severe("Could not create repository: " + name + '\n' + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Contains all logic which directly interacts with the Github API. All methods which need to perform an action on a
|
||||
* repository or team should find it within this package.
|
||||
*
|
||||
* @author Andrew Lalis
|
||||
*/
|
||||
package nl.andrewlalis.git_api;
|
|
@ -0,0 +1,89 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
/**
|
||||
* A generic object that students, teaching assistants, and professors can extend from. This covers all the basic
|
||||
* functionality that applies to anyone in the system.
|
||||
*/
|
||||
public abstract class Person {
|
||||
|
||||
/**
|
||||
* The unique identification number for this person. (P- or S-Number)
|
||||
*/
|
||||
protected int number;
|
||||
|
||||
/**
|
||||
* The person's first and last name.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* The person's email address.
|
||||
*/
|
||||
protected String emailAddress;
|
||||
|
||||
/**
|
||||
* The person's github username.
|
||||
*/
|
||||
protected String githubUsername;
|
||||
|
||||
/**
|
||||
* Constructs a Person from all the basic information needed.
|
||||
* @param number Either an S- or P-Number without the letter prefix.
|
||||
* @param name The first, middle (if applicable) and last name.
|
||||
* @param emailAddress The email address. (Either university or personal.)
|
||||
* @param githubUsername The person's github username.
|
||||
*/
|
||||
public Person(int number, String name, String emailAddress, String githubUsername){
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
this.emailAddress = emailAddress;
|
||||
this.githubUsername = githubUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessors
|
||||
*/
|
||||
public int getNumber(){
|
||||
return this.number;
|
||||
}
|
||||
|
||||
public String getName(){
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getEmailAddress(){
|
||||
return this.emailAddress;
|
||||
}
|
||||
|
||||
public String getGithubUsername(){
|
||||
return this.githubUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two persons are the same. This is defined as:
|
||||
* Two persons are equal if at least one of their personal data points is identical. Because each of the data points
|
||||
* should be unique to this person alone, if there is any conflict, assume that they are equal.
|
||||
* @param o The object to compare to.
|
||||
* @return True if the two persons share personal data, or false if all data is unique among the two.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Person)) {
|
||||
return false;
|
||||
}
|
||||
Person p = (Person)o;
|
||||
return p.getNumber() == this.getNumber()
|
||||
|| p.getEmailAddress().equals(this.getEmailAddress())
|
||||
|| p.getGithubUsername().equals(this.getGithubUsername())
|
||||
|| p.getName().equalsIgnoreCase(this.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the person as a basic comma-separated string object.
|
||||
* @return A comma-separated String object.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getName() + ", " + this.getNumber() + ", " + this.getEmailAddress() + ", " + this.getGithubUsername();
|
||||
}
|
||||
}
|
|
@ -1,66 +1,34 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents one student's github information.
|
||||
*/
|
||||
public class Student {
|
||||
|
||||
/**
|
||||
* The student's S-number.
|
||||
*/
|
||||
private int number;
|
||||
|
||||
/**
|
||||
* The student's name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The student's email.
|
||||
*/
|
||||
private String emailAddress;
|
||||
|
||||
/**
|
||||
* The student's github username.
|
||||
*/
|
||||
private String githubUsername;
|
||||
public class Student extends Person {
|
||||
|
||||
/**
|
||||
* A list of partners that the student has said that they would like to be partners with.
|
||||
*/
|
||||
private List<Integer> preferredPartners;
|
||||
|
||||
/**
|
||||
* Constructs a student similarly to a Person, but with an extra preferredPartners list.
|
||||
* @param number The student's S-Number.
|
||||
* @param name The student's name.
|
||||
* @param emailAddress The student's email address.
|
||||
* @param githubUsername The student's github username.
|
||||
* @param preferredPartners A list of this student's preferred partners, as a list of integers representing the
|
||||
* other students' numbers.
|
||||
*/
|
||||
public Student(int number, String name, String emailAddress, String githubUsername, List<Integer> preferredPartners) {
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
this.emailAddress = emailAddress;
|
||||
this.githubUsername = githubUsername;
|
||||
super(number, name, emailAddress, githubUsername);
|
||||
this.preferredPartners = preferredPartners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.number + " - " + this.name + " - " + this.emailAddress + " - " + this.githubUsername;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public String getGithubUsername() {
|
||||
return githubUsername;
|
||||
}
|
||||
|
||||
public List<Integer> getPreferredPartners() {
|
||||
return preferredPartners;
|
||||
return this.preferredPartners;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,23 +36,12 @@ public class Student {
|
|||
* @param studentMap A mapping from student number to student for all students who have signed up.
|
||||
* @return A team with unknown id, comprised of this student's preferred partners.
|
||||
*/
|
||||
public Team getPreferredTeam(Map<Integer, Student> studentMap) {
|
||||
Team t = new Team();
|
||||
public StudentTeam getPreferredTeam(Map<Integer, Student> studentMap) {
|
||||
StudentTeam t = new StudentTeam();
|
||||
for (int partnerNumber : this.getPreferredPartners()) {
|
||||
t.addStudent(studentMap.get(partnerNumber));
|
||||
t.addMember(studentMap.get(partnerNumber));
|
||||
}
|
||||
t.addStudent(this);
|
||||
t.addMember(this);
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object s) {
|
||||
if (!(s instanceof Student)) {
|
||||
return false;
|
||||
}
|
||||
Student student = (Student) s;
|
||||
return student.getNumber() == this.getNumber()
|
||||
|| student.getEmailAddress().equals(this.getEmailAddress())
|
||||
|| student.getGithubUsername().equals(this.getGithubUsername());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents one or more students' collective information.
|
||||
*/
|
||||
public class StudentTeam extends Team{
|
||||
|
||||
public StudentTeam() {
|
||||
super(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of students, casted from the original Person[].
|
||||
* @return An array of Students.
|
||||
*/
|
||||
public Student[] getStudents() {
|
||||
return Arrays.copyOf(this.getMembers(), this.memberCount(), Student[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a team is valid, and ready to be added to the Github organization.
|
||||
* A team is valid if and only if:
|
||||
* - The student count is equal to the team size.
|
||||
* - Each student is unique.
|
||||
* - Each student's preferred partners match all the others.
|
||||
* @param teamSize The preferred size of teams.
|
||||
* @return True if the team is valid, and false otherwise.
|
||||
*/
|
||||
public boolean isValid(int teamSize) {
|
||||
if (this.memberCount() == teamSize) {
|
||||
for (Student studentA : this.getStudents()) {
|
||||
for (Student studentB : this.getStudents()) {
|
||||
if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique name which is intended to be used for the repository name of this team.
|
||||
* @param prefix A prefix to further reduce the chances of duplicate names.
|
||||
* It is suggested to use something like "2018_OOP"
|
||||
* @return A string comprised of the prefix, team id, and student number of each team member.
|
||||
*/
|
||||
public String generateUniqueName(String prefix) {
|
||||
StringBuilder sb = new StringBuilder(prefix);
|
||||
sb.append("_team_").append(this.id);
|
||||
for (Student s : this.getStudents()) {
|
||||
sb.append('_').append(s.getNumber());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a description for the repository, based on the students' names and group number.
|
||||
* @return A description for the students' repository.
|
||||
*/
|
||||
public String generateRepoDescription() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Group ").append(this.id).append(": ");
|
||||
for (int i = 0; i < this.memberCount(); i++) {
|
||||
sb.append(this.getStudents()[i].getName());
|
||||
if (i != this.memberCount()-1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import org.kohsuke.github.GHOrganization;
|
||||
import org.kohsuke.github.GHTeam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a teaching assistant team, which is itself a 'team' in the organization. This class is used for parsing
|
||||
* json from requests to github to get a list of all teams in the organization.
|
||||
*/
|
||||
public class TATeam {
|
||||
|
||||
private List<TeachingAssistant> teachingAssistants;
|
||||
|
||||
/**
|
||||
* The team's display name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The team's unique identifier.
|
||||
*/
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* The Github team associated with this team.
|
||||
*/
|
||||
private GHTeam githubTeam;
|
||||
|
||||
/**
|
||||
* Constructs a team without any teaching assistant members.
|
||||
* @param name The name of the team.
|
||||
* @param id The unique identifier for this team.
|
||||
*/
|
||||
public TATeam(String name, int id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.teachingAssistants = new ArrayList<TeachingAssistant>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a team with a list of teaching assistants that are part of it.
|
||||
* @param teachingAssistants The list of teaching assistants that are part of the team.
|
||||
*/
|
||||
public TATeam(List<TeachingAssistant> teachingAssistants, String name, int id) {
|
||||
this.teachingAssistants = teachingAssistants;
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique identification for this TA team.
|
||||
* @return An integer representing the id of this team.
|
||||
*/
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public GHTeam getGithubTeam() {
|
||||
return this.githubTeam;
|
||||
}
|
||||
|
||||
public void setGithubTeam(GHTeam team) {
|
||||
this.githubTeam = team;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
public class TeachingAssistant extends Person {
|
||||
|
||||
/**
|
||||
* Constructs a Teaching Assistant from all the basic information needed, much like its parent, Person.
|
||||
*
|
||||
* @param number Either an S- or P-Number without the letter prefix.
|
||||
* @param name The first, middle (if applicable) and last name.
|
||||
* @param emailAddress The email address. (Either university or personal.)
|
||||
* @param githubUsername The person's github username.
|
||||
*/
|
||||
public TeachingAssistant(int number, String name, String emailAddress, String githubUsername) {
|
||||
super(number, name, emailAddress, githubUsername);
|
||||
}
|
||||
}
|
|
@ -1,147 +1,153 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents one or more students' collective information.
|
||||
* An abstract Team object from which both Teaching Assistant and Student teams can be built. A Team consists of a list
|
||||
* of members, and a unique identification number.
|
||||
*/
|
||||
public class Team {
|
||||
public abstract class Team {
|
||||
|
||||
/**
|
||||
* The list of students in this team.
|
||||
* An identification number unique to this team alone.
|
||||
*/
|
||||
private List<Student> students;
|
||||
protected int id;
|
||||
|
||||
/**
|
||||
* The team identification number.
|
||||
* A list of members of this team.
|
||||
*/
|
||||
private int id;
|
||||
private List<Person> members;
|
||||
|
||||
public Team() {
|
||||
this.students = new ArrayList<>();
|
||||
this.id = -1;
|
||||
/**
|
||||
* Constructs this team with the given id.
|
||||
* @param id The id to assign to this team.
|
||||
*/
|
||||
public Team(int id) {
|
||||
this.id = id;
|
||||
this.members = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a student is already included in this team.
|
||||
* @param student A student.
|
||||
* @return True if the student is in this team, false otherwise.
|
||||
* @param newId The new id number to assign to this team.
|
||||
*/
|
||||
public boolean hasStudent(Student student) {
|
||||
for (Student s : this.students) {
|
||||
if (s.equals(student)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getStudentCount() {
|
||||
return this.students.size();
|
||||
public void setId(int newId) {
|
||||
this.id = newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return This team's id number.
|
||||
*/
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
/**
|
||||
* Adds a new person to this team, only if they do not exist in this team yet.
|
||||
* @param newMember The new member to add.
|
||||
*/
|
||||
public void addMember(Person newMember) {
|
||||
for (Person person : this.members) {
|
||||
if (person.equals(newMember)) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void setStudents(List<Student> students) {
|
||||
this.students = students;
|
||||
}
|
||||
|
||||
public List<Student> getStudents() {
|
||||
return this.students;
|
||||
this.members.add(newMember);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a student to this team.
|
||||
* @param student The student to add.
|
||||
* @return True if the student could be added, false otherwise.
|
||||
* Removes a person from this team.
|
||||
* @param person The person to remove.
|
||||
*/
|
||||
public boolean addStudent(Student student) {
|
||||
if (!this.hasStudent(student)) {
|
||||
this.students.add(student);
|
||||
public void removeMember(Person person) {
|
||||
this.members.remove(person);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this team contains the given person.
|
||||
* @param person The person to check for.
|
||||
* @return True if the person is a member of this team, false otherwise.
|
||||
*/
|
||||
public boolean containsMember(Person person) {
|
||||
for (Person p : this.members) {
|
||||
if (p.equals(person)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a team is valid, and ready to be added to the Github organization.
|
||||
* A team is valid if and only if:
|
||||
* - The student count is equal to the team size.
|
||||
* - Each student is unique.
|
||||
* - Each student's preferred partners match all the others.
|
||||
* @param teamSize The preferred size of teams.
|
||||
* @return True if the team is valid, and false otherwise.
|
||||
* Sets the team to be comprised of only the members given in the array.
|
||||
* @param people The people which will make up the members of this team.
|
||||
*/
|
||||
public boolean isValid(int teamSize) {
|
||||
if (this.getStudentCount() == teamSize) {
|
||||
List<Integer> encounteredIds = new ArrayList<>();
|
||||
for (Student studentA : this.students) {
|
||||
for (Student studentB : this.students) {
|
||||
if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) {
|
||||
return false;
|
||||
public void setMembers(Person[] people) {
|
||||
this.members = new ArrayList<>(Arrays.asList(people));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of people in this team.
|
||||
* @return A list of people in this team.
|
||||
*/
|
||||
public Person[] getMembers() {
|
||||
Person[] people = new Person[this.memberCount()];
|
||||
this.members.toArray(people);
|
||||
return people;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of people in this team.
|
||||
* @return The number of people in this team.
|
||||
*/
|
||||
public int memberCount() {
|
||||
return this.members.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if another team has the same members as this team.
|
||||
* @param team The team to compare to this team.
|
||||
* @return True if the other team has all the same members as this team.
|
||||
*/
|
||||
public boolean hasSameMembers(Team team) {
|
||||
if (this.memberCount() == team.memberCount()) {
|
||||
for (Person person : this.members) {
|
||||
if (!team.containsMember(person)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique name which is intended to be used for the repository name of this team.
|
||||
* @param prefix A prefix to further reduce the chances of duplicate names.
|
||||
* It is suggested to use something like "2018_OOP"
|
||||
* @return A string comprised of the prefix, team id, and student number of each team member.
|
||||
* Checks if an object is equal to this team. First checks if the other object is a Team, and then if it has the
|
||||
* same id and team size. If both of those conditions are met, then it will check that all team members are the
|
||||
* same.
|
||||
* @param obj The object to check for equality.
|
||||
* @return True if the two objects represent the same team, or false otherwise.
|
||||
*/
|
||||
public String generateUniqueName(String prefix) {
|
||||
StringBuilder sb = new StringBuilder(prefix);
|
||||
sb.append("_team_").append(this.id);
|
||||
for (Student s : this.students) {
|
||||
sb.append('_').append(s.getNumber());
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Team) {
|
||||
Team team = (Team) obj;
|
||||
return team.getId() == this.getId() && this.hasSameMembers(team);
|
||||
}
|
||||
return sb.toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A String containing a line for each member in the team.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("Team: ");
|
||||
sb.append(this.id).append('\n');
|
||||
for (Student s : this.students) {
|
||||
sb.append('\t').append(s.toString()).append('\n');
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Team of ").append(this.memberCount()).append(" members:\tID: ").append(this.id).append('\n');
|
||||
for (Person person : this.members) {
|
||||
sb.append(person.toString()).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if one team is equivalent to another. This is determined by if the two teams are comprised of the same
|
||||
* students, in any order.
|
||||
* @param o The object to compare to this team.
|
||||
* @return True if the teams contain the same students, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Team) {
|
||||
Team t = (Team) o;
|
||||
if (t.getStudentCount() != this.getStudentCount()) {
|
||||
return false;
|
||||
}
|
||||
for (Student s : this.students) {
|
||||
if (!t.hasStudent(s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
package nl.andrewlalis.model.database;
|
||||
|
||||
import nl.andrewlalis.model.*;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This class abstracts many of the functions needed for interaction with the application's SQLite database.
|
||||
*/
|
||||
public class Database {
|
||||
|
||||
private static final int PERSON_TYPE_STUDENT = 0;
|
||||
private static final int PERSON_TYPE_TA = 1;
|
||||
|
||||
private static final int TEAM_TYPE_STUDENT = 0;
|
||||
private static final int TEAM_TYPE_TA = 1;
|
||||
private static final int TEAM_TYPE_TA_ALL = 2;
|
||||
|
||||
private static final int TEAM_NONE = 1000000;
|
||||
private static final int TEAM_TA_ALL = 1000001;
|
||||
|
||||
private static final int ERROR_TYPE_TEAM = 0;
|
||||
private static final int ERROR_TYPE_PERSON = 1;
|
||||
private static final int ERROR_TYPE_SYSTEM = 2;
|
||||
|
||||
/**
|
||||
* The connection needed for all queries.
|
||||
*/
|
||||
private Connection connection;
|
||||
|
||||
/**
|
||||
* The logger for outputting debug info.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(Database.class.getName());
|
||||
static {
|
||||
logger.setParent(Logger.getGlobal());
|
||||
}
|
||||
|
||||
public Database(String databaseFilename) {
|
||||
try {
|
||||
this.connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFilename);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database from the table_init.sql script, which defines the table schema.
|
||||
* Then, inserts some constant starter data from /sql/insert/types.sql.
|
||||
* @return True if successful, false if not.
|
||||
*/
|
||||
public boolean initialize() {
|
||||
List<PreparedStatement> tableStatements = Utils.prepareStatementsFromFile("/sql/table_init.sql", this.connection);
|
||||
for (PreparedStatement statement : tableStatements) {
|
||||
try {
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQLException while executing prepared statement:\n" + statement.toString() + "\nCode: " + e.getErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
logger.fine("Database tables initialized.");
|
||||
List<PreparedStatement> insertStatements = Utils.prepareStatementsFromFile("/sql/insert/types.sql", this.connection);
|
||||
for (PreparedStatement statement : insertStatements) {
|
||||
try {
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQLException while inserting into table:\n" + statement.toString() + "\nCode: " + e.getErrorCode());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
logger.fine("Initial types inserted.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a person in the database.
|
||||
* @param person The person object to store.
|
||||
* @param personType The type of person to store, using a constant defined above.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
private boolean storePerson(Person person, int personType) {
|
||||
try {
|
||||
logger.finest("Storing person: " + person);
|
||||
String sql = "INSERT INTO persons (id, name, email_address, github_username, person_type_id) VALUES (?, ?, ?, ?, ?);";
|
||||
PreparedStatement stmt = this.connection.prepareStatement(sql);
|
||||
stmt.setInt(1, person.getNumber());
|
||||
stmt.setString(2, person.getName());
|
||||
stmt.setString(3, person.getEmailAddress());
|
||||
stmt.setString(4, person.getGithubUsername());
|
||||
stmt.setInt(5, personType);
|
||||
stmt.execute();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQLException while inserting Person: " + person + '\n' + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of preferred partners that each student has set.
|
||||
* @param studentId The student id to search by.
|
||||
* @return A list of student id's for all students that the given student wishes to be their partner.
|
||||
*/
|
||||
private List<Integer> retrievePreferredPartners(int studentId) {
|
||||
try {
|
||||
logger.finest("Retrieving preferred partners of student: " + studentId);
|
||||
String sql = "SELECT partner_id FROM student_preferred_partners WHERE student_id=?;";
|
||||
PreparedStatement stmt = this.connection.prepareStatement(sql);
|
||||
stmt.setInt(1, studentId);
|
||||
ResultSet results = stmt.executeQuery();
|
||||
List<Integer> partners = new ArrayList<>();
|
||||
while (results.next()) {
|
||||
partners.add(results.getInt(1));
|
||||
}
|
||||
return partners;
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQL Exception while retrieving preferred partners of student: " + studentId + '\n' + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a student by their id.
|
||||
* @param id The id of the student (student number)
|
||||
* @return The student corresponding to this number, or null if it could not be found.
|
||||
*/
|
||||
public Student retrieveStudent(int id) {
|
||||
try {
|
||||
String sql = "SELECT * FROM persons WHERE id=?";
|
||||
PreparedStatement stmt = this.connection.prepareStatement(sql);
|
||||
stmt.setInt(1, id);
|
||||
ResultSet result = stmt.executeQuery();
|
||||
return new Student(id, result.getString("name"), result.getString("email_address"), result.getString("github_username"), this.retrievePreferredPartners(id));
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQL Exception while retrieving Student.\n" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a teaching assistant without a team.
|
||||
* @param ta The teaching assistant to store.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
public boolean storeTeachingAssistant(TeachingAssistant ta) {
|
||||
return this.storeTeachingAssistant(ta, TEAM_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a teaching assistant in the database.
|
||||
* @param ta The teaching assistant to store.
|
||||
* @param teamId The teaching assistant's team id.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
public boolean storeTeachingAssistant(TeachingAssistant ta, int teamId) {
|
||||
if (!storePerson(ta, PERSON_TYPE_TA)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String sql = "INSERT INTO teaching_assistants (person_id, team_id) VALUES (?, ?);";
|
||||
PreparedStatement stmt = this.connection.prepareStatement(sql);
|
||||
stmt.setInt(1, ta.getNumber());
|
||||
stmt.setInt(2, teamId);
|
||||
stmt.execute();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQL Exception while inserting TeachingAssistant.\n" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a team in the database.
|
||||
* @param team The team to store.
|
||||
* @param type The type of team that this is.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
public boolean storeTeam(Team team, int type) {
|
||||
try {
|
||||
String sql = "INSERT INTO teams (id, team_type_id) VALUES (?, ?);";
|
||||
PreparedStatement stmt = this.connection.prepareStatement(sql);
|
||||
stmt.setInt(1, team.getId());
|
||||
stmt.setInt(2, type);
|
||||
stmt.execute();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQLException while inserting team: " + team + '\n' + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a list of student teams in the database.
|
||||
* @param teams The list of teams to store.
|
||||
* @return True if successful, or false if an error occurred.
|
||||
*/
|
||||
public boolean storeStudentTeams(List<StudentTeam> teams) {
|
||||
for (StudentTeam team : teams) {
|
||||
if (!this.storeTeam(team, TEAM_TYPE_STUDENT)) {
|
||||
return false;
|
||||
}
|
||||
for (Student student : team.getStudents()) {
|
||||
if (!this.storeStudent(student, team.getId())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a student without a team.
|
||||
* @param student The student to store.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
public boolean storeStudent(Student student) {
|
||||
return this.storeStudent(student, TEAM_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a student in the database.
|
||||
* @param student The student to store.
|
||||
* @param teamId The team id for the team the student is in.
|
||||
* @return True if the operation was successful, false otherwise.
|
||||
*/
|
||||
public boolean storeStudent(Student student, int teamId) {
|
||||
logger.finest("Storing student: " + student);
|
||||
if (!storePerson(student, PERSON_TYPE_STUDENT)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String sql = "INSERT INTO students (person_id, team_id, chose_partner) VALUES (?, ?, ?);";
|
||||
PreparedStatement stmt = this.connection.prepareStatement(sql);
|
||||
stmt.setInt(1, student.getNumber());
|
||||
stmt.setInt(2, teamId);
|
||||
stmt.setInt(3, student.getPreferredPartners().size() > 0 ? 1 : 0);
|
||||
stmt.execute();
|
||||
// Storing partners.
|
||||
String sqlPartner = "INSERT INTO student_preferred_partners (student_id, partner_id) VALUES (?, ?);";
|
||||
PreparedStatement stmtPartner = this.connection.prepareStatement(sqlPartner);
|
||||
for (int partnerId : student.getPreferredPartners()) {
|
||||
stmtPartner.setInt(1, student.getNumber());
|
||||
stmtPartner.setInt(2, partnerId);
|
||||
stmtPartner.execute();
|
||||
}
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQL Exception while inserting Student into database.\n" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package nl.andrewlalis.model.database;
|
||||
|
||||
import nl.andrewlalis.util.FileUtils;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Contains some methods which make database actions much easier.
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
/**
|
||||
* The logger for outputting debug info.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(Utils.class.getName());
|
||||
static {
|
||||
logger.setParent(Logger.getGlobal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an ordered list of prepared statements from a file which contains multiple statements separated by a
|
||||
* semicolon. This method separates those statements into their own strings, and prepares them individually to be
|
||||
* executed later.
|
||||
* @param filename The name of the file which contains the statements.
|
||||
* @param connection The connection to a database; used to prepare statements.
|
||||
* @return An ordered list of prepared statements which are based on the contents of the file provided.
|
||||
*/
|
||||
public static List<PreparedStatement> prepareStatementsFromFile(String filename, Connection connection) {
|
||||
String string = FileUtils.readStringFromFile(filename);
|
||||
if (string == null || string.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
String[] splits = string.split(";");
|
||||
List<PreparedStatement> statements = new ArrayList<>();
|
||||
for (String split : splits) {
|
||||
if (split.trim().length() > 1) {
|
||||
try {
|
||||
statements.add(connection.prepareStatement(split));
|
||||
} catch (SQLException e) {
|
||||
logger.severe("SQLException while preparing a statement:\n" + split + "\nError Code: " + e.getErrorCode() + '\n' + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Contains all objects which form the conceptual basis for this application, such as Students, Teams, and any other
|
||||
* abstract data containers.
|
||||
*
|
||||
* @author Andrew Lalis
|
||||
*/
|
||||
package nl.andrewlalis.model;
|
|
@ -0,0 +1,45 @@
|
|||
package nl.andrewlalis.ui.control;
|
||||
|
||||
import nl.andrewlalis.ui.view.OutputTextPane;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
/**
|
||||
* A custom handler for printing log messages to the user interface text output pane.
|
||||
*/
|
||||
public class OutputTextHandler extends Handler {
|
||||
|
||||
/**
|
||||
* The pane to which this handler writes.
|
||||
*/
|
||||
private OutputTextPane outputPane;
|
||||
|
||||
public OutputTextHandler(OutputTextPane outputPane) {
|
||||
this.outputPane = outputPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord logRecord) {
|
||||
DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||
String dateString = df.format(new Date(logRecord.getMillis()));
|
||||
String sourceLocationString = logRecord.getSourceClassName() + "::" + logRecord.getSourceMethodName();
|
||||
this.outputPane.printStyled(dateString + ' ', "gray_italics");
|
||||
this.outputPane.printStyled(logRecord.getLevel().getName() + ": ", "bold");
|
||||
this.outputPane.printStyled(sourceLocationString + "\n\t", "bold");
|
||||
this.outputPane.printStyled(logRecord.getMessage() + '\n', "smaller");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package nl.andrewlalis.ui.control.command;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Manages parsing an entered string and executing a task based upon information in the command.
|
||||
*/
|
||||
public class CommandExecutor {
|
||||
|
||||
/**
|
||||
* The logger for outputting debug info.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(CommandExecutor.class.getName());
|
||||
static {
|
||||
logger.setParent(Logger.getGlobal());
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of named commands which can be executed.
|
||||
*/
|
||||
private Map<String, Executable> commands;
|
||||
|
||||
public CommandExecutor() {
|
||||
this.commands = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new command to the list of commands which this executor can handle.
|
||||
* @param commandName The name that the command will be found by.
|
||||
* @param executable The executable command that is bound to the given name.
|
||||
*/
|
||||
public void registerCommand(String commandName, Executable executable) {
|
||||
this.commands.put(commandName, executable);
|
||||
logger.fine("Registered command: " + commandName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to execute a command string, or show an error message if an invalid command or argument was entered.
|
||||
* @param commandString The String command and any arguments that go with it.
|
||||
*/
|
||||
public void executeString(String commandString) {
|
||||
String[] words = commandString.trim().split(" ");
|
||||
if (words.length < 1) {
|
||||
logger.warning("No command supplied.");
|
||||
return;
|
||||
}
|
||||
String commandName = words[0];
|
||||
String[] args = new String[words.length - 1];
|
||||
if (words.length > 1) {
|
||||
System.arraycopy(words, 1, args, 0, words.length - 1);
|
||||
}
|
||||
this.executeCommand(commandName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command with the given name, and given arguments.
|
||||
* @param commandName The name of the command. A command must be registered using registerCommand before it can be
|
||||
* called here.
|
||||
* @param args The list of arguments to provide to the command as needed by the executable that was registered.
|
||||
*/
|
||||
public void executeCommand(String commandName, String[] args) {
|
||||
if (this.commands.containsKey(commandName)) {
|
||||
logger.info(commandName + ' ' + Arrays.toString(args));
|
||||
this.commands.get(commandName).execute(args);
|
||||
} else {
|
||||
logger.warning(commandName + " is not a valid command.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package nl.andrewlalis.ui.control.command;
|
||||
|
||||
/**
|
||||
* Classes which implement this interface tell that they may be 'executed', either via command-line, or through the use
|
||||
* of user interface actions.
|
||||
*/
|
||||
public interface Executable {
|
||||
|
||||
/**
|
||||
* Runs this Executable's main functionality.
|
||||
*
|
||||
* @param args The list of arguments supplied to the executable.
|
||||
* @return True if successful, false if an error occurred.
|
||||
*/
|
||||
boolean execute(String[] args);
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package nl.andrewlalis.ui.control.command.executables;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Represents the action archive all repositories with a certain substring in their name.
|
||||
* It takes the following arguments:
|
||||
*
|
||||
* 1. Repo substring to archive by
|
||||
*/
|
||||
public class ArchiveRepos extends GithubExecutable {
|
||||
|
||||
@Override
|
||||
protected boolean executeWithManager(GithubManager manager, String[] args) {
|
||||
if (args.length < 1) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
manager.archiveAllRepositories(args[0]);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package nl.andrewlalis.ui.control.command.executables;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Generates the assignments repository, with the supplied github manager, as well as the following extra arguments:
|
||||
*
|
||||
* 1. The name of the repository.
|
||||
* 2. Description of the repository.
|
||||
* 3. Name of TA team containing all members.
|
||||
*/
|
||||
public class GenerateAssignmentsRepo extends GithubExecutable {
|
||||
|
||||
@Override
|
||||
protected boolean executeWithManager(GithubManager manager, String[] args) {
|
||||
if (args.length < 3) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
manager.setupAssignmentsRepo(args[0], args[1], args[2]);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package nl.andrewlalis.ui.control.command.executables;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
|
||||
public class GenerateStudentRepos extends GithubExecutable {
|
||||
|
||||
@Override
|
||||
protected boolean executeWithManager(GithubManager manager, String[] args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package nl.andrewlalis.ui.control.command.executables;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.ui.control.command.Executable;
|
||||
|
||||
/**
|
||||
* Represents an executable which interacts with github, and therefore needs access to a Github
|
||||
* manager to execute.
|
||||
*
|
||||
* Requires two arguments:
|
||||
* 1. The organization name.
|
||||
* 2. The organization's access token.
|
||||
*/
|
||||
public abstract class GithubExecutable implements Executable {
|
||||
|
||||
@Override
|
||||
public boolean execute(String[] args) {
|
||||
if (args.length < 2) {
|
||||
return false;
|
||||
}
|
||||
String[] extraArgs = new String[args.length-2];
|
||||
System.arraycopy(args, 2, extraArgs, 0, args.length-2);
|
||||
GithubManager manager = new GithubManager(args[0], args[1]);
|
||||
return this.executeWithManager(manager, extraArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command and provides a github manager with which to perform operations.
|
||||
* @param manager The GithubManager used to perform actions on the repositories.
|
||||
* @param args Any additional arguments provided to the executable.
|
||||
* @return True if successful, or false otherwise.
|
||||
*/
|
||||
protected abstract boolean executeWithManager(GithubManager manager, String[] args);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package nl.andrewlalis.ui.control.command.executables;
|
||||
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.Database;
|
||||
import nl.andrewlalis.ui.control.command.Executable;
|
||||
import nl.andrewlalis.util.FileUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Execute this class to read students from a supplied filename and teamsize, and store their
|
||||
* information in the database.
|
||||
* Requires the following arguments:
|
||||
*
|
||||
* 1. filename
|
||||
* 2. teamsize
|
||||
*/
|
||||
public class ReadStudentsFileToDB implements Executable {
|
||||
|
||||
/**
|
||||
* The database used to store the students.
|
||||
*/
|
||||
private Database db;
|
||||
|
||||
public ReadStudentsFileToDB(Database db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean execute(String[] args) {
|
||||
if (args.length < 2) {
|
||||
return false;
|
||||
}
|
||||
String filename = args[0];
|
||||
int teamSize = Integer.parseUnsignedInt(args[1]);
|
||||
List<StudentTeam> teams = FileUtils.getStudentTeamsFromCSV(filename, teamSize);
|
||||
return this.db.storeStudentTeams(teams);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package nl.andrewlalis.ui.control.listeners;
|
||||
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
import nl.andrewlalis.ui.view.InitializerApp;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user performs an action with the intent to archive all repositories.
|
||||
*/
|
||||
public class ArchiveAllListener extends ExecutableListener {
|
||||
|
||||
public ArchiveAllListener(CommandExecutor executor, InitializerApp app) {
|
||||
super(executor, app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
String response = JOptionPane.showInputDialog(this.app, "Enter a substring to archive repositories by.", "Enter a substring", JOptionPane.QUESTION_MESSAGE);
|
||||
if (response != null) {
|
||||
this.executor.executeCommand("archiveall", new String[]{
|
||||
this.app.getOrganizationName(),
|
||||
this.app.getAccessToken(),
|
||||
response
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package nl.andrewlalis.ui.control.listeners;
|
||||
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
|
||||
/**
|
||||
* This Key Listener listens for when the ENTER key is pressed in the command-line text field, and executes the command
|
||||
* when that is the case.
|
||||
*/
|
||||
public class CommandFieldKeyListener implements KeyListener {
|
||||
|
||||
/**
|
||||
* This is responsible for parsing and running entered commands.
|
||||
*/
|
||||
private CommandExecutor executor;
|
||||
|
||||
public CommandFieldKeyListener(CommandExecutor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent keyEvent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent keyEvent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent keyEvent) {
|
||||
if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
JTextField inputField = (JTextField) keyEvent.getComponent();
|
||||
this.executor.executeString(inputField.getText());
|
||||
inputField.setText(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package nl.andrewlalis.ui.control.listeners;
|
||||
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
import nl.andrewlalis.ui.view.InitializerApp;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* An action listener which is pre-set to execute an executable once an action is performed.
|
||||
* Since these are used for the user interface, an instance of the application is passed, for the purpose of providing
|
||||
* a parent component for many popups, and to have access to input fields.
|
||||
*/
|
||||
public abstract class ExecutableListener implements ActionListener {
|
||||
|
||||
/**
|
||||
* The executor, with some registered commands that will be executed by listeners which extend this one.
|
||||
*/
|
||||
protected CommandExecutor executor;
|
||||
|
||||
/**
|
||||
* An instance of the UI application.
|
||||
*/
|
||||
protected InitializerApp app;
|
||||
|
||||
public ExecutableListener(CommandExecutor executor, InitializerApp app) {
|
||||
this.executor = executor;
|
||||
this.app = app;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package nl.andrewlalis.ui.control.listeners;
|
||||
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
import nl.andrewlalis.ui.view.InitializerApp;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user does an action to generate the assignments repository.
|
||||
*/
|
||||
public class GenerateAssignmentsRepoListener extends ExecutableListener {
|
||||
|
||||
public GenerateAssignmentsRepoListener(CommandExecutor executor, InitializerApp app) {
|
||||
super(executor, app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
String repoName = JOptionPane.showInputDialog(this.app, "Enter a name for the assignments repository.", "Repository Name", JOptionPane.QUESTION_MESSAGE);
|
||||
if (repoName != null) {
|
||||
String description = JOptionPane.showInputDialog(this.app, "Enter a description for the repository.", "Repository Description", JOptionPane.QUESTION_MESSAGE);
|
||||
String teamName = JOptionPane.showInputDialog(this.app, "Enter the name of the TA team containing all teaching assistants.", "TA Team Name", JOptionPane.QUESTION_MESSAGE);
|
||||
if (teamName != null) {
|
||||
this.executor.executeCommand("generateassignments", new String[]{
|
||||
this.app.getOrganizationName(),
|
||||
this.app.getAccessToken(),
|
||||
repoName,
|
||||
description,
|
||||
teamName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package nl.andrewlalis.ui.control.listeners;
|
||||
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
import nl.andrewlalis.ui.view.InitializerApp;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user performs an action to read all students from a file, and output the contents to a database.
|
||||
*/
|
||||
public class ReadStudentsFileListener extends ExecutableListener {
|
||||
|
||||
public ReadStudentsFileListener(CommandExecutor executor, InitializerApp app) {
|
||||
super(executor, app);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
int fileResponse = chooser.showOpenDialog(this.app);
|
||||
|
||||
if (fileResponse == JFileChooser.APPROVE_OPTION) {
|
||||
String teamSizeString = JOptionPane.showInputDialog(this.app, "Enter the student team size.", "Team Size", JOptionPane.QUESTION_MESSAGE);
|
||||
if (teamSizeString != null) {
|
||||
this.executor.executeCommand("readstudents", new String[]{
|
||||
chooser.getSelectedFile().getName(),
|
||||
teamSizeString
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
package nl.andrewlalis.ui.view;
|
||||
|
||||
import nl.andrewlalis.ui.control.OutputTextHandler;
|
||||
import nl.andrewlalis.ui.control.command.CommandExecutor;
|
||||
import nl.andrewlalis.ui.control.command.executables.ArchiveRepos;
|
||||
import nl.andrewlalis.ui.control.listeners.ArchiveAllListener;
|
||||
import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener;
|
||||
import nl.andrewlalis.ui.control.listeners.GenerateAssignmentsRepoListener;
|
||||
import nl.andrewlalis.ui.control.listeners.ReadStudentsFileListener;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Represents the main user interface element that is referenced in main to construct the graphic interface.
|
||||
*/
|
||||
public class InitializerApp extends JFrame {
|
||||
|
||||
/**
|
||||
* The window title.
|
||||
*/
|
||||
private static final String FRAME_TITLE = "Github Initializer";
|
||||
|
||||
/**
|
||||
* A default size of the window on startup.
|
||||
*/
|
||||
private static final Dimension SIZE = new Dimension(1000, 600);
|
||||
|
||||
/**
|
||||
* The pane on which general purpose program output is written.
|
||||
*/
|
||||
private OutputTextPane outputTextPane;
|
||||
|
||||
private JTextField organizationField = new JTextField();
|
||||
private JTextField accessTokenField = new JTextField();
|
||||
private JTextField assignmentsRepoField = new JTextField();
|
||||
private JTextField teachingAssistantsField = new JTextField();
|
||||
private JTextField studentRepoField = new JTextField();
|
||||
|
||||
/**
|
||||
* The executor responsible for performing meaningful actions.
|
||||
*/
|
||||
private CommandExecutor executor;
|
||||
|
||||
public InitializerApp(CommandExecutor executor) {
|
||||
this.executor = executor;
|
||||
|
||||
// UI initialization.
|
||||
this.initFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins showing the application
|
||||
*/
|
||||
public void begin() {
|
||||
this.pack();
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the handler which passes logging information to the text pane for display.
|
||||
*/
|
||||
private void initLoggingHandler() {
|
||||
Logger logger = Logger.getGlobal();
|
||||
OutputTextHandler handler = new OutputTextHandler(this.outputTextPane);
|
||||
handler.setLevel(Level.FINE);
|
||||
logger.addHandler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the frame before display should begin.
|
||||
*/
|
||||
private void initFrame() {
|
||||
this.setTitle(FRAME_TITLE);
|
||||
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
this.setPreferredSize(SIZE);
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
mainPanel.add(this.initCommandPanel(), BorderLayout.CENTER);
|
||||
mainPanel.add(this.initRepoPanel(), BorderLayout.WEST);
|
||||
mainPanel.add(this.initGithubManagerPanel(), BorderLayout.EAST);
|
||||
|
||||
this.setContentPane(mainPanel);
|
||||
|
||||
this.initLoggingHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel containing input for all fields needed to connect to github, plus some commonly used buttons
|
||||
* which perform actions, as shortcuts for command actions.
|
||||
*/
|
||||
private JPanel initGithubManagerPanel() {
|
||||
JPanel githubManagerPanel = new JPanel(new BorderLayout());
|
||||
|
||||
// Information input (org name, key, etc.)
|
||||
JPanel infoInputPanel = new JPanel();
|
||||
infoInputPanel.setLayout(new BoxLayout(infoInputPanel, BoxLayout.PAGE_AXIS));
|
||||
|
||||
infoInputPanel.add(generateTextFieldPanel("Organization Name", this.organizationField));
|
||||
this.organizationField.setText("InitializerTesting");
|
||||
infoInputPanel.add(generateTextFieldPanel("Access Token", this.accessTokenField));
|
||||
this.accessTokenField.setText("haha get your own");
|
||||
infoInputPanel.add(generateTextFieldPanel("Assignments Repo Name", this.assignmentsRepoField));
|
||||
this.assignmentsRepoField.setText("assignments_2018");
|
||||
infoInputPanel.add(generateTextFieldPanel("TA-All Team Name", this.teachingAssistantsField));
|
||||
this.teachingAssistantsField.setText("teaching-assistants");
|
||||
infoInputPanel.add(generateTextFieldPanel("Student Repo Prefix", this.studentRepoField));
|
||||
this.studentRepoField.setText("advoop_2018");
|
||||
|
||||
githubManagerPanel.add(infoInputPanel, BorderLayout.NORTH);
|
||||
|
||||
// Common actions panel.
|
||||
JPanel commonActionsPanel = new JPanel();
|
||||
commonActionsPanel.setLayout(new BoxLayout(commonActionsPanel, BoxLayout.PAGE_AXIS));
|
||||
|
||||
JButton archiveAllButton = new JButton("Archive All");
|
||||
archiveAllButton.addActionListener(new ArchiveAllListener(this.executor, this));
|
||||
commonActionsPanel.add(archiveAllButton);
|
||||
|
||||
JButton generateStudentTeamsButton = new JButton("Read teams from file");
|
||||
generateStudentTeamsButton.addActionListener(new ReadStudentsFileListener(this.executor, this));
|
||||
commonActionsPanel.add(generateStudentTeamsButton);
|
||||
|
||||
JButton generateAssignmentsRepoButton = new JButton("Generate Assignments Repo");
|
||||
generateAssignmentsRepoButton.addActionListener(new GenerateAssignmentsRepoListener(this.executor, this));
|
||||
commonActionsPanel.add(generateAssignmentsRepoButton);
|
||||
|
||||
githubManagerPanel.add(commonActionsPanel, BorderLayout.CENTER);
|
||||
|
||||
return githubManagerPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel containing the command prompt field and output text pane.
|
||||
*/
|
||||
private JPanel initCommandPanel() {
|
||||
JPanel commandPanel = new JPanel(new BorderLayout());
|
||||
// Output text pane.
|
||||
this.outputTextPane = new OutputTextPane();
|
||||
JScrollPane scrollPane = new JScrollPane(this.outputTextPane);
|
||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
// Text enter field and label.
|
||||
JPanel textEnterPanel = new JPanel(new BorderLayout());
|
||||
textEnterPanel.setBorder(BorderFactory.createLoweredBevelBorder());
|
||||
textEnterPanel.add(new JLabel("Command:"), BorderLayout.WEST);
|
||||
JTextField commandField = new JTextField();
|
||||
commandField.addKeyListener(new CommandFieldKeyListener(this.executor));
|
||||
textEnterPanel.add(commandField, BorderLayout.CENTER);
|
||||
// Top Label
|
||||
JLabel commandPanelLabel = new JLabel("Program output", SwingConstants.CENTER);
|
||||
|
||||
commandPanel.add(scrollPane, BorderLayout.CENTER);
|
||||
commandPanel.add(textEnterPanel, BorderLayout.SOUTH);
|
||||
commandPanel.add(commandPanelLabel, BorderLayout.NORTH);
|
||||
return commandPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel containing a list of repositories.
|
||||
*/
|
||||
private JPanel initRepoPanel() {
|
||||
JPanel repoPanel = new JPanel();
|
||||
repoPanel.setLayout(new BoxLayout(repoPanel, BoxLayout.PAGE_AXIS));
|
||||
repoPanel.add(new JLabel("Repositories"));
|
||||
repoPanel.add(new JList());
|
||||
return repoPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a text input field panel.
|
||||
* @param labelText The text for the label above the panel.
|
||||
* @param textField A reference to the text field that is used in the panel.
|
||||
* @return A JPanel containing the label and text field.
|
||||
*/
|
||||
private JPanel generateTextFieldPanel(String labelText, JTextField textField) {
|
||||
JPanel newPanel = new JPanel(new BorderLayout());
|
||||
newPanel.add(new JLabel(labelText), BorderLayout.NORTH);
|
||||
newPanel.add(textField);
|
||||
newPanel.setBorder(BorderFactory.createEmptyBorder(5, 2, 5, 2));
|
||||
return newPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the organization name entered in the relevant field.
|
||||
* @return The organization name the user has entered.
|
||||
*/
|
||||
public String getOrganizationName() {
|
||||
return this.organizationField.getText().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the oauth access token entered in the relevant field.
|
||||
* @return The access token the user has entered.
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
return this.accessTokenField.getText().trim();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package nl.andrewlalis.ui.view;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Style;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import java.awt.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A custom JTextPane object which provides easy methods to print to the screen.
|
||||
*/
|
||||
public class OutputTextPane extends JTextPane {
|
||||
|
||||
/**
|
||||
* A list of named styles.
|
||||
*/
|
||||
private Map<String, Style> styles;
|
||||
|
||||
/**
|
||||
* A basic constructor to set default properties on this text pane.
|
||||
*/
|
||||
public OutputTextPane() {
|
||||
this.initStyles();
|
||||
this.setEditable(false);
|
||||
this.setAutoscrolls(true);
|
||||
}
|
||||
|
||||
private void initStyles() {
|
||||
this.styles = new HashMap<>();
|
||||
Style defaultStyle = this.addStyle("default", null);
|
||||
defaultStyle.addAttribute(StyleConstants.FontFamily, "Lucida Consonle");
|
||||
defaultStyle.addAttribute(StyleConstants.FontSize, 12);
|
||||
this.styles.put("default", defaultStyle);
|
||||
|
||||
Style grayItalics = this.addStyle("gray_italics", defaultStyle);
|
||||
grayItalics.addAttribute(StyleConstants.Foreground, new Color(128, 128, 128));
|
||||
grayItalics.addAttribute(StyleConstants.Italic, true);
|
||||
this.styles.put("gray_italics", grayItalics);
|
||||
|
||||
Style bold = this.addStyle("bold", defaultStyle);
|
||||
bold.addAttribute(StyleConstants.Bold, true);
|
||||
this.styles.put("bold", bold);
|
||||
|
||||
Style smaller = this.addStyle("smaller", defaultStyle);
|
||||
smaller.addAttribute(StyleConstants.FontSize, 11);
|
||||
this.styles.put("smaller", smaller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints some text styled with a style that is defined in initStyles.
|
||||
* @param text The text to display.
|
||||
* @param styleName The name of the style to use.
|
||||
*/
|
||||
public void printStyled(String text, String styleName) {
|
||||
StyledDocument doc = this.getStyledDocument();
|
||||
Style style = this.styles.get(styleName);
|
||||
if (style == null) {
|
||||
style = this.styles.get("default");
|
||||
}
|
||||
try {
|
||||
doc.insertString(doc.getLength(), text, style);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a line of text, with a newline character appended at the bottom.
|
||||
* @param text The text to append.
|
||||
*/
|
||||
public void printLine(String text) {
|
||||
StyledDocument doc = this.getStyledDocument();
|
||||
try {
|
||||
doc.insertString(doc.getLength(), (text + '\n'), null);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -67,7 +67,7 @@ public class CommandLine {
|
|||
options.addOption(organizationInput);
|
||||
|
||||
// The maximum team size.
|
||||
Option teamSizeInput = new Option("s", "teamsize", true, "The maximum size of teams to generate.");
|
||||
Option teamSizeInput = new Option("ts", "teamsize", true, "The maximum size of teams to generate.");
|
||||
teamSizeInput.setRequired(false);
|
||||
options.addOption(teamSizeInput);
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package nl.andrewlalis.util;
|
||||
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Contains some methods which come in handy in lots of other places.
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* The logger for outputting debug info.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(FileUtils.class.getName());
|
||||
static {
|
||||
logger.setParent(Logger.getGlobal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the file specified by the filename into a String.
|
||||
* @param filename The filename to read the file of, either relative or absolute.
|
||||
* @return A string containing the file's contents.
|
||||
*/
|
||||
public static String readStringFromFile(String filename) {
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(FileUtils.class.getResourceAsStream(filename)))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a list of students from a CSV file and compiles a list of teams based on their preferred partners.
|
||||
* @param filename The name of the CSV file.
|
||||
* @param teamSize The intended size of teams.
|
||||
* @return A list of student teams.
|
||||
*/
|
||||
public static List<StudentTeam> getStudentTeamsFromCSV(String filename, int teamSize) {
|
||||
List<StudentTeam> studentTeams;
|
||||
try {
|
||||
studentTeams = TeamGenerator.generateFromCSV(filename, teamSize);
|
||||
logger.fine("Teams created:\n" + studentTeams);
|
||||
return studentTeams;
|
||||
} catch (IOException | ArrayIndexOutOfBoundsException e) {
|
||||
logger.severe("Unable to generate studentTeams from CSV file, exiting. " + e.getMessage());
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,17 +14,22 @@ public class Logging {
|
|||
public static void setup(boolean verbose) throws IOException {
|
||||
Logger logger = Logger.getGlobal();
|
||||
|
||||
if (verbose) {
|
||||
logger.setLevel(Level.FINEST);
|
||||
} else {
|
||||
logger.setLevel(Level.INFO);
|
||||
}
|
||||
|
||||
outputFile = new FileHandler("log/latest.txt");
|
||||
outputFile = new FileHandler("log/latest.log");
|
||||
formatter = new SimpleFormatter();
|
||||
|
||||
outputFile.setFormatter(formatter);
|
||||
outputFile.setLevel(Level.FINEST);
|
||||
|
||||
if (verbose) {
|
||||
Handler systemOut = new ConsoleHandler();
|
||||
systemOut.setLevel(Level.ALL);
|
||||
|
||||
logger.addHandler(systemOut);
|
||||
}
|
||||
logger.addHandler(outputFile);
|
||||
logger.setLevel(Level.ALL);
|
||||
|
||||
Logger.getLogger("").setLevel(Level.OFF);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package nl.andrewlalis.util;
|
||||
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.Team;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
|
@ -32,11 +32,11 @@ public class TeamGenerator {
|
|||
* @throws IOException If the file is unable to be read.
|
||||
* @throws IllegalArgumentException If an invalid teamsize is given.
|
||||
*/
|
||||
public static List<Team> generateFromCSV(String filename, int teamSize) throws IOException, IllegalArgumentException {
|
||||
logger.info("Generating teams of size " + teamSize);
|
||||
public static List<StudentTeam> generateFromCSV(String filename, int teamSize) throws IOException, IllegalArgumentException {
|
||||
logger.fine("Generating teams of size " + teamSize);
|
||||
if (teamSize < 1) {
|
||||
logger.severe("Invalid team size.");
|
||||
throw new IllegalArgumentException("Team size must be greater than or equal to 1. Got " + teamSize);
|
||||
throw new IllegalArgumentException("StudentTeam size must be greater than or equal to 1. Got " + teamSize);
|
||||
}
|
||||
logger.fine("Parsing CSV file.");
|
||||
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new FileReader(filename));
|
||||
|
@ -46,8 +46,8 @@ public class TeamGenerator {
|
|||
try {
|
||||
studentMap = readAllStudents(records, teamSize);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
logger.severe("Team size does not match column count in records.");
|
||||
throw new IllegalArgumentException("Team size does not match column count in records.");
|
||||
logger.severe("StudentTeam size does not match column count in records.");
|
||||
throw new IllegalArgumentException("StudentTeam size does not match column count in records.");
|
||||
}
|
||||
|
||||
|
||||
|
@ -72,28 +72,38 @@ public class TeamGenerator {
|
|||
* @param teamSize The preferred maximum size for a team.
|
||||
* @return A list of teams, most of which are of teamSize size.
|
||||
*/
|
||||
private static List<Team> generateAllValidTeams(Map<Integer, Student> studentMap, int teamSize) {
|
||||
private static List<StudentTeam> generateAllValidTeams(Map<Integer, Student> studentMap, int teamSize) {
|
||||
List<Student> singleStudents = new ArrayList<>(studentMap.values());
|
||||
List<Team> teams = new ArrayList<>();
|
||||
List<StudentTeam> studentTeams = new ArrayList<>();
|
||||
|
||||
int teamCount = 1;
|
||||
// For each student, try to make a team from its preferred partners.
|
||||
for (Map.Entry<Integer, Student> e : studentMap.entrySet()) {
|
||||
Team t = e.getValue().getPreferredTeam(studentMap);
|
||||
logger.finest("Checking if student's preferred team is valid: " + t.getStudents());
|
||||
StudentTeam newTeam = e.getValue().getPreferredTeam(studentMap);
|
||||
logger.finest("Checking if student's preferred team is valid:\n" + newTeam);
|
||||
// Check if the team is of a valid size, and is not a duplicate.
|
||||
// Note that at this stage, singles are treated as teams of 1, and thus not valid for any teamSize > 1.
|
||||
if (t.isValid(teamSize) && !teams.contains(t)) {
|
||||
// Note that at this stage, singles are treated as studentTeams of 1, and thus not valid for any teamSize > 1.
|
||||
if (newTeam.isValid(teamSize)) {
|
||||
// We know that the team is valid on its own, so now we check if it has members identical to any team already created.
|
||||
boolean matchFound = false;
|
||||
for (StudentTeam team : studentTeams) {
|
||||
if (newTeam.hasSameMembers(team)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) {
|
||||
// Once we know this team is completely valid, we remove all the students in it from the list of singles.
|
||||
t.setId(teamCount++);
|
||||
singleStudents.removeAll(t.getStudents());
|
||||
teams.add(t);
|
||||
logger.fine("Created team: " + t);
|
||||
newTeam.setId(teamCount++);
|
||||
singleStudents.removeAll(Arrays.asList(newTeam.getStudents()));
|
||||
studentTeams.add(newTeam);
|
||||
logger.fine("Created team:\n" + newTeam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
teams.addAll(mergeSingleStudents(singleStudents, teamSize, teamCount));
|
||||
return teams;
|
||||
studentTeams.addAll(mergeSingleStudents(singleStudents, teamSize, teamCount));
|
||||
return studentTeams;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,21 +114,21 @@ public class TeamGenerator {
|
|||
* @param teamIndex The current number used in assigning an id to the team.
|
||||
* @return A list of teams comprising of single students.
|
||||
*/
|
||||
private static List<Team> mergeSingleStudents(List<Student> singleStudents, int teamSize, int teamIndex) {
|
||||
List<Team> teams = new ArrayList<>();
|
||||
private static List<StudentTeam> mergeSingleStudents(List<Student> singleStudents, int teamSize, int teamIndex) {
|
||||
List<StudentTeam> studentTeams = new ArrayList<>();
|
||||
while (!singleStudents.isEmpty()) {
|
||||
Team t = new Team();
|
||||
StudentTeam t = new StudentTeam();
|
||||
t.setId(teamIndex++);
|
||||
logger.fine("Creating new team of single students: " + t);
|
||||
while (t.getStudentCount() < teamSize && !singleStudents.isEmpty()) {
|
||||
logger.fine("Creating new team of single students:\n" + t);
|
||||
while (t.memberCount() < teamSize && !singleStudents.isEmpty()) {
|
||||
Student s = singleStudents.remove(0);
|
||||
logger.finest("Single student: " + s);
|
||||
t.addStudent(s);
|
||||
t.addMember(s);
|
||||
}
|
||||
teams.add(t);
|
||||
logger.fine("Created team: " + t);
|
||||
studentTeams.add(t);
|
||||
logger.fine("Created team:\n" + t);
|
||||
}
|
||||
return teams;
|
||||
return studentTeams;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
INSERT INTO person_types (id, name)
|
||||
VALUES (0, 'student'),
|
||||
(1, 'teaching-assistant'),
|
||||
(2, 'professor');
|
||||
|
||||
INSERT INTO team_types (id, name)
|
||||
VALUES (0, 'student_team'),
|
||||
(1, 'teaching_assistant_team'),
|
||||
(2, 'all_teaching_assistants'),
|
||||
(3, 'none');
|
||||
|
||||
INSERT INTO teams (id, team_type_id)
|
||||
VALUES (1000000, 3), -- None team for all students or TA's without a team.
|
||||
(1000001, 2); -- Team for all teaching assistants.
|
||||
|
||||
INSERT INTO error_types (id, name)
|
||||
VALUES (0, 'team_error'),
|
||||
(1, 'person_error'),
|
||||
(2, 'system_error');
|
|
@ -0,0 +1,140 @@
|
|||
PRAGMA foreign_keys = TRUE;
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger');
|
||||
PRAGMA writable_schema = 0;
|
||||
VACUUM;
|
||||
|
||||
-- Basic schema design.
|
||||
CREATE TABLE IF NOT EXISTS person_types (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS persons (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email_address TEXT NOT NULL,
|
||||
github_username TEXT NOT NULL UNIQUE,
|
||||
person_type_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (person_type_id)
|
||||
REFERENCES person_types(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS team_types (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_type_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (team_type_id)
|
||||
REFERENCES team_types(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teaching_assistant_teams (
|
||||
team_id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
FOREIGN KEY (team_id)
|
||||
REFERENCES teams(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS student_teams (
|
||||
team_id INTEGER PRIMARY KEY,
|
||||
repository_name TEXT,
|
||||
group_id INTEGER NOT NULL UNIQUE,
|
||||
teaching_assistant_team_id INTEGER,
|
||||
FOREIGN KEY (team_id)
|
||||
REFERENCES teams(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
FOREIGN KEY (teaching_assistant_team_id)
|
||||
REFERENCES teaching_assistant_teams(team_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS students (
|
||||
person_id INTEGER PRIMARY KEY,
|
||||
team_id INTEGER NOT NULL,
|
||||
chose_partner INTEGER NOT NULL,
|
||||
FOREIGN KEY (person_id)
|
||||
REFERENCES persons(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
FOREIGN KEY (team_id)
|
||||
REFERENCES teams(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS student_preferred_partners (
|
||||
student_id INTEGER PRIMARY KEY,
|
||||
partner_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (student_id)
|
||||
REFERENCES students(person_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
UNIQUE (student_id, partner_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teaching_assistants (
|
||||
person_id INTEGER PRIMARY KEY,
|
||||
team_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (person_id)
|
||||
REFERENCES persons(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
FOREIGN KEY (team_id)
|
||||
REFERENCES teams(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- Error queue storage.
|
||||
CREATE TABLE IF NOT EXISTS error_types (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS errors (
|
||||
id INTEGER PRIMARY KEY,
|
||||
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
error_type_id INTEGER NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
FOREIGN KEY (error_type_id)
|
||||
REFERENCES error_types(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS team_errors (
|
||||
error_id INTEGER PRIMARY KEY,
|
||||
team_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (error_id)
|
||||
REFERENCES errors(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
FOREIGN KEY (team_id)
|
||||
REFERENCES teams(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS person_errors (
|
||||
error_id INTEGER PRIMARY KEY,
|
||||
person_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (error_id)
|
||||
REFERENCES errors(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
FOREIGN KEY (person_id)
|
||||
REFERENCES persons(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
Loading…
Reference in New Issue