diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/api/ExchangeApiController.java b/src/main/java/nl/andrewl/coyotecredit/ctl/api/ExchangeApiController.java index 7356432..358d533 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/api/ExchangeApiController.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/api/ExchangeApiController.java @@ -1,7 +1,10 @@ package nl.andrewl.coyotecredit.ctl.api; import lombok.RequiredArgsConstructor; +import nl.andrewl.coyotecredit.ctl.exchange.dto.PublicAccountData; +import nl.andrewl.coyotecredit.model.User; import nl.andrewl.coyotecredit.service.ExchangeService; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,7 +19,12 @@ public class ExchangeApiController { private final ExchangeService exchangeService; @GetMapping(path = "/tradeables") - public Map getCurrentTradeables(@PathVariable long exchangeId) { - return exchangeService.getCurrentTradeables(exchangeId); + public Map getCurrentTradeables(@PathVariable long exchangeId, @AuthenticationPrincipal User user) { + return exchangeService.getCurrentTradeables(exchangeId, user); + } + + @GetMapping(path = "/accounts/{number}") + public PublicAccountData getAccountData(@PathVariable long exchangeId, @PathVariable String number, @AuthenticationPrincipal User user) { + return exchangeService.getPublicAccountData(exchangeId, number, user); } } diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/AccountPage.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/AccountPage.java similarity index 91% rename from src/main/java/nl/andrewl/coyotecredit/ctl/AccountPage.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/AccountPage.java index 9696ea5..506f472 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/AccountPage.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/AccountPage.java @@ -1,7 +1,7 @@ -package nl.andrewl.coyotecredit.ctl; +package nl.andrewl.coyotecredit.ctl.exchange; import lombok.RequiredArgsConstructor; -import nl.andrewl.coyotecredit.ctl.dto.TransferPayload; +import nl.andrewl.coyotecredit.ctl.exchange.dto.TransferPayload; import nl.andrewl.coyotecredit.model.User; import nl.andrewl.coyotecredit.service.AccountService; import org.springframework.http.HttpStatus; @@ -45,7 +45,7 @@ public class AccountPage { @GetMapping(path = "/transfer") public String getTransferPage(Model model, @PathVariable long accountId, @AuthenticationPrincipal User user) { - model.addAttribute("balances", accountService.getTransferData(accountId, user)); + model.addAttribute("data", accountService.getTransferData(accountId, user)); return "account/transfer"; } diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/TradePage.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/TradePage.java similarity index 89% rename from src/main/java/nl/andrewl/coyotecredit/ctl/TradePage.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/TradePage.java index e29372a..ae37323 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/TradePage.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/TradePage.java @@ -1,7 +1,7 @@ -package nl.andrewl.coyotecredit.ctl; +package nl.andrewl.coyotecredit.ctl.exchange; import lombok.RequiredArgsConstructor; -import nl.andrewl.coyotecredit.ctl.dto.TradePayload; +import nl.andrewl.coyotecredit.ctl.exchange.dto.TradePayload; import nl.andrewl.coyotecredit.model.User; import nl.andrewl.coyotecredit.service.ExchangeService; import nl.andrewl.coyotecredit.service.TradeService; @@ -26,7 +26,7 @@ public class TradePage { @AuthenticationPrincipal User user ) { model.addAttribute("data", tradeService.getTradeData(accountId, user)); - return "trade"; + return "account/trade"; } @PostMapping diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/BalanceData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/BalanceData.java similarity index 63% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/BalanceData.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/BalanceData.java index c3ec7b0..773efda 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/BalanceData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/BalanceData.java @@ -1,4 +1,4 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; public record BalanceData( long id, diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/ExchangeAccountData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/ExchangeAccountData.java index b33f7a6..f67c066 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/ExchangeAccountData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/ExchangeAccountData.java @@ -1,7 +1,5 @@ package nl.andrewl.coyotecredit.ctl.exchange.dto; -import nl.andrewl.coyotecredit.ctl.dto.SimpleAccountData; - public record ExchangeAccountData( ExchangeData exchange, SimpleAccountData account diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/FullAccountData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/FullAccountData.java similarity index 83% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/FullAccountData.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/FullAccountData.java index 37ce7cb..9eeb86e 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/FullAccountData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/FullAccountData.java @@ -1,6 +1,4 @@ -package nl.andrewl.coyotecredit.ctl.dto; - -import nl.andrewl.coyotecredit.ctl.exchange.dto.ExchangeData; +package nl.andrewl.coyotecredit.ctl.exchange.dto; import java.util.List; diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/PublicAccountData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/PublicAccountData.java new file mode 100644 index 0000000..f923e4d --- /dev/null +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/PublicAccountData.java @@ -0,0 +1,10 @@ +package nl.andrewl.coyotecredit.ctl.exchange.dto; + +/** + * Account data that can be publicly seen by any member of an exchange. + */ +public record PublicAccountData( + long id, + String number, + String name +) {} diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/SimpleAccountData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/SimpleAccountData.java similarity index 73% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/SimpleAccountData.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/SimpleAccountData.java index 3a54c8a..ee5c692 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/SimpleAccountData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/SimpleAccountData.java @@ -1,4 +1,4 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; public record SimpleAccountData ( long id, diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TradeData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TradeData.java similarity index 73% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/TradeData.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TradeData.java index 434fc9e..2d33158 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TradeData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TradeData.java @@ -1,4 +1,6 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; + +import nl.andrewl.coyotecredit.ctl.dto.TradeableData; import java.math.BigDecimal; import java.util.List; diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TradePayload.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TradePayload.java similarity index 93% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/TradePayload.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TradePayload.java index 0e1d0f8..c8c1f83 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TradePayload.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TradePayload.java @@ -1,4 +1,4 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; /** * The payload that's sent when a user performs a trade with the market. This diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransactionData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransactionData.java similarity index 84% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransactionData.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransactionData.java index d8bca50..cba86a9 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransactionData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransactionData.java @@ -1,5 +1,6 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; +import nl.andrewl.coyotecredit.ctl.dto.TradeableData; import nl.andrewl.coyotecredit.model.Transaction; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferData.java similarity index 84% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferData.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferData.java index 7b4b9b3..ccbf4ac 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferData.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferData.java @@ -1,5 +1,6 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; +import nl.andrewl.coyotecredit.ctl.dto.TradeableData; import nl.andrewl.coyotecredit.model.Transfer; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferPageData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferPageData.java new file mode 100644 index 0000000..8e4bf8b --- /dev/null +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferPageData.java @@ -0,0 +1,9 @@ +package nl.andrewl.coyotecredit.ctl.exchange.dto; + +import java.util.List; + +public record TransferPageData( + long exchangeId, + List balances +) { +} diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferPayload.java b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferPayload.java similarity index 70% rename from src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferPayload.java rename to src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferPayload.java index ac7a16e..36b7582 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferPayload.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/exchange/dto/TransferPayload.java @@ -1,4 +1,4 @@ -package nl.andrewl.coyotecredit.ctl.dto; +package nl.andrewl.coyotecredit.ctl.exchange.dto; public record TransferPayload ( String recipientNumber, diff --git a/src/main/java/nl/andrewl/coyotecredit/dao/AccountRepository.java b/src/main/java/nl/andrewl/coyotecredit/dao/AccountRepository.java index 9ffee97..4eab4e8 100644 --- a/src/main/java/nl/andrewl/coyotecredit/dao/AccountRepository.java +++ b/src/main/java/nl/andrewl/coyotecredit/dao/AccountRepository.java @@ -14,6 +14,7 @@ public interface AccountRepository extends JpaRepository { List findAllByUser(User user); Optional findByNumber(String number); Optional findByUserAndExchange(User user, Exchange exchange); + Optional findByNumberAndExchange(String number, Exchange exchange); boolean existsByUserAndExchange(User user, Exchange exchange); List findAllByExchange(Exchange e); diff --git a/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java b/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java index b39fec6..aa5b972 100644 --- a/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java +++ b/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java @@ -2,7 +2,7 @@ package nl.andrewl.coyotecredit.service; import lombok.RequiredArgsConstructor; import nl.andrewl.coyotecredit.ctl.dto.*; -import nl.andrewl.coyotecredit.ctl.exchange.dto.ExchangeData; +import nl.andrewl.coyotecredit.ctl.exchange.dto.*; import nl.andrewl.coyotecredit.dao.*; import nl.andrewl.coyotecredit.model.*; import nl.andrewl.coyotecredit.util.AccountNumberUtils; @@ -34,13 +34,13 @@ public class AccountService { private final UserNotificationRepository notificationRepository; @Transactional(readOnly = true) - public List getTransferData(long accountId, User user) { + public TransferPageData getTransferData(long accountId, User user) { Account account = accountRepository.findById(accountId) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); if (!account.getUser().getId().equals(user.getId())) { throw new ResponseStatusException(HttpStatus.NOT_FOUND); } - return account.getBalances().stream() + List balances = account.getBalances().stream() .filter(b -> b.getAmount().compareTo(BigDecimal.ZERO) > 0) .map(b -> new BalanceData( b.getTradeable().getId(), @@ -50,6 +50,7 @@ public class AccountService { )) .sorted(Comparator.comparing(BalanceData::symbol)) .toList(); + return new TransferPageData(account.getExchange().getId(), balances); } @Transactional diff --git a/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java b/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java index 78ede28..9c9103a 100644 --- a/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java +++ b/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java @@ -103,6 +103,18 @@ public class ExchangeService { .toList(); } + @Transactional(readOnly = true) + public PublicAccountData getPublicAccountData(long exchangeId, String number, User user) { + Exchange exchange = exchangeRepository.findById(exchangeId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + if (!accountRepository.existsByUserAndExchange(user, exchange)) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + Account account = accountRepository.findByNumberAndExchange(number, exchange) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + return new PublicAccountData(account.getId(), account.getNumber(), account.getName()); + } + @Transactional(readOnly = true) public void ensureAdminAccount(long exchangeId, User user) { getExchangeIfAdmin(exchangeId, user); @@ -168,9 +180,12 @@ public class ExchangeService { } @Transactional(readOnly = true) - public Map getCurrentTradeables(long exchangeId) { + public Map getCurrentTradeables(long exchangeId, User user) { Exchange e = exchangeRepository.findById(exchangeId) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + if (!accountRepository.existsByUserAndExchange(user, e)) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } Map tradeables = new HashMap<>(); for (var t : e.getAllTradeables()) { tradeables.put(t.getId(), t.getMarketPriceUsd().toPlainString()); diff --git a/src/main/java/nl/andrewl/coyotecredit/service/TradeService.java b/src/main/java/nl/andrewl/coyotecredit/service/TradeService.java index 37cff7f..083d187 100644 --- a/src/main/java/nl/andrewl/coyotecredit/service/TradeService.java +++ b/src/main/java/nl/andrewl/coyotecredit/service/TradeService.java @@ -1,7 +1,7 @@ package nl.andrewl.coyotecredit.service; import lombok.RequiredArgsConstructor; -import nl.andrewl.coyotecredit.ctl.dto.TradeData; +import nl.andrewl.coyotecredit.ctl.exchange.dto.TradeData; import nl.andrewl.coyotecredit.ctl.dto.TradeableData; import nl.andrewl.coyotecredit.dao.AccountRepository; import nl.andrewl.coyotecredit.model.Account; diff --git a/src/main/java/nl/andrewl/coyotecredit/service/TradeableUpdateService.java b/src/main/java/nl/andrewl/coyotecredit/service/TradeableUpdateService.java index b87eb9c..e98c531 100644 --- a/src/main/java/nl/andrewl/coyotecredit/service/TradeableUpdateService.java +++ b/src/main/java/nl/andrewl/coyotecredit/service/TradeableUpdateService.java @@ -9,6 +9,7 @@ import nl.andrewl.coyotecredit.dao.TradeableRepository; import nl.andrewl.coyotecredit.model.Tradeable; import nl.andrewl.coyotecredit.model.TradeableType; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -32,6 +33,7 @@ import java.util.concurrent.TimeUnit; @Slf4j public class TradeableUpdateService { private final TradeableRepository tradeableRepository; + private final Environment environment; private final HttpClient httpClient = HttpClient.newHttpClient(); private final ObjectMapper objectMapper = new ObjectMapper(); @@ -57,7 +59,11 @@ public class TradeableUpdateService { for (var tradeable : publicTradeables) { // Special case of ignoring USD as the universal transfer currency. if (tradeable.getSymbol().equals("USD")) continue; - executorService.schedule(() -> updateTradeable(tradeable), delay, TimeUnit.SECONDS); + if (environment.getProperty("coyote-credit.enable-tradeable-updates", Boolean.TYPE, true)) { + executorService.schedule(() -> updateTradeable(tradeable), delay, TimeUnit.SECONDS); + } else { + log.info("Tradeable update skipped for {}.", tradeable.getSymbol()); + } delay += POLYGON_API_TIMEOUT; } } diff --git a/src/main/resources/application-development.properties b/src/main/resources/application-development.properties index f7edcc9..e033f73 100644 --- a/src/main/resources/application-development.properties +++ b/src/main/resources/application-development.properties @@ -10,3 +10,4 @@ spring.mail.host=127.0.0.1 spring.mail.port=1025 coyote-credit.base-url=http://localhost:8080 +coyote-credit.enable-tradeable-updates=false diff --git a/src/main/resources/static/js/transfer.js b/src/main/resources/static/js/transfer.js index 84d0daf..2051a23 100644 --- a/src/main/resources/static/js/transfer.js +++ b/src/main/resources/static/js/transfer.js @@ -1,7 +1,14 @@ const tradeableSelect = document.getElementById("tradeableSelect"); const valueInput = document.getElementById("amountInput"); +const recipientNumberInput = document.getElementById("recipientNumberInput"); +const recipientNumberNote = document.getElementById("recipientNumberNote"); + +const exchangeId = document.getElementById("exchangeIdInput").value; +const accountNumberRegex = new RegExp("\\d{4}-\\d{4}-\\d{4}-\\d{4}"); tradeableSelect.addEventListener("change", onSelectChanged); +recipientNumberInput.addEventListener("change", onAccountNumberChanged); +recipientNumberInput.addEventListener("keyup", onAccountNumberChanged); function onSelectChanged() { valueInput.value = null; @@ -17,3 +24,52 @@ function onSelectChanged() { valueInput.setAttribute("min", 0.0000000001); } } + +/** + * Check that the account number that was entered is valid. + */ +function onAccountNumberChanged() { + const currentNumber = recipientNumberInput.value; + console.log("Account number changed to " + currentNumber); + if (currentNumber !== undefined && currentNumber.length > 0) { + if (accountNumberRegex.test(currentNumber)) { + recipientNumberNote.innerText = "Valid account number."; + fetch("/api/exchanges/" + exchangeId + "/accounts/" + currentNumber) + .then(response => { + if (response.status === 200) { + response.json() + .then(data => { + console.log(data); + showRecipientNumberNote("info", "Account number is valid. Will transfer to " + data.name + ""); + }) + .catch(() => { + showRecipientNumberNote("warning", "Could not find an account with that number."); + }); + } else if (response.status === 404) { + showRecipientNumberNote("warning", "Could not find an account with that number."); + } else { + showRecipientNumberNote("warning", "Error: Couldn't read API response."); + } + }) + .catch(error => { + console.log(error); + showRecipientNumberNote("warning", "API error occurred. Couldn't fetch account information."); + }); + } else { + showRecipientNumberNote("warning", "Invalid account number. Format: 1234-1234-1234-1234"); + } + } else { + showRecipientNumberNote("info", "Enter an account number."); + } +} + +function showRecipientNumberNote(type, text) { + recipientNumberNote.innerHTML = text; + if (type === "warning") { + recipientNumberNote.classList.add("text-danger"); + recipientNumberNote.classList.remove("text-muted"); + } else { + recipientNumberNote.classList.remove("text-danger"); + recipientNumberNote.classList.add("text-muted"); + } +} diff --git a/src/main/resources/templates/trade.html b/src/main/resources/templates/account/trade.html similarity index 100% rename from src/main/resources/templates/trade.html rename to src/main/resources/templates/account/trade.html diff --git a/src/main/resources/templates/account/transfer.html b/src/main/resources/templates/account/transfer.html index a402028..2f30ce8 100644 --- a/src/main/resources/templates/account/transfer.html +++ b/src/main/resources/templates/account/transfer.html @@ -10,11 +10,12 @@ Transfer funds to other accounts.

+
- - - Format: 1234-1234-1234-1234
+ + + Enter an account number.
@@ -22,7 +23,7 @@