Added transfers page, extra checks for transfer logic.
This commit is contained in:
parent
0448c5049c
commit
2640115e50
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>nl.andrewl</groupId>
|
||||
<artifactId>coyotecredit</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
<name>coyotecredit</name>
|
||||
<description>Banking and stock trading application to teach students.</description>
|
||||
<properties>
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<Transfer, Long> {
|
||||
public interface TransferRepository extends JpaRepository<Transfer, Long>, JpaSpecificationExecutor<Transfer> {
|
||||
@Query(
|
||||
"SELECT t FROM Transfer t " +
|
||||
"WHERE t.senderNumber = :accountNumber OR t.recipientNumber = :accountNumber " +
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<TransferData> getTransfers(long exchangeId, User user, Pageable pageable) {
|
||||
Exchange exchange = getExchangeIfAdmin(exchangeId, user);
|
||||
Page<Transfer> transfers = transferRepository.findAll((root, query, criteriaBuilder) -> {
|
||||
Subquery<String> accountNumberSubquery = query.subquery(String.class);
|
||||
Root<Account> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
th:replace="~{layout/basic_page :: layout (title='Exchange Accounts', content=~{::#content})}"
|
||||
>
|
||||
<div id="content" class="container">
|
||||
<h1>Accounts</h1>
|
||||
<h1 class="display-4">Accounts</h1>
|
||||
|
||||
<table class="table table-dark">
|
||||
<thead>
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
</dl>
|
||||
<a class="btn btn-primary" th:href="@{/accounts/{aId}(aId=${exchange.accountId()})}">My Account</a>
|
||||
<span th:if="${exchange.accountAdmin()}">
|
||||
<a class="btn btn-primary" th:href="@{/exchanges/{eId}/accounts(eId=${exchange.id()})}">View All Accounts</a>
|
||||
<a class="btn btn-secondary" th:href="@{/exchanges/{eId}/accounts(eId=${exchange.id()})}">View All Accounts</a>
|
||||
<a class="btn btn-secondary" th:href="@{/exchanges/{eId}/transfers(eId=${exchange.id()}, page=0, size=50)}">View Transfers</a>
|
||||
<a class="btn btn-secondary" th:href="@{/exchanges/{eId}/edit(eId=${exchange.id()})}">Edit Exchange Settings</a>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
th:replace="~{layout/basic_page :: layout (title='Transfers', content=~{::#content})}"
|
||||
>
|
||||
<div id="content" class="container">
|
||||
<h1 class="display-4">Transfers</h1>
|
||||
<p class="lead">
|
||||
View all transfers between accounts within this exchange.
|
||||
</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<span class="lead">
|
||||
Page <span th:text="${transfers.getNumber() + 1}"></span> of <span th:text="${transfers.getTotalPages()}"></span>
|
||||
<small class="text-muted">
|
||||
(<span th:text="${transfers.getTotalElements()}"></span> total transfers)
|
||||
</small>
|
||||
</span>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
th:if="${transfers.hasPrevious()}"
|
||||
th:href="@{/exchanges/{eId}/transfers(eId=${exchangeId}, page=${transfers.previousPageable().getPageNumber()}, size=${transfers.previousPageable().getPageSize()})}"
|
||||
>
|
||||
Previous Page
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
th:if="${transfers.hasNext()}"
|
||||
th:href="@{/exchanges/{eId}/transfers(eId=${exchangeId}, page=${transfers.nextPageable().getPageNumber()}, size=${transfers.nextPageable().getPageSize()})}"
|
||||
>
|
||||
Next Page
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<table class="table table-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Timestamp</th>
|
||||
<th>Sender</th>
|
||||
<th>Recipient</th>
|
||||
<th>Asset</th>
|
||||
<th>Amount</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="transfer : ${transfers.getContent()}">
|
||||
<td class="monospace" th:text="${transfer.id()}"></td>
|
||||
<td th:text="${transfer.timestamp()}"></td>
|
||||
<td class="monospace" th:text="${transfer.senderNumber()}"></td>
|
||||
<td class="monospace" th:text="${transfer.recipientNumber()}"></td>
|
||||
<td>
|
||||
<a class="colored-link" th:text="${transfer.tradeable().name()}" th:href="@{/tradeables/{tId}(tId=${transfer.tradeable().id()})}"></a>
|
||||
</td>
|
||||
<td class="monospace" th:text="${transfer.amount()}"></td>
|
||||
<td class="small" th:text="${transfer.message()}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
Loading…
Reference in New Issue