finnow/finnow-api/source/profile/data_impl_sqlite.d

194 lines
5.9 KiB
D

module profile.data_impl_sqlite;
import slf4d;
import handy_httpd.components.optional;
import handy_httpd.components.handler : HttpStatusException;
import handy_httpd.components.response : HttpStatus;
import d2sqlite3;
import profile.data;
import profile.model;
import util.sqlite;
const DEFAULT_USERS_DIR = "users";
/// Profile repository that uses an SQLite3 database file for each profile.
class FileSystemProfileRepository : ProfileRepository {
import std.path;
import std.file;
private const string usersDir;
private const string username;
this(string usersDir, string username) {
this.usersDir = usersDir;
this.username = username;
}
this(string username) {
this(DEFAULT_USERS_DIR, username);
}
Optional!Profile findByName(string name) {
string path = getProfilePath(name);
if (!exists(path)) return Optional!Profile.empty;
return Optional!Profile.of(new Profile(name));
}
Profile createProfile(string name) {
string path = getProfilePath(name);
if (exists(path)) throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Profile already exists.");
if (!exists(getProfilesDir())) mkdir(getProfilesDir());
ProfileDataSource ds = new SqliteProfileDataSource(path);
import std.datetime;
auto propsRepo = ds.getPropertiesRepository();
propsRepo.setProperty("name", name);
propsRepo.setProperty("createdAt", Clock.currTime(UTC()).toISOExtString());
propsRepo.setProperty("user", username);
return new Profile(name);
}
Profile[] findAll() {
string profilesDir = getProfilesDir();
if (!exists(profilesDir)) return [];
Profile[] profiles;
foreach (DirEntry entry; dirEntries(profilesDir, SpanMode.shallow, false)) {
import std.string : endsWith;
const suffix = ".sqlite";
if (endsWith(entry.name, suffix)) {
string profileName = baseName(entry.name, suffix);
profiles ~= new Profile(profileName);
}
}
import std.algorithm.sorting : sort;
sort(profiles);
return profiles;
}
void deleteByName(string name) {
string path = getProfilePath(name);
if (exists(path)) {
std.file.remove(path);
}
}
ProfileDataSource getDataSource(in Profile profile) {
return new SqliteProfileDataSource(getProfilePath(profile.name));
}
private string getProfilesDir() {
return buildPath(this.usersDir, username, "profiles");
}
private string getProfilePath(string name) {
return buildPath(this.usersDir, username, "profiles", name ~ ".sqlite");
}
}
class SqlitePropertiesRepository : PropertiesRepository {
private Database db;
this(Database db) {
this.db = db;
}
Optional!string findProperty(string propertyName) {
return findOne(
db,
"SELECT value FROM profile_property WHERE property = ?",
r => r.peek!string(0),
propertyName
);
}
void setProperty(string name, string value) {
if (findProperty(name).isNull) {
Statement stmt = this.db.prepare("INSERT INTO profile_property (property, value) VALUES (?, ?)");
stmt.bind(1, name);
stmt.bind(2, value);
stmt.execute();
} else {
Statement stmt = this.db.prepare("UPDATE profile_property SET value = ? WHERE property = ?");
stmt.bind(1, value);
stmt.bind(2, name);
stmt.execute();
}
}
void deleteProperty(string name) {
Statement stmt = this.db.prepare("DELETE FROM profile_property WHERE property = ?");
stmt.bind(1, name);
stmt.execute();
}
ProfileProperty[] findAll() {
Statement stmt = this.db.prepare("SELECT * FROM profile_property ORDER BY property ASC");
ResultRange result = stmt.execute();
ProfileProperty[] props;
foreach (Row row; result) {
ProfileProperty prop;
prop.property = row.peek!string("property");
prop.value = row.peek!string("value");
props ~= prop;
}
return props;
}
}
/**
* An SQLite implementation of the ProfileDataSource that uses a single
* database connection to initialize various entity data access objects lazily.
*/
class SqliteProfileDataSource : ProfileDataSource {
import account.data;
import account.data_impl_sqlite;
import transaction.data;
import transaction.data_impl_sqlite;
const SCHEMA = import("schema.sql");
private const string dbPath;
private Database db;
this(string path) {
this.dbPath = path;
import std.file : exists;
bool needsInit = !exists(path);
this.db = Database(path);
if (needsInit) {
infoF!"Initializing database: %s"(dbPath);
db.run(SCHEMA);
}
}
PropertiesRepository getPropertiesRepository() {
return new SqlitePropertiesRepository(db);
}
AccountRepository getAccountRepository() {
return new SqliteAccountRepository(db);
}
AccountJournalEntryRepository getAccountJournalEntryRepository() {
return new SqliteAccountJournalEntryRepository(db);
}
TransactionVendorRepository getTransactionVendorRepository() {
return new SqliteTransactionVendorRepository(db);
}
TransactionCategoryRepository getTransactionCategoryRepository() {
return new SqliteTransactionCategoryRepository(db);
}
TransactionTagRepository getTransactionTagRepository() {
return new SqliteTransactionTagRepository(db);
}
TransactionRepository getTransactionRepository() {
return new SqliteTransactionRepository(db);
}
void doTransaction(void delegate () dg) {
util.sqlite.doTransaction(db, dg);
}
}