/** * 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 util.money; import util.pagination; import account.data; /// The data the API provides for an Account entity. struct AccountResponse { import asdf : serdeTransformOut; import util.data; ulong id; string createdAt; bool archived; string type; string numberSuffix; string name; string currency; string description; @serdeTransformOut!serializeOptional Optional!long currentBalance; static AccountResponse of(in Account account, Optional!long currentBalance) { AccountResponse r; r.id = account.id; r.createdAt = account.createdAt.toISOExtString(); r.archived = account.archived; r.type = account.type.id; r.numberSuffix = account.numberSuffix; r.name = account.name; r.currency = account.currency.code.dup; r.description = account.description; r.currentBalance = currentBalance; return r; } } 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))); } // The data provided by a user to create a new account. struct AccountCreationPayload { string type; string numberSuffix; string name; string currency; string description; } 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); } const PageRequest VALUE_RECORD_DEFAULT_PAGE_REQUEST = PageRequest(1, 10, [Sort("timestamp", SortDir.DESC)]); struct AccountValueRecordResponse { ulong id; string timestamp; ulong accountId; string type; long value; Currency currency; static AccountValueRecordResponse of(in AccountValueRecord vr) { return AccountValueRecordResponse( vr.id, vr.timestamp.toISOExtString(), vr.accountId, vr.type, vr.value, vr.currency ); } } void handleGetValueRecords(ref ServerHttpRequest request, ref ServerHttpResponse response) { ulong accountId = request.getPathParamAs!ulong("accountId"); auto ds = getProfileDataSource(request); auto page = ds.getAccountValueRecordRepository() .findAllByAccountId(accountId, PageRequest.parse(request, VALUE_RECORD_DEFAULT_PAGE_REQUEST)) .mapTo!()(&AccountValueRecordResponse.of); 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 record = ds.getAccountValueRecordRepository().findById(accountId, valueRecordId) .orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND)); writeJsonBody(response, AccountValueRecordResponse.of(record)); } struct ValueRecordCreationPayload { string timestamp; string type; long value; } 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)); ValueRecordCreationPayload payload = readJsonBodyAs!ValueRecordCreationPayload(request); SysTime timestamp = SysTime.fromISOExtString(payload.timestamp); AccountValueRecordType type = AccountValueRecordType.BALANCE; // TODO: Support more types. AccountValueRecord record = ds.getAccountValueRecordRepository().insert( timestamp, account.id, type, payload.value, account.currency ); writeJsonBody(response, AccountValueRecordResponse.of(record)); } void handleDeleteValueRecord(ref ServerHttpRequest request, ref ServerHttpResponse response) { ulong accountId = request.getPathParamAs!ulong("accountId"); ulong valueRecordId = request.getPathParamAs!ulong("valueRecordId"); ProfileDataSource ds = getProfileDataSource(request); ds.getAccountValueRecordRepository() .deleteById(accountId, valueRecordId); }