diff --git a/pom.xml b/pom.xml index 015f857..bf802c4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ nl.andrewl coyotecredit - 1.0.0 + 1.1.0 coyotecredit Banking and stock trading application to teach students. diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/ExchangeController.java b/src/main/java/nl/andrewl/coyotecredit/ctl/ExchangeController.java index 9ecdcda..ab547da 100644 --- a/src/main/java/nl/andrewl/coyotecredit/ctl/ExchangeController.java +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/ExchangeController.java @@ -6,6 +6,9 @@ import nl.andrewl.coyotecredit.ctl.dto.EditExchangePayload; import nl.andrewl.coyotecredit.ctl.dto.InviteUserPayload; import nl.andrewl.coyotecredit.model.User; import nl.andrewl.coyotecredit.service.ExchangeService; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; @@ -113,4 +116,16 @@ public class ExchangeController { exchangeService.removeSupportedTradeable(exchangeId, tradeableId, user); return "redirect:/exchanges/" + exchangeId + "/editTradeables"; } + + @GetMapping(path = "/{exchangeId}/transfers") + public String getTransfers( + Model model, + @PathVariable long exchangeId, + @AuthenticationPrincipal User user, + @PageableDefault(size = 50, sort = "timestamp", direction = Sort.Direction.DESC) + Pageable pageable + ) { + model.addAttribute("transfers", exchangeService.getTransfers(exchangeId, user, pageable)); + return "exchange/transfers"; + } } diff --git a/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferData.java b/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferData.java new file mode 100644 index 0000000..7b4b9b3 --- /dev/null +++ b/src/main/java/nl/andrewl/coyotecredit/ctl/dto/TransferData.java @@ -0,0 +1,27 @@ +package nl.andrewl.coyotecredit.ctl.dto; + +import nl.andrewl.coyotecredit.model.Transfer; + +import java.time.format.DateTimeFormatter; + +public record TransferData( + long id, + String timestamp, + String senderNumber, + String recipientNumber, + TradeableData tradeable, + String amount, + String message +) { + public TransferData(Transfer t) { + this( + t.getId(), + t.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " UTC", + t.getSenderNumber(), + t.getRecipientNumber(), + new TradeableData(t.getTradeable()), + t.getAmount().toPlainString(), + t.getMessage() + ); + } +} diff --git a/src/main/java/nl/andrewl/coyotecredit/dao/TransferRepository.java b/src/main/java/nl/andrewl/coyotecredit/dao/TransferRepository.java index e8ea028..36cf35c 100644 --- a/src/main/java/nl/andrewl/coyotecredit/dao/TransferRepository.java +++ b/src/main/java/nl/andrewl/coyotecredit/dao/TransferRepository.java @@ -4,11 +4,12 @@ import nl.andrewl.coyotecredit.model.Transfer; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository -public interface TransferRepository extends JpaRepository { +public interface TransferRepository extends JpaRepository, JpaSpecificationExecutor { @Query( "SELECT t FROM Transfer t " + "WHERE t.senderNumber = :accountNumber OR t.recipientNumber = :accountNumber " + diff --git a/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java b/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java index 1b295b7..3a0577a 100644 --- a/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java +++ b/src/main/java/nl/andrewl/coyotecredit/service/AccountService.java @@ -66,6 +66,9 @@ public class AccountService { } Account recipient = accountRepository.findByNumber(recipientNumber) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown recipient.")); + if (!recipient.getExchange().getId().equals(sender.getExchange().getId())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot transfer funds between exchanges."); + } Tradeable tradeable = tradeableRepository.findById(payload.tradeableId()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown tradeable asset.")); BigDecimal amount = new BigDecimal(payload.amount()); diff --git a/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java b/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java index 40286c5..dfbd960 100644 --- a/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java +++ b/src/main/java/nl/andrewl/coyotecredit/service/ExchangeService.java @@ -7,6 +7,8 @@ import nl.andrewl.coyotecredit.model.*; import nl.andrewl.coyotecredit.util.AccountNumberUtils; import nl.andrewl.coyotecredit.util.StringUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; @@ -16,6 +18,9 @@ import org.springframework.web.server.ResponseStatusException; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.DecimalFormat; @@ -29,6 +34,7 @@ public class ExchangeService { private final ExchangeRepository exchangeRepository; private final AccountRepository accountRepository; private final TransactionRepository transactionRepository; + private final TransferRepository transferRepository; private final TradeableRepository tradeableRepository; private final AccountValueSnapshotRepository accountValueSnapshotRepository; private final UserRepository userRepository; @@ -389,4 +395,21 @@ public class ExchangeService { } exchangeRepository.save(exchange); } + + @Transactional(readOnly = true) + public Page getTransfers(long exchangeId, User user, Pageable pageable) { + Exchange exchange = getExchangeIfAdmin(exchangeId, user); + Page transfers = transferRepository.findAll((root, query, criteriaBuilder) -> { + Subquery accountNumberSubquery = query.subquery(String.class); + Root accountRoot = accountNumberSubquery.from(Account.class); + accountNumberSubquery.select(accountRoot.get("number")) + .distinct(true) + .where(criteriaBuilder.equal(accountRoot.get("exchange").get("id"), exchange.getId())); + return criteriaBuilder.or( + criteriaBuilder.in(root.get("senderNumber")).value(accountNumberSubquery), + criteriaBuilder.in(root.get("recipientNumber")).value(accountNumberSubquery) + ); + }, pageable); + return transfers.map(TransferData::new); + } } diff --git a/src/main/resources/templates/exchange/accounts.html b/src/main/resources/templates/exchange/accounts.html index 735b31b..025700b 100644 --- a/src/main/resources/templates/exchange/accounts.html +++ b/src/main/resources/templates/exchange/accounts.html @@ -5,7 +5,7 @@ th:replace="~{layout/basic_page :: layout (title='Exchange Accounts', content=~{::#content})}" > - Accounts + Accounts diff --git a/src/main/resources/templates/exchange/exchange.html b/src/main/resources/templates/exchange/exchange.html index ba5c550..54ed2be 100644 --- a/src/main/resources/templates/exchange/exchange.html +++ b/src/main/resources/templates/exchange/exchange.html @@ -25,7 +25,8 @@ My Account - View All Accounts + View All Accounts + View Transfers Edit Exchange Settings diff --git a/src/main/resources/templates/exchange/transfers.html b/src/main/resources/templates/exchange/transfers.html new file mode 100644 index 0000000..6af7f32 --- /dev/null +++ b/src/main/resources/templates/exchange/transfers.html @@ -0,0 +1,62 @@ + + + + Transfers + + View all transfers between accounts within this exchange. + + + + + Page of + + ( total transfers) + + + + Previous Page + + + Next Page + + + + + + + Id + Timestamp + Sender + Recipient + Asset + Amount + Message + + + + + + + + + + + + + + + + + \ No newline at end of file
+ View all transfers between accounts within this exchange. +