finnow/finnow-api/source/transaction/api.d

310 lines
13 KiB
D

module transaction.api;
import handy_http_primitives;
import handy_http_data.json;
import handy_http_handlers.path_handler;
import slf4d;
import std.typecons;
import transaction.model;
import transaction.data;
import transaction.service;
import transaction.dto;
import profile.data;
import profile.service;
import profile.api : PROFILE_PATH;
import account.api;
import util.money;
import util.pagination;
import util.data;
import transaction.search_filters;
// Transactions API
immutable DEFAULT_TRANSACTION_PAGE = PageRequest(1, 10, [Sort("txn.timestamp", SortDir.DESC)]);
@GetMapping(PROFILE_PATH ~ "/transactions")
void handleGetTransactions(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
PageRequest pr = PageRequest.parse(request, DEFAULT_TRANSACTION_PAGE);
auto responsePage = getTransactions(ds, pr);
writeJsonBody(response, responsePage);
}
@GetMapping(PROFILE_PATH ~ "/transactions/search")
void handleSearchTransactions(ref ServerHttpRequest request, ref ServerHttpResponse response) {
import transaction.search_filters : extractSearchParams;
ProfileDataSource ds = getProfileDataSource(request);
PageRequest pr = PageRequest.parse(request, DEFAULT_TRANSACTION_PAGE);
auto page = ds.getTransactionRepository().search(pr, extractSearchParams(request));
writeJsonBody(response, page);
}
@GetMapping(PROFILE_PATH ~ "/transactions/aggregate-data")
void handleGetTransactionAggregateData(ref ServerHttpRequest request, ref ServerHttpResponse response) {
import transaction.search_filters : extractSearchParams;
ProfileDataSource ds = getProfileDataSource(request);
auto aggregateData = ds.getTransactionRepository().getAggregateData(extractSearchParams(request));
writeJsonBody(response, aggregateData);
}
@GetMapping(PROFILE_PATH ~ "/transactions/export")
void handleExportTransactions(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
exportTransactionsToFile(ds, request, response);
}
@GetMapping(PROFILE_PATH ~ "/transactions/:transactionId:ulong")
void handleGetTransaction(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
TransactionDetail txn = getTransaction(ds, getTransactionIdOrThrow(request));
import asdf : serializeToJson;
string jsonStr = serializeToJson(txn);
response.writeBodyString(jsonStr, "application/json");
}
@PostMapping(PROFILE_PATH ~ "/transactions")
void handleAddTransaction(ref ServerHttpRequest request, ref ServerHttpResponse response) {
import asdf : serializeToJson;
auto fullPayload = parseMultipartFilesAndBody!AddTransactionPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
TransactionDetail txn = addTransaction(ds, fullPayload.payload, fullPayload.files);
string jsonStr = serializeToJson(txn);
response.writeBodyString(jsonStr, "application/json");
}
@PutMapping(PROFILE_PATH ~ "/transactions/:transactionId:ulong")
void handleUpdateTransaction(ref ServerHttpRequest request, ref ServerHttpResponse response) {
import asdf : serializeToJson;
ProfileDataSource ds = getProfileDataSource(request);
ulong txnId = getTransactionIdOrThrow(request);
auto fullPayload = parseMultipartFilesAndBody!AddTransactionPayload(request);
TransactionDetail txn = updateTransaction(ds, txnId, fullPayload.payload, fullPayload.files);
string jsonStr = serializeToJson(txn);
response.writeBodyString(jsonStr, "application/json");
}
@DeleteMapping(PROFILE_PATH ~ "/transactions/:transactionId:ulong")
void handleDeleteTransaction(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
ulong txnId = getTransactionIdOrThrow(request);
deleteTransaction(ds, txnId);
}
@GetMapping(PROFILE_PATH ~ "/transaction-tags")
void handleGetAllTags(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
string[] tags = ds.getTransactionTagRepository().findAll();
writeJsonBody(response, tags);
}
private ulong getTransactionIdOrThrow(in ServerHttpRequest request) {
return getPathParamOrThrow!ulong(request, "transactionId");
}
// Vendors API
@GetMapping(PROFILE_PATH ~ "/vendors")
void handleGetVendors(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
TransactionVendor[] vendors = getAllVendors(ds);
writeJsonBody(response, vendors);
}
@GetMapping(PROFILE_PATH ~ "/vendors/:vendorId:ulong")
void handleGetVendor(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
TransactionVendor vendor = getVendor(ds, getVendorId(request));
writeJsonBody(response, vendor);
}
struct VendorPayload {
string name;
string description;
}
@PostMapping(PROFILE_PATH ~ "/vendors")
void handleCreateVendor(ref ServerHttpRequest request, ref ServerHttpResponse response) {
VendorPayload payload = readJsonBodyAs!VendorPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
TransactionVendor vendor = createVendor(ds, payload);
writeJsonBody(response, vendor);
}
@PutMapping(PROFILE_PATH ~ "/vendors/:vendorId:ulong")
void handleUpdateVendor(ref ServerHttpRequest request, ref ServerHttpResponse response) {
VendorPayload payload = readJsonBodyAs!VendorPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
TransactionVendor updated = updateVendor(ds, getVendorId(request), payload);
writeJsonBody(response, updated);
}
@DeleteMapping(PROFILE_PATH ~ "/vendors/:vendorId:ulong")
void handleDeleteVendor(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
deleteVendor(ds, getVendorId(request));
}
private ulong getVendorId(in ServerHttpRequest request) {
return getPathParamOrThrow!ulong(request, "vendorId");
}
// Categories API
@GetMapping(PROFILE_PATH ~ "/categories")
void handleGetCategories(ref ServerHttpRequest request, ref ServerHttpResponse response) {
TransactionCategoryTree[] categories = getCategories(getProfileDataSource(request));
writeJsonBody(response, categories);
}
@GetMapping(PROFILE_PATH ~ "/categories/:categoryId:ulong")
void handleGetCategory(ref ServerHttpRequest request, ref ServerHttpResponse response) {
auto category = getCategory(getProfileDataSource(request), getCategoryId(request));
writeJsonBody(response, category);
}
@GetMapping(PROFILE_PATH ~ "/categories/:categoryId:ulong/children")
void handleGetChildCategories(ref ServerHttpRequest request, ref ServerHttpResponse response) {
auto children = getChildCategories(getProfileDataSource(request), getCategoryId(request));
writeJsonBody(response, children);
}
@GetMapping(PROFILE_PATH ~ "/categories/:categoryId:ulong/balances")
void handleGetCategoryBalances(ref ServerHttpRequest request, ref ServerHttpResponse response) {
bool includeChildren = request.getParamAs!bool("includeChildren", true);
// TODO: Support optional before/after timestamps to limit the scope.
auto balances = getCategoryBalances(
getProfileDataSource(request),
getCategoryId(request),
includeChildren
);
writeJsonBody(response, balances);
}
struct CategoryPayload {
string name;
string description;
string color;
Optional!ulong parentId;
}
@PostMapping(PROFILE_PATH ~ "/categories")
void handleCreateCategory(ref ServerHttpRequest request, ref ServerHttpResponse response) {
CategoryPayload payload = readJsonBodyAs!CategoryPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
auto category = createCategory(ds, payload);
writeJsonBody(response, category);
}
@PutMapping(PROFILE_PATH ~ "/categories/:categoryId:ulong")
void handleUpdateCategory(ref ServerHttpRequest request, ref ServerHttpResponse response) {
CategoryPayload payload = readJsonBodyAs!CategoryPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
ulong categoryId = getCategoryId(request);
auto category = updateCategory(ds, categoryId, payload);
writeJsonBody(response, category);
}
@DeleteMapping(PROFILE_PATH ~ "/categories/:categoryId:ulong")
void handleDeleteCategory(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
ulong categoryId = getCategoryId(request);
deleteCategory(ds, categoryId);
}
private ulong getCategoryId(in ServerHttpRequest request) {
return getPathParamOrThrow!ulong(request, "categoryId");
}
// Drafts & Templates & Recurring Transactions
immutable DEFAULT_DRAFT_PAGE = PageRequest(1, 10, [Sort("draft.id", SortDir.DESC)]);
@GetMapping(PROFILE_PATH ~ "/transaction-drafts")
void handleGetDrafts(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
PageRequest pr = PageRequest.parse(request, DEFAULT_DRAFT_PAGE);
Page!TransactionDraftListItem page = getDrafts(ds, pr);
writeJsonBody(response, page);
}
@GetMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong")
void handleGetDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
TransactionDraftResponse draft = getDraft(ds, getDraftId(request));
import asdf : serializeToJson;
string jsonStr = serializeToJson(draft);
response.writeBodyString(jsonStr, "application/json");
}
@PostMapping(PROFILE_PATH ~ "/transaction-drafts")
void handleAddDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
auto fullPayload = parseMultipartFilesAndBody!TransactionDraftPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
TransactionDraftResponse draft = addDraft(ds, fullPayload.payload, fullPayload.files);
import asdf : serializeToJson;
string jsonStr = serializeToJson(draft);
response.writeBodyString(jsonStr, "application/json");
}
@PutMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong")
void handleUpdateDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
auto fullPayload = parseMultipartFilesAndBody!TransactionDraftPayload(request);
TransactionDraftResponse draft = updateDraft(ds, getDraftId(request), fullPayload.payload, fullPayload.files);
import asdf : serializeToJson;
string jsonStr = serializeToJson(draft);
response.writeBodyString(jsonStr, "application/json");
}
@DeleteMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong")
void handleDeleteDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
ulong draftId = getDraftId(request);
deleteDraft(ds, draftId);
}
@GetMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong/recurring-transactions")
void handleGetDraftRecurringTransactions(
ref ServerHttpRequest request,
ref ServerHttpResponse response
) {
ProfileDataSource ds = getProfileDataSource(request);
ulong draftId = getDraftId(request);
RecurringTransactionResponse[] results = getRecurringTransactionsForDraft(ds, draftId);
writeJsonBody(response, results);
}
private ulong getDraftId(in ServerHttpRequest request) {
return getPathParamOrThrow!ulong(request, "draftId");
}
immutable DEFAULT_RECURRING_TRANSACTIONS_PAGE = PageRequest(1, 10, [Sort("id", SortDir.DESC)]);
@GetMapping(PROFILE_PATH ~ "/recurring-transactions")
void handleGetRecurringTransactions(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
PageRequest pr = PageRequest.parse(request, DEFAULT_RECURRING_TRANSACTIONS_PAGE);
Page!RecurringTransactionResponse page = getRecurringTransactions(ds, pr);
writeJsonBody(response, page);
}
@PostMapping(PROFILE_PATH ~ "/recurring-transactions")
void handlePostRecurringTransaction(ref ServerHttpRequest request, ref ServerHttpResponse response) {
RecurringTransactionPayload payload = readJsonBodyAs!RecurringTransactionPayload(request);
ProfileDataSource ds = getProfileDataSource(request);
RecurringTransactionResponse result = createRecurringTransaction(ds, payload);
writeJsonBody(response, result);
}
@DeleteMapping(PROFILE_PATH ~ "/recurring-transactions/:recurringTransactionId:ulong")
void handleDeleteRecurringTransaction(ref ServerHttpRequest request, ref ServerHttpResponse response) {
ProfileDataSource ds = getProfileDataSource(request);
ulong id = getRecurringTransactionId(request);
deleteRecurringTransaction(ds, id);
}
private ulong getRecurringTransactionId(in ServerHttpRequest request) {
return getPathParamOrThrow!ulong(request, "recurringTransactionId");
}