200 lines
8.4 KiB
D
200 lines
8.4 KiB
D
/**
|
|
* This module defines the API endpoints for dealing with Accounts directly,
|
|
* including any data-transfer objects that are needed.
|
|
*/
|
|
module account.api;
|
|
|
|
import handy_http_primitives;
|
|
import handy_http_data.json;
|
|
import handy_http_handlers.path_handler;
|
|
import std.datetime;
|
|
|
|
import profile.service;
|
|
import profile.data;
|
|
import account.model;
|
|
import account.service;
|
|
import account.dto;
|
|
import util.money;
|
|
import util.pagination;
|
|
import util.data;
|
|
import account.data;
|
|
import attachment.data;
|
|
import attachment.dto;
|
|
|
|
void handleGetAccounts(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
import std.algorithm;
|
|
import std.array;
|
|
auto ds = getProfileDataSource(request);
|
|
auto accounts = ds.getAccountRepository().findAll()
|
|
.map!(a => AccountResponse.of(a, getBalance(ds, a.id))).array;
|
|
writeJsonBody(response, accounts);
|
|
}
|
|
|
|
void handleGetAccount(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
auto ds = getProfileDataSource(request);
|
|
auto account = ds.getAccountRepository().findById(accountId)
|
|
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
|
writeJsonBody(response, AccountResponse.of(account, getBalance(ds, account.id)));
|
|
}
|
|
|
|
void handleCreateAccount(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
auto ds = getProfileDataSource(request);
|
|
AccountCreationPayload payload = readJsonBodyAs!AccountCreationPayload(request);
|
|
// TODO: Validate the account creation payload.
|
|
AccountType type = AccountType.fromId(payload.type);
|
|
Currency currency = Currency.ofCode(payload.currency);
|
|
Account account = ds.getAccountRepository().insert(
|
|
type,
|
|
payload.numberSuffix,
|
|
payload.name,
|
|
currency,
|
|
payload.description
|
|
);
|
|
writeJsonBody(response, AccountResponse.of(account, getBalance(ds, account.id)));
|
|
}
|
|
|
|
void handleUpdateAccount(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
AccountCreationPayload payload = readJsonBodyAs!AccountCreationPayload(request);
|
|
auto ds = getProfileDataSource(request);
|
|
AccountRepository repo = ds.getAccountRepository();
|
|
auto account = repo.findById(accountId).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
|
Account updated = repo.update(accountId, Account(
|
|
account.id,
|
|
account.createdAt,
|
|
account.archived,
|
|
AccountType.fromId(payload.type),
|
|
payload.numberSuffix,
|
|
payload.name,
|
|
Currency.ofCode(payload.currency),
|
|
payload.description
|
|
));
|
|
writeJsonBody(response, AccountResponse.of(updated, getBalance(ds, updated.id)));
|
|
}
|
|
|
|
void handleDeleteAccount(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
auto ds = getProfileDataSource(request);
|
|
ds.getAccountRepository().deleteById(accountId);
|
|
}
|
|
|
|
void handleGetAccountHistory(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamOrThrow!ulong("accountId");
|
|
PageRequest pagination = PageRequest.parse(request, PageRequest(1, 10, [Sort("timestamp", SortDir.DESC)]));
|
|
auto ds = getProfileDataSource(request);
|
|
AccountRepository accountRepo = ds.getAccountRepository();
|
|
auto page = accountRepo.getHistory(accountId, pagination);
|
|
writeHistoryResponse(response, page);
|
|
}
|
|
|
|
private string serializeAccountHistoryItem(in AccountHistoryItemResponse i) {
|
|
import asdf : serializeToJson;
|
|
if (i.type == AccountHistoryItemType.JournalEntry) {
|
|
return serializeToJson(cast(AccountHistoryJournalEntryItemResponse) i);
|
|
} else if (i.type == AccountHistoryItemType.ValueRecord) {
|
|
return serializeToJson(cast(AccountHistoryValueRecordItemResponse) i);
|
|
} else {
|
|
return serializeToJson(i);
|
|
}
|
|
}
|
|
|
|
private void writeHistoryResponse(ref ServerHttpResponse response, in Page!AccountHistoryItemResponse page) {
|
|
// Manual serialization of response due to inheritance structure.
|
|
import asdf;
|
|
import std.json;
|
|
string initialJsonObj = serializeToJson(page);
|
|
JSONValue obj = parseJSON(initialJsonObj);
|
|
obj.object["items"] = JSONValue.emptyArray;
|
|
foreach (item; page.items) {
|
|
string initialItemJson = serializeAccountHistoryItem(item);
|
|
obj.object["items"].array ~= parseJSON(initialItemJson);
|
|
}
|
|
|
|
string jsonStr = obj.toJSON();
|
|
response.writeBodyString(jsonStr, ContentTypes.APPLICATION_JSON);
|
|
}
|
|
|
|
void handleGetTotalBalances(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
auto ds = getProfileDataSource(request);
|
|
auto balances = getTotalBalanceForAllAccounts(ds);
|
|
writeJsonBody(response, balances);
|
|
}
|
|
|
|
// Value records:
|
|
|
|
const PageRequest VALUE_RECORD_DEFAULT_PAGE_REQUEST = PageRequest(1, 10, [Sort("timestamp", SortDir.DESC)]);
|
|
|
|
void handleGetValueRecords(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
auto ds = getProfileDataSource(request);
|
|
scope attachmentRepo = ds.getAttachmentRepository();
|
|
auto page = ds.getAccountValueRecordRepository()
|
|
.findAllByAccountId(accountId, PageRequest.parse(request, VALUE_RECORD_DEFAULT_PAGE_REQUEST))
|
|
.mapTo!()((vr) => AccountValueRecordResponse.of(vr, attachmentRepo));
|
|
writeJsonBody(response, page);
|
|
}
|
|
|
|
void handleGetValueRecord(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
ulong valueRecordId = request.getPathParamAs!ulong("valueRecordId");
|
|
auto ds = getProfileDataSource(request);
|
|
auto attachmentRepo = ds.getAttachmentRepository();
|
|
auto record = ds.getAccountValueRecordRepository().findById(accountId, valueRecordId)
|
|
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
|
writeJsonBody(response, AccountValueRecordResponse.of(record, attachmentRepo));
|
|
}
|
|
|
|
void handleCreateValueRecord(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
ProfileDataSource ds = getProfileDataSource(request);
|
|
Account account = ds.getAccountRepository().findById(accountId)
|
|
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
|
AccountValueRecordRepository valueRecordRepo = ds.getAccountValueRecordRepository();
|
|
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
|
auto fullPayload = parseMultipartFilesAndBody!ValueRecordCreationPayload(request);
|
|
ValueRecordCreationPayload payload = fullPayload.payload;
|
|
SysTime timestamp = SysTime.fromISOExtString(payload.timestamp);
|
|
AccountValueRecordType type = AccountValueRecordType.BALANCE; // TODO: Support more types.
|
|
ulong valueRecordId;
|
|
ds.doTransaction(() {
|
|
AccountValueRecord record = valueRecordRepo.insert(
|
|
timestamp,
|
|
account.id,
|
|
type,
|
|
payload.value,
|
|
account.currency
|
|
);
|
|
foreach (attachment; fullPayload.files) {
|
|
ulong attachmentId = attachmentRepo.save(
|
|
timestamp, attachment.name, attachment.contentType, attachment.content);
|
|
valueRecordRepo.linkAttachment(record.id, attachmentId);
|
|
}
|
|
valueRecordId = record.id;
|
|
});
|
|
writeJsonBody(
|
|
response,
|
|
AccountValueRecordResponse.of(
|
|
valueRecordRepo.findById(accountId, valueRecordId).orElseThrow(),
|
|
attachmentRepo
|
|
)
|
|
);
|
|
}
|
|
|
|
void handleDeleteValueRecord(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
|
ulong accountId = request.getPathParamAs!ulong("accountId");
|
|
ulong valueRecordId = request.getPathParamAs!ulong("valueRecordId");
|
|
ProfileDataSource ds = getProfileDataSource(request);
|
|
AccountValueRecordRepository valueRecordRepo = ds.getAccountValueRecordRepository();
|
|
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
|
AccountValueRecord valueRecord = valueRecordRepo.findById(accountId, valueRecordId)
|
|
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
|
ds.doTransaction(() {
|
|
// First delete all attachments.
|
|
foreach (a; attachmentRepo.findAllByValueRecordId(valueRecord.id)) {
|
|
attachmentRepo.remove(a.id);
|
|
}
|
|
valueRecordRepo.deleteById(accountId, valueRecordId);
|
|
});
|
|
}
|