Half of features implemented #2

Merged
andrewlalis merged 19 commits from repo_creation into master 2018-08-28 18:26:28 +00:00
14 changed files with 343 additions and 101 deletions
Showing only changes of commit 2d40820025 - Show all commits

View File

@ -6,6 +6,8 @@ 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.ReadStudentsFileToDB;
import nl.andrewlalis.ui.view.InitializerApp;
import nl.andrewlalis.util.CommandLine;
import nl.andrewlalis.util.Logging;
@ -38,18 +40,26 @@ public class Main {
logger.severe("Unable to save log to file.");
}
// 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("test", args1 -> {
System.out.println("TESTING");
return true;
});
executor.registerCommand("readstudents", new ReadStudentsFileToDB(db));
executor.registerCommand("archiveall", new ArchiveRepos());
// Initialize User Interface.
InitializerApp app = new InitializerApp(executor);
logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized.");
app.begin();
logger.info("GithubManager for Github Repositories in Educational Organizations.");
// Get studentTeams from CSV file.
// List<StudentTeam> studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize")));
@ -68,17 +78,6 @@ public class Main {
} catch (Exception e) {
e.printStackTrace();
}
// Initialize database.
// Database db = new Database("database/initializer.sqlite");
// db.initialize();
// for (StudentTeam team : studentTeams) {
// for (Student student : team.getStudents()) {
// db.storeStudent(student);
// }
// }
}

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import jdk.nashorn.internal.ir.annotations.Ignore;
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;
@ -26,18 +27,6 @@ public class GithubManager {
* The assignments repository where students will get assignments from.
*/
private GHRepository assignmentsRepo;
private String assignmentsRepoName;
/**
* The name of the team which contains all teaching assistants.
*/
private String teachingAssistantsTeamName;
/**
* The prefix used to prepend the names of student repositories.
* Should ideally contain the current school year.
*/
private String studentRepoPrefix;
/**
* Github object for API interactions.
@ -54,12 +43,8 @@ public class GithubManager {
logger.setParent(Logger.getGlobal());
}
public GithubManager(String organizationName, String accessToken, String assignmentsRepo, String teachingAssistantsTeamName, String studentRepoPrefix) {
this.assignmentsRepoName = assignmentsRepo;
this.teachingAssistantsTeamName = teachingAssistantsTeamName;
this.studentRepoPrefix = studentRepoPrefix;
public GithubManager(String organizationName, String accessToken) {
this.accessToken = accessToken;
try {
this.github = GitHub.connectUsingOAuth(accessToken);
this.organization = this.github.getOrganization(organizationName);
@ -80,57 +65,59 @@ public class GithubManager {
* - 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) throws Exception {
GHTeam teamAll = this.organization.getTeamByName(this.teachingAssistantsTeamName);
this.setupAssignmentsRepo(teamAll);
public void initializeGithubRepos(List<StudentTeam> studentTeams, TATeam teamAll, String assignmentsRepoName) throws Exception {
this.setupAssignmentsRepo(assignmentsRepoName, "fuck the police", teamAll);
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);
this.setupStudentTeam(t, teamAll, "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 A team consisting of all teaching assistants.
* @throws IOException If an HTTP request failed.
*/
@SuppressWarnings("deprecation")
private void setupAssignmentsRepo(GHTeam allTeachingAssistants) throws IOException {
private void setupAssignmentsRepo(String assignmentsRepoName, String description, TATeam allTeachingAssistants) throws IOException {
// Check if the repository already exists.
GHRepository existingRepo = this.organization.getRepository(this.assignmentsRepoName);
GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName);
if (existingRepo != null) {
existingRepo.delete();
logger.fine("Deleted pre-existing assignments repository.");
}
// Create the repository.
GHCreateRepositoryBuilder builder = this.organization.createRepository(this.assignmentsRepoName);
GHCreateRepositoryBuilder builder = this.organization.createRepository(assignmentsRepoName);
builder.description("Assignments repository for Advanced Object Oriented Programming");
builder.wiki(false);
builder.issues(true);
builder.private_(false); // TODO: Make this true for production.
builder.team(allTeachingAssistants);
builder.team(allTeachingAssistants.getGithubTeam());
builder.gitignoreTemplate("Java");
this.assignmentsRepo = builder.create();
logger.info("Created assignments repository.");
// Protect the master branch.
GHBranchProtectionBuilder protectionBuilder = this.assignmentsRepo.getBranch("master").enableProtection();
protectionBuilder.includeAdmins(false);
protectionBuilder.restrictPushAccess();
protectionBuilder.teamPushAccess(allTeachingAssistants);
protectionBuilder.addRequiredChecks("ci/circleci");
protectionBuilder.enable();
logger.fine("Protected master branch of assignments repository.");
this.assignmentsRepo = this.createRepository(assignmentsRepoName, allTeachingAssistants, description, false, true, false);
if (this.assignmentsRepo == null) {
logger.severe("Could not create assignments repository.");
return;
}
this.protectMasterBranch(this.assignmentsRepo, allTeachingAssistants);
// Grant all teaching assistants write access.
allTeachingAssistants.add(this.assignmentsRepo, GHOrganization.Permission.ADMIN);
allTeachingAssistants.getGithubTeam().add(this.assignmentsRepo, GHOrganization.Permission.ADMIN);
logger.fine("Gave admin rights to all teaching assistants in team: " + allTeachingAssistants.getName());
}
@ -139,44 +126,27 @@ public class GithubManager {
* 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.
*/
@SuppressWarnings("deprecation")
private void setupStudentTeam(StudentTeam team, GHTeam taTeam) throws IOException {
String teamRepoName = team.generateUniqueName(this.studentRepoPrefix);
Student[] students = team.getStudents();
StringBuilder description = new StringBuilder("Group ");
description.append(team.getId()).append(": ");
for (Student s : students) {
description.append(s.getName()).append(' ');
private void setupStudentTeam(StudentTeam team, TATeam 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;
}
GHCreateRepositoryBuilder builder = this.organization.createRepository(teamRepoName);
builder.team(taTeam);
builder.wiki(false);
builder.issues(true);
builder.description(description.toString());
builder.gitignoreTemplate("Java");
builder.private_(false); // TODO: Change this to true for production
GHRepository repo = builder.create();
logger.info("Created repository: " + repo.getName());
GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam, team.generateRepoDescription(), false, true, false);
// Protect the master branch.
GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection();
protectionBuilder.includeAdmins(false);
protectionBuilder.teamPushAccess(taTeam);
protectionBuilder.addRequiredChecks("ci/circleci");
protectionBuilder.enable();
logger.fine("Protected master branch of repository: " + repo.getName());
if (repo == null) {
logger.severe("Repository for student team " + team.getId() + " could not be created.");
return;
}
// Create development branch.
String sha1 = repo.getBranch(repo.getDefaultBranch()).getSHA1();
repo.createRef("refs/heads/development", sha1);
logger.fine("Created development branch of repository: " + repo.getName());
this.protectMasterBranch(repo, taTeam);
this.createDevelopmentBranch(repo);
taTeam.add(repo, GHOrganization.Permission.ADMIN);
taTeam.getGithubTeam().add(repo, GHOrganization.Permission.ADMIN);
logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + repo.getName());
List<GHUser> users = new ArrayList<>();
@ -234,4 +204,68 @@ public class GithubManager {
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, TATeam team) {
try {
GHBranchProtectionBuilder protectionBuilder = repo.getBranch("master").enableProtection();
protectionBuilder.includeAdmins(false);
protectionBuilder.restrictPushAccess();
protectionBuilder.teamPushAccess(team.getGithubTeam());
protectionBuilder.addRequiredChecks("ci/circleci");
protectionBuilder.enable();
logger.fine("Protected master branch of repository: " + repo.getName());
} catch (IOException e) {
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, TATeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){
try {
GHCreateRepositoryBuilder builder = this.organization.createRepository(name);
builder.team(taTeam.getGithubTeam());
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;
}
}
}

View File

@ -52,9 +52,22 @@ public class StudentTeam extends Team{
public String generateUniqueName(String prefix) {
StringBuilder sb = new StringBuilder(prefix);
sb.append("_team_").append(this.id);
for (Student s : (Student[]) this.getMembers()) {
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 (Student s : this.getStudents()) {
sb.append(s.getName()).append(' ');
}
return sb.toString();
}
}

View File

@ -1,8 +1,7 @@
package nl.andrewlalis.model;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHTeam;
import java.util.ArrayList;
import java.util.List;
@ -25,6 +24,11 @@ public class TATeam {
*/
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.
@ -58,4 +62,11 @@ public class TATeam {
return this.name;
}
public GHTeam getGithubTeam() {
return this.githubTeam;
}
public void setGithubTeam(GHTeam team) {
this.githubTeam = team;
}
}

View File

@ -1,9 +1,6 @@
package nl.andrewlalis.model.database;
import nl.andrewlalis.model.Person;
import nl.andrewlalis.model.Student;
import nl.andrewlalis.model.TeachingAssistant;
import nl.andrewlalis.model.Team;
import nl.andrewlalis.model.*;
import java.sql.Connection;
import java.sql.DriverManager;
@ -24,8 +21,8 @@ public class Database {
private static final int TEAM_TYPE_TA = 1;
private static final int TEAM_TYPE_TA_ALL = 2;
private static final int TEAM_NONE = 0;
private static final int TEAM_TA_ALL = 1;
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;
@ -157,6 +154,25 @@ public class Database {
}
}
/**
* 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.

View File

@ -8,6 +8,9 @@ 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 {
/**

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.ui.control.command;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
@ -33,6 +34,7 @@ public class CommandExecutor {
*/
public void registerCommand(String commandName, Executable executable) {
this.commands.put(commandName, executable);
logger.fine("Registered command: " + commandName);
}
/**
@ -40,7 +42,7 @@ public class CommandExecutor {
* @param commandString The String command and any arguments that go with it.
*/
public void executeString(String commandString) {
String[] words = commandString.trim().toLowerCase().split(" ");
String[] words = commandString.trim().split(" ");
if (words.length < 1) {
logger.warning("No command supplied.");
return;
@ -50,9 +52,19 @@ public class CommandExecutor {
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);
logger.info(commandString);
} else {
logger.warning(commandName + " is not a valid command.");
}

View File

@ -0,0 +1,30 @@
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. Organization name
* 2. Access Token
* 3. 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;
}
}
}

View File

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

View File

@ -0,0 +1,36 @@
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.
*/
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);
}
}

View File

@ -15,7 +15,7 @@ public class CommandFieldKeyListener implements KeyListener {
/**
* This is responsible for parsing and running entered commands.
*/
CommandExecutor executor;
private CommandExecutor executor;
public CommandFieldKeyListener(CommandExecutor executor) {
this.executor = executor;

View File

@ -6,6 +6,7 @@ import nl.andrewlalis.ui.control.OutputTextHandler;
import javax.swing.*;
import java.awt.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@ -28,6 +29,13 @@ public class InitializerApp extends JFrame {
*/
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();
private JTextField teamSizeField = new JTextField();
/**
* The executor responsible for performing meaningful actions.
*/
@ -53,7 +61,9 @@ public class InitializerApp extends JFrame {
*/
private void initLoggingHandler() {
Logger logger = Logger.getGlobal();
logger.addHandler(new OutputTextHandler(this.outputTextPane));
OutputTextHandler handler = new OutputTextHandler(this.outputTextPane);
handler.setLevel(Level.FINE);
logger.addHandler(handler);
}
/**
@ -68,12 +78,42 @@ public class InitializerApp extends JFrame {
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("d3699963f23cee85fe44c42f66057acc98c9ec7a");
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");
infoInputPanel.add(generateTextFieldPanel("Team Size", this.teamSizeField));
this.teamSizeField.setText("2");
githubManagerPanel.add(infoInputPanel, BorderLayout.NORTH);
return githubManagerPanel;
}
/**
* @return A JPanel containing the command prompt field and output text pane.
*/
@ -83,7 +123,7 @@ public class InitializerApp extends JFrame {
this.outputTextPane = new OutputTextPane();
JScrollPane scrollPane = new JScrollPane(this.outputTextPane);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// Text enter field and label.
JPanel textEnterPanel = new JPanel(new BorderLayout());
textEnterPanel.setBorder(BorderFactory.createLoweredBevelBorder());
@ -111,4 +151,18 @@ public class InitializerApp extends JFrame {
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;
}
}

View File

@ -47,13 +47,13 @@ public class FileUtils {
* @return A list of student teams.
*/
public static List<StudentTeam> getStudentTeamsFromCSV(String filename, int teamSize) {
List<StudentTeam> studentTeams = null;
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.");
logger.severe("Unable to generate studentTeams from CSV file, exiting. " + e.getMessage());
System.exit(1);
return null;
}

View File

@ -10,8 +10,8 @@ VALUES (0, 'student_team'),
(3, 'none');
INSERT INTO teams (id, team_type_id)
VALUES (0, 3), -- None team for all students or TA's without a team.
(1, 2); -- Team for all teaching assistants.
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'),