module account.data_impl_sqlite; import std.datetime; import d2sqlite3; import handy_httpd.components.optional; import account.data; import account.model; import money.currency; import history.model; import util.sqlite; class SqliteAccountRepository : AccountRepository { private Database db; this(Database db) { this.db = db; } Optional!Account findById(ulong id) { return findOne( db, "SELECT * FROM account WHERE id = ?", &parseAccount, id ); } Account insert(AccountType type, string numberSuffix, string name, Currency currency, string description) { Statement stmt = db.prepare(q"SQL INSERT INTO account (created_at, type, number_suffix, name, currency, description) VALUES (?, ?, ?, ?, ?, ?) SQL"); stmt.bind(1, Clock.currTime(UTC()).toISOExtString()); stmt.bind(2, type.id); stmt.bind(3, numberSuffix); stmt.bind(4, name); stmt.bind(5, currency.code); stmt.bind(6, description); stmt.execute(); ulong accountId = db.lastInsertRowid(); return findById(accountId).orElseThrow("Couldn't find account!"); } void setArchived(ulong id, bool archived) { util.sqlite.update( db, "UPDATE account SET archived = ? WHERE id = ?", archived ? 1 : 0, id ); } Account update(ulong id, in Account newData) { return doTransaction(db, () { Account oldAccount = this.findById(id).orElseThrow("Account doesn't exist."); bool typeDiff = oldAccount.type != newData.type; bool numberSuffixDiff = oldAccount.numberSuffix != newData.numberSuffix; bool nameDiff = oldAccount.name != newData.name; bool currencyDiff = oldAccount.currency != newData.currency; bool descriptionDiff = oldAccount.description != newData.description; util.sqlite.update( db, q"SQL UPDATE account SET type = ?, number_suffix = ?, name = ?, currency = ?, description = ? WHERE id = ? SQL", newData.type.id, newData.numberSuffix, newData.name, newData.currency.code, newData.description ); return this.findById(id).orElseThrow("Account doesn't exist"); }); } void deleteById(ulong id) { doTransaction(db, () { util.sqlite.update( db, "DELETE FROM history WHERE id IN ( SELECT history_id FROM account_history WHERE account_id = ? )", id ); util.sqlite.update(db, "DELETE FROM account WHERE id = ?", id); }); } Account[] findAll() { return util.sqlite.findAll(db, "SELECT * FROM account", &parseAccount); } AccountCreditCardProperties getCreditCardProperties(ulong id) { Account account = findById(id).orElseThrow("Account doesn't exist."); if (account.type != AccountTypes.CREDIT_CARD) throw new Exception("Account is not credit card."); auto optionalProps = findOne( db, "SELECT * FROM account_credit_card_properties WHERE account_id = ?", &parseCreditCardProperties, id ); if (!optionalProps.isNull) return optionalProps.value; // No properties exist, so set them and return the new data. AccountCreditCardProperties props; props.account_id = account.id; props.creditLimit = -1; util.sqlite.update( db, "INSERT INTO account_credit_card_properties (account_id, credit_limit) VALUES (?, ?)", props.account_id, props.creditLimit ); return props; } void setCreditCardProperties(ulong id, in AccountCreditCardProperties props) { bool hasProps = exists(db, "SELECT * FROM account_credit_card_properties WHERE account_id = ?", id); if (hasProps) { util.sqlite.update( db, "UPDATE account_credit_card_properties SET credit_limit = ? WHERE account_id = ?", props.creditLimit, id ); } else { util.sqlite.update( db, "INSERT INTO account_credit_card_properties (account_id, credit_limit) VALUES (?, ?)", id, props.creditLimit ); } } History getHistory(ulong id) { if (!exists(db, "SELECT id FROM account WHERE id = ?", id)) { throw new Exception("Account doesn't exist."); } Optional!History history = findOne( db, q"SQL SELECT * FROM history LEFT JOIN account_history ah ON ah.history_id = history.id WHERE ah.account_id = ? SQL", r => History(r.peek!ulong(0)), id ); if (!history.empty) { return history.value; } // No history exists yet, so add it. ulong historyId = doTransaction(db, () { util.sqlite.update(db, "INSERT INTO history DEFAULT VALUES"); ulong historyId = db.lastInsertRowid(); util.sqlite.update(db, "INSERT INTO account_history (account_id, history_id) VALUES (?, ?)", id, historyId); return historyId; }); return History(historyId); } static Account parseAccount(Row row) { return Account( row.peek!ulong(0), SysTime.fromISOExtString(row.peek!string(1)), row.peek!bool(2), AccountType.fromId(row.peek!string(3)), row.peek!string(4), row.peek!string(5), Currency.ofCode(row.peek!string(6)), row.peek!string(7) ); } static AccountCreditCardProperties parseCreditCardProperties(Row row) { import std.typecons : Nullable; ulong accountId = row.peek!ulong(0); Nullable!ulong creditLimit = row.peek!ulong(1); return AccountCreditCardProperties( accountId, creditLimit.isNull ? -1 : creditLimit.get() ); } }