teacher-tools/api/source/db.d

111 lines
3.0 KiB
D

module db;
import std.algorithm;
import std.array;
import std.typecons;
import std.conv;
import d2sqlite3;
import slf4d;
import handy_httpd.components.optional;
struct Column {
const string name;
}
Database getDb() {
import std.file;
bool shouldInitDb = !exists("teacher-tools.db");
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
if (d2sqlite3.threadSafe()) {
flags |= SQLITE_OPEN_NOMUTEX;
}
Database db = Database("teacher-tools.db", flags);
db.execute("PRAGMA foreign_keys=ON");
if (shouldInitDb) {
const string authSchema = import("schema/auth.sql");
const string classroomComplianceSchema = import("schema/classroom_compliance.sql");
db.run(authSchema);
db.run(classroomComplianceSchema);
import sample_data;
insertSampleData(db);
info("Initialized database schema.");
}
return db;
}
T[] findAll(T, Args...)(Database db, string query, Args args) {
Statement stmt = db.prepare(query);
stmt.bindAll(args);
ResultRange result = stmt.execute();
return result.map!(row => parseRow!T(row)).array;
}
Optional!T findOne(T, Args...)(Database db, string query, Args args) {
Statement stmt = db.prepare(query);
stmt.bindAll(args);
ResultRange result = stmt.execute();
if (result.empty) return Optional!T.empty;
return Optional!T.of(parseRow!T(result.front));
}
bool canFind(Args...)(Database db, string query, Args args) {
Statement stmt = db.prepare(query);
stmt.bindAll(args);
return !stmt.execute().empty;
}
private string toSnakeCase(string camelCase) {
import std.uni;
if (camelCase.length == 0) return camelCase;
auto app = appender!string;
app ~= toLower(camelCase[0]);
for (int i = 1; i < camelCase.length; i++) {
if (isUpper(camelCase[i])) {
app ~= '_';
app ~= toLower(camelCase[i]);
} else {
app ~= camelCase[i];
}
}
return app[];
}
unittest {
assert(toSnakeCase("testValue") == "test_value");
}
private string[] getColumnNames(T)() {
import std.string : toLower;
alias members = __traits(allMembers, T);
string[members.length] columnNames;
static foreach (i; 0 .. members.length) {
static if (__traits(getAttributes, __traits(getMember, T, members[i])).length > 0) {
columnNames[i] = toLower(__traits(getAttributes, __traits(getMember, T, members[i]))[0].name);
} else {
columnNames[i] = toLower(toSnakeCase(members[i]));
}
}
return columnNames.dup;
}
private string getArgsStr(T)() {
import std.traits : Fields;
alias types = Fields!T;
string argsStr = "";
static foreach (i, type; types) {
argsStr ~= "row.peek!(" ~ type.stringof ~ ")(" ~ i.to!string ~ ")";
static if (i + 1 < types.length) {
argsStr ~= ", ";
}
}
return argsStr;
}
T parseRow(T)(Row row) {
mixin("T t = T(" ~ getArgsStr!T ~ ");");
return t;
}