Added create account page, and more info for accounts.
This commit is contained in:
parent
27a7ee6e72
commit
046d6dda09
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>nl.andrewl</groupId>
|
<groupId>nl.andrewl</groupId>
|
||||||
<artifactId>coyotecredit</artifactId>
|
<artifactId>coyotecredit</artifactId>
|
||||||
<version>1.2.0</version>
|
<version>1.3.0</version>
|
||||||
<name>coyotecredit</name>
|
<name>coyotecredit</name>
|
||||||
<description>Banking and stock trading application to teach students.</description>
|
<description>Banking and stock trading application to teach students.</description>
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
@ -12,6 +12,8 @@ public record FullAccountData (
|
||||||
String number,
|
String number,
|
||||||
String name,
|
String name,
|
||||||
boolean admin,
|
boolean admin,
|
||||||
|
long userId,
|
||||||
|
String username,
|
||||||
boolean userAdmin,// If the current user is an admin of the exchange this account is in.
|
boolean userAdmin,// If the current user is an admin of the exchange this account is in.
|
||||||
boolean userIsOwner,// If the current user is the owner of this account.
|
boolean userIsOwner,// If the current user is the owner of this account.
|
||||||
ExchangeData exchange,
|
ExchangeData exchange,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.coyotecredit.ctl.exchange;
|
package nl.andrewl.coyotecredit.ctl.exchange;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import nl.andrewl.coyotecredit.ctl.exchange.dto.AddAccountPayload;
|
||||||
import nl.andrewl.coyotecredit.ctl.exchange.dto.AddSupportedTradeablePayload;
|
import nl.andrewl.coyotecredit.ctl.exchange.dto.AddSupportedTradeablePayload;
|
||||||
import nl.andrewl.coyotecredit.ctl.exchange.dto.EditExchangePayload;
|
import nl.andrewl.coyotecredit.ctl.exchange.dto.EditExchangePayload;
|
||||||
import nl.andrewl.coyotecredit.ctl.exchange.dto.InviteUserPayload;
|
import nl.andrewl.coyotecredit.ctl.exchange.dto.InviteUserPayload;
|
||||||
|
@ -66,6 +67,18 @@ public class ExchangeController {
|
||||||
return "redirect:/users/" + user.getId();
|
return "redirect:/users/" + user.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{exchangeId}/createAccount")
|
||||||
|
public String getCreateAccountPage(@PathVariable long exchangeId, @AuthenticationPrincipal User user) {
|
||||||
|
exchangeService.ensureAdminAccount(exchangeId, user);
|
||||||
|
return "exchange/create_account";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{exchangeId}/createAccount")
|
||||||
|
public String postCreateAccount(@PathVariable long exchangeId, @AuthenticationPrincipal User user, @ModelAttribute AddAccountPayload payload) {
|
||||||
|
exchangeService.addAccount(exchangeId, user, payload);
|
||||||
|
return "redirect:/exchanges/" + exchangeId + "/accounts";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/{exchangeId}/removeAccount/{accountId}")
|
@GetMapping(path = "/{exchangeId}/removeAccount/{accountId}")
|
||||||
public String getRemoveAccountPage(@PathVariable long exchangeId, @PathVariable long accountId, @AuthenticationPrincipal User user) {
|
public String getRemoveAccountPage(@PathVariable long exchangeId, @PathVariable long accountId, @AuthenticationPrincipal User user) {
|
||||||
exchangeService.ensureAdminAccount(exchangeId, user);
|
exchangeService.ensureAdminAccount(exchangeId, user);
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package nl.andrewl.coyotecredit.ctl.exchange.dto;
|
||||||
|
|
||||||
|
public record AddAccountPayload(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
String email,
|
||||||
|
String accountName
|
||||||
|
) {}
|
|
@ -12,4 +12,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
boolean existsByUsername(String username);
|
boolean existsByUsername(String username);
|
||||||
|
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
|
boolean existsByEmail(String email);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewl.coyotecredit.model;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -53,7 +54,7 @@ public class Account {
|
||||||
* Administrators have special permissions to add and remove other accounts,
|
* Administrators have special permissions to add and remove other accounts,
|
||||||
* custom tradeables, exchange rates, and more.
|
* custom tradeables, exchange rates, and more.
|
||||||
*/
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false) @Setter
|
||||||
private boolean admin;
|
private boolean admin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -115,23 +115,6 @@ public class AccountService {
|
||||||
notificationRepository.save(new UserNotification(recipient.getUser(), recipientMessage));
|
notificationRepository.save(new UserNotification(recipient.getUser(), recipientMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static record AccountData (
|
|
||||||
long id,
|
|
||||||
String accountNumber,
|
|
||||||
String exchangeName
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public List<AccountData> getAccountsOverview(User user) {
|
|
||||||
return accountRepository.findAllByUser(user).stream()
|
|
||||||
.map(a -> new AccountData(
|
|
||||||
a.getId(),
|
|
||||||
a.getNumber(),
|
|
||||||
a.getExchange().getName()
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public FullAccountData getAccountData(User user, long accountId) {
|
public FullAccountData getAccountData(User user, long accountId) {
|
||||||
Account account = accountRepository.findById(accountId)
|
Account account = accountRepository.findById(accountId)
|
||||||
|
@ -149,6 +132,8 @@ public class AccountService {
|
||||||
account.getNumber(),
|
account.getNumber(),
|
||||||
account.getName(),
|
account.getName(),
|
||||||
account.isAdmin(),
|
account.isAdmin(),
|
||||||
|
account.getUser().getId(),
|
||||||
|
account.getUser().getUsername(),
|
||||||
userAccount.isAdmin(),
|
userAccount.isAdmin(),
|
||||||
account.getUser().getId().equals(user.getId()),
|
account.getUser().getId().equals(user.getId()),
|
||||||
new ExchangeData(
|
new ExchangeData(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
@ -41,6 +42,7 @@ public class ExchangeService {
|
||||||
private final ExchangeInvitationRepository invitationRepository;
|
private final ExchangeInvitationRepository invitationRepository;
|
||||||
private final UserNotificationRepository notificationRepository;
|
private final UserNotificationRepository notificationRepository;
|
||||||
private final JavaMailSender mailSender;
|
private final JavaMailSender mailSender;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Value("${coyote-credit.base-url}")
|
@Value("${coyote-credit.base-url}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
|
@ -117,6 +119,23 @@ public class ExchangeService {
|
||||||
return exchange;
|
return exchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void addAccount(long exchangeId, User user, AddAccountPayload payload) {
|
||||||
|
Exchange exchange = getExchangeIfAdmin(exchangeId, user);
|
||||||
|
if (userRepository.existsByUsername(payload.username())) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username is taken.");
|
||||||
|
}
|
||||||
|
if (userRepository.existsByEmail(payload.email())) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "There is already an account with that email.");
|
||||||
|
}
|
||||||
|
User newUser = userRepository.save(new User(payload.username(), passwordEncoder.encode(payload.password()), payload.email()));
|
||||||
|
Account account = accountRepository.save(new Account(AccountNumberUtils.generate(), newUser, payload.accountName(), exchange));
|
||||||
|
for (var t : exchange.getAllTradeables()) {
|
||||||
|
account.getBalances().add(new Balance(account, t, BigDecimal.ZERO));
|
||||||
|
}
|
||||||
|
accountRepository.save(account);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void removeAccount(long exchangeId, long accountId, User user) {
|
public void removeAccount(long exchangeId, long accountId, User user) {
|
||||||
Exchange exchange = exchangeRepository.findById(exchangeId)
|
Exchange exchange = exchangeRepository.findById(exchangeId)
|
||||||
|
|
|
@ -67,6 +67,9 @@ public class UserService {
|
||||||
if (userRepository.existsByUsername(payload.username())) {
|
if (userRepository.existsByUsername(payload.username())) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username is already taken.");
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username is already taken.");
|
||||||
}
|
}
|
||||||
|
if (userRepository.existsByEmail(payload.email())) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "There is already an account with that email.");
|
||||||
|
}
|
||||||
User user = new User(payload.username(), passwordEncoder.encode(payload.password()), payload.email());
|
User user = new User(payload.username(), passwordEncoder.encode(payload.password()), payload.email());
|
||||||
user = userRepository.save(user);
|
user = userRepository.save(user);
|
||||||
if (payload.inviteCode() != null) {
|
if (payload.inviteCode() != null) {
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
<dd class="col-sm-6">
|
<dd class="col-sm-6">
|
||||||
<span class="monospace" th:text="${account.totalBalance()}"></span> <span th:text="${account.exchange().primaryTradeable()}"></span>
|
<span class="monospace" th:text="${account.totalBalance()}"></span> <span th:text="${account.exchange().primaryTradeable()}"></span>
|
||||||
</dd>
|
</dd>
|
||||||
|
<dt class="col-sm-6">Username</dt>
|
||||||
|
<dd class="col-sm-6" th:text="${account.username()}"></dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
>
|
>
|
||||||
<div id="content" class="container">
|
<div id="content" class="container">
|
||||||
<h1 class="display-4">Accounts</h1>
|
<h1 class="display-4">Accounts</h1>
|
||||||
|
<p class="lead">
|
||||||
|
View all accounts in the exchange.
|
||||||
|
</p>
|
||||||
|
|
||||||
<table class="table table-dark">
|
<table class="table table-dark">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -33,4 +36,5 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a class="btn btn-primary" th:href="@{/exchanges/{eId}/inviteUser(eId=${exchangeId})}">Invite User</a>
|
<a class="btn btn-primary" th:href="@{/exchanges/{eId}/inviteUser(eId=${exchangeId})}">Invite User</a>
|
||||||
|
<a class="btn btn-secondary" th:href="@{/exchanges/{eId}/createAccount(eId=${exchangeId})}">Create Account</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
lang="en"
|
||||||
|
xmlns:th="http://www.thymeleaf.org"
|
||||||
|
th:replace="~{layout/basic_page :: layout (title='Exchange Accounts', content=~{::#content})}"
|
||||||
|
>
|
||||||
|
<div id="content" class="container">
|
||||||
|
<h1 class="display-4">Create Account</h1>
|
||||||
|
<p class="lead">
|
||||||
|
Create a new account for someone to join the exchange. Only for users who don't yet have an account on Coyote Credit.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form th:action="@{/exchanges/{eId}/createAccount(eId=${exchangeId})}" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="usernameInput" class="form-label">Username</label>
|
||||||
|
<input id="usernameInput" name="username" class="form-control" type="text" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="passwordInput" class="form-label">Password</label>
|
||||||
|
<input id="passwordInput" name="password" class="form-control" type="password" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="emailInput" class="form-label">Email</label>
|
||||||
|
<input id="emailInput" name="email" class="form-control" type="email" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="accountNameInput" class="form-label">Account Name</label>
|
||||||
|
<input id="accountNameInput" name="accountName" class="form-control" type="text" required/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Create</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
Loading…
Reference in New Issue