Started database support.

This commit is contained in:
Andrew Lalis 2018-08-18 13:25:18 +02:00
parent e901b0affd
commit b7a89c0946
9 changed files with 394 additions and 32 deletions

View File

@ -1,20 +1,19 @@
package nl.andrewlalis;
import nl.andrewlalis.database.Database;
import nl.andrewlalis.git_api.GithubManager;
import nl.andrewlalis.model.Student;
import nl.andrewlalis.model.StudentTeam;
import nl.andrewlalis.util.CommandLine;
import nl.andrewlalis.util.FileUtils;
import nl.andrewlalis.util.Logging;
import nl.andrewlalis.util.TeamGenerator;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import nl.andrewlalis.util.CommandLine;
/**
* Main program entry point.
*/
@ -28,16 +27,9 @@ public class Main {
Map<String, String> userOptions = CommandLine.parseArgs(args);
// Initialize logger.
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.INFO);
try {
Logging.setup(true); // TODO: Replace true with command line arg.
Handler[] handlers = logger.getHandlers();
for (Handler h : handlers) {
logger.removeHandler(h);
}
logger.setUseParentHandlers(false);
logger.addHandler(handler);
} catch (IOException e) {
logger.severe("Unable to save log to file.");
}
@ -62,6 +54,16 @@ public class Main {
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

@ -0,0 +1,159 @@
package nl.andrewlalis.database;
import nl.andrewlalis.model.Person;
import nl.andrewlalis.model.Student;
import nl.andrewlalis.model.TeachingAssistant;
import nl.andrewlalis.util.FileUtils;
import java.sql.*;
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 = 0;
private static final int TEAM_TA_ALL = 1;
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 and starting data.
* @return True, if successful, false if not.
*/
public boolean initialize() {
String sql = FileUtils.readStringFromFile("/sql/table_init.sql");
String[] commands = sql.split(";");
for (String command : commands) {
logger.finest("Executing command: " + command);
if (command.trim().length() > 1) {
try {
PreparedStatement statement = this.connection.prepareStatement(command);
statement.execute();
} catch (SQLException e) {
logger.severe("SQLException: " + e.getErrorCode());
return false;
}
}
}
logger.fine("Database initialized.");
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 {
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);
return stmt.execute();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
/**
* 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);
return stmt.execute();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
/**
* 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) {
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);
return stmt.execute();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}

View File

@ -14,7 +14,6 @@ import org.kohsuke.github.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
@ -206,21 +205,30 @@ public class GithubManager {
List<GHRepository> repositories = this.organization.listRepositories().asList();
for (GHRepository repo : repositories) {
if (repo.getName().contains(sub)) {
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());
// TODO: archive repository using Github Java API, instead of Apache HttpUtils.
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());
}
}

View File

@ -20,7 +20,7 @@ public class Student extends Person {
* @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.
* other students' numbers.
*/
public Student(int number, String name, String emailAddress, String githubUsername, List<Integer> preferredPartners) {
super(number, name, emailAddress, githubUsername);

View File

@ -0,0 +1,31 @@
package nl.andrewlalis.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Contains some methods which come in handy in lots of other places.
*/
public class FileUtils {
/**
* 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;
}
}
}

View File

@ -14,16 +14,26 @@ 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);
Handler[] handlers = logger.getHandlers();
for (Handler h : handlers) {
logger.removeHandler(h);
}
logger.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
if (verbose) {
handler.setLevel(Level.FINEST);
} else {
handler.setLevel(Level.INFO);
}
logger.addHandler(handler);
outputFile = new FileHandler("log/latest.txt");
formatter = new SimpleFormatter();
outputFile.setFormatter(formatter);
outputFile.setLevel(Level.FINEST);
logger.addHandler(outputFile);
}

View File

@ -0,0 +1,2 @@
INSERT INTO persons (id, name, email_address, github_username, person_type_id)
VALUES (?, ?, ?, ?, ?);

View File

@ -0,0 +1,150 @@
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
);
INSERT INTO person_types (id, name)
VALUES (0, 'student'),
(1, 'teaching-assistant'),
(2, 'professor');
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
);
INSERT INTO team_types (id, name)
VALUES (0, 'student_team'),
(1, 'teaching_assistant_team'),
(2, 'all_teaching_assistants'),
(3, 'none');
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
);
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.
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 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
);
INSERT INTO error_types (id, name)
VALUES (0, 'team_error'),
(1, 'person_error'),
(2, 'system_error');
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
);