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

248 lines
7.9 KiB
D
Raw Permalink Normal View History

module account.data_impl_sqlite;
import std.datetime;
import d2sqlite3;
import handy_httpd.components.optional;
import account.data;
import account.model;
import history.model;
import util.sqlite;
import util.money;
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) {
util.sqlite.update(
db,
"INSERT INTO account
(created_at, type, number_suffix, name, currency, description)
VALUES (?, ?, ?, ?, ?, ?)",
Clock.currTime(UTC()).toISOExtString(),
type.id,
numberSuffix,
name,
currency.code,
description
);
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.
const props = AccountCreditCardProperties(account.id, -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()
);
}
}
class SqliteAccountJournalEntryRepository : AccountJournalEntryRepository {
private Database db;
this(Database db) {
this.db = db;
}
Optional!AccountJournalEntry findById(ulong id) {
return util.sqlite.findById(db, "account_journal_entry", &parseEntry, id);
}
AccountJournalEntry insert(
SysTime timestamp,
ulong accountId,
ulong transactionId,
ulong amount,
AccountJournalEntryType type,
Currency currency
) {
util.sqlite.update(
db,
"INSERT INTO account_journal_entry
(timestamp, account_id, transaction_id, amount, type, currency)
VALUES (?, ?, ?, ?, ?, ?)",
timestamp.toISOExtString(),
accountId,
transactionId,
amount,
type,
currency.code
);
ulong id = db.lastInsertRowid();
return findById(id).orElseThrow();
}
void deleteById(ulong id) {
util.sqlite.deleteById(db, "account_journal_entry", id);
}
static AccountJournalEntry parseEntry(Row row) {
string typeStr = row.peek!(string, PeekMode.slice)(5);
AccountJournalEntryType type;
if (typeStr == "CREDIT") {
type = AccountJournalEntryType.CREDIT;
} else if (typeStr == "DEBIT") {
type = AccountJournalEntryType.DEBIT;
} else {
throw new Exception("Invalid account journal entry type: " ~ typeStr);
}
return AccountJournalEntry(
row.peek!ulong(0),
SysTime.fromISOExtString(row.peek!string(1)),
row.peek!ulong(2),
row.peek!ulong(3),
row.peek!ulong(4),
type,
Currency.ofCode(row.peek!(string, PeekMode.slice)(6))
);
}
}