teacher-tools/api/source/db.d

147 lines
4.8 KiB
D

module db;
import std.algorithm;
import std.array;
import std.typecons;
import std.conv;
import std.string : split, strip;
import ddbc;
import slf4d;
import handy_httpd.components.optional;
import handy_httpd;
private DataSource dataSource;
static this() {
import std.process : environment;
string username = environment.get("TEACHER_TOOLS_DB_USERNAME", "teacher-tools-dev");
string password = environment.get("TEACHER_TOOLS_DB_PASSWORD", "testpass");
string dbUrl = environment.get("TEACHER_TOOLS_DB_URL", "postgresql://localhost:5432/teacher-tools-dev");
string connectionStr = dbUrl ~ "?user=" ~ username ~ ",password=" ~ password;
dataSource = createDataSource(connectionStr);
}
Connection getDb() {
return dataSource.getConnection();
}
void initializeSchema() {
info("Initializing database schema.");
Connection conn = getDb();
scope(exit) conn.close();
Statement stmt = conn.createStatement();
scope(exit) stmt.close();
const string AUTH_SCHEMA = import("schema/auth.sql");
const string CLASSROOM_COMPLIANCE_SCHEMA = import("schema/classroom_compliance.sql");
const schemas = [AUTH_SCHEMA, CLASSROOM_COMPLIANCE_SCHEMA];
uint schemaNumber = 1;
foreach (schema; schemas) {
infoF!"Intializing schema #%d."(schemaNumber++);
auto statements = schema.split(";")
.map!(s => strip(s))
.filter!(s => s.length > 0);
uint stmtNumber = 1;
foreach (statementStr; statements) {
infoF!"Executing statement #%d."(stmtNumber++);
try {
stmt.executeUpdate(statementStr);
} catch (SQLException e) {
error(e, "Failed to execute schema statement.");
throw new HttpStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
"Failed to initialize schema. See logs for more info."
);
}
}
}
}
T[] findAll(T, Args...)(
Connection conn,
string query,
T function(DataSetReader) parser,
Args args
) {
PreparedStatement ps = conn.prepareStatement(query);
scope(exit) ps.close();
bindAllArgs(ps, args);
ResultSet rs = ps.executeQuery();
scope(exit) rs.close();
Appender!(T[]) app;
foreach (row; rs) {
app ~= parser(row);
}
return app[];
}
Optional!T findOne(T, Args...)(
Connection conn,
string query,
T function(DataSetReader) parser,
Args args
) {
PreparedStatement ps = conn.prepareStatement(query);
scope(exit) ps.close();
bindAllArgs(ps, args);
ResultSet rs = ps.executeQuery();
scope(exit) rs.close();
if (rs.next()) {
return Optional!T.of(parser(rs));
}
return Optional!T.empty;
}
ulong count(Args...)(Connection conn, string query, Args args) {
return findOne(conn, query, r => r.getUlong(1), args).orElse(0);
}
bool recordExists(Args...)(Connection conn, string query, Args args) {
PreparedStatement ps = conn.prepareStatement(query);
scope(exit) ps.close();
bindAllArgs(ps, args);
ResultSet rs = ps.executeQuery();
scope(exit) rs.close();
return rs.next();
}
ulong insertOne(Args...)(Connection conn, string query, Args args) {
PreparedStatement ps = conn.prepareStatement(query);
scope(exit) ps.close();
bindAllArgs(ps, args);
import std.variant;
Variant insertedId;
int affectedRows = ps.executeUpdate(insertedId);
if (affectedRows != 1) {
throw new Exception("Failed to insert exactly 1 row.");
}
return insertedId.coerce!ulong;
}
int update(Args...)(Connection conn, string query, Args args) {
PreparedStatement ps = conn.prepareStatement(query);
scope(exit) ps.close();
bindAllArgs(ps, args);
return ps.executeUpdate();
}
void bindAllArgs(Args...)(PreparedStatement ps, Args args) {
int idx;
static foreach (i, arg; args) {
idx = i + 1;
static if (is(typeof(arg) == string)) ps.setString(idx, arg);
else static if (is(typeof(arg) == const(string))) ps.setString(idx, arg);
else static if (is(typeof(arg) == bool)) ps.setBoolean(idx, arg);
else static if (is(typeof(arg) == ulong)) ps.setUlong(idx, arg);
else static if (is(typeof(arg) == const(ulong))) ps.setUlong(idx, arg);
else static if (is(typeof(arg) == ushort)) ps.setUshort(idx, arg);
else static if (is(typeof(arg) == const(ushort))) ps.setUshort(idx, arg);
else static if (is(typeof(arg) == int)) ps.setInt(idx, arg);
else static if (is(typeof(arg) == const(int))) ps.setInt(idx, arg);
else static if (is(typeof(arg) == uint)) ps.setUint(idx, arg);
else static if (is(typeof(arg) == const(uint))) ps.setUint(idx, arg);
else static assert(false, "Unsupported argument type: " ~ (typeof(arg).stringof));
}
}