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