Added create account page, and more info for accounts.

This commit is contained in:
Andrew Lalis 2022-02-25 21:33:48 +01:00
parent 27a7ee6e72
commit 046d6dda09
12 changed files with 89 additions and 19 deletions

View File

@ -10,7 +10,7 @@
</parent>
<groupId>nl.andrewl</groupId>
<artifactId>coyotecredit</artifactId>
<version>1.2.0</version>
<version>1.3.0</version>
<name>coyotecredit</name>
<description>Banking and stock trading application to teach students.</description>
<properties>

View File

@ -12,6 +12,8 @@ public record FullAccountData (
String number,
String name,
boolean admin,
long userId,
String username,
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.
ExchangeData exchange,

View File

@ -1,6 +1,7 @@
package nl.andrewl.coyotecredit.ctl.exchange;
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.EditExchangePayload;
import nl.andrewl.coyotecredit.ctl.exchange.dto.InviteUserPayload;
@ -66,6 +67,18 @@ public class ExchangeController {
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}")
public String getRemoveAccountPage(@PathVariable long exchangeId, @PathVariable long accountId, @AuthenticationPrincipal User user) {
exchangeService.ensureAdminAccount(exchangeId, user);

View File

@ -0,0 +1,8 @@
package nl.andrewl.coyotecredit.ctl.exchange.dto;
public record AddAccountPayload(
String username,
String password,
String email,
String accountName
) {}

View File

@ -12,4 +12,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}

View File

@ -3,6 +3,7 @@ package nl.andrewl.coyotecredit.model;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.math.BigDecimal;
@ -53,7 +54,7 @@ public class Account {
* Administrators have special permissions to add and remove other accounts,
* custom tradeables, exchange rates, and more.
*/
@Column(nullable = false)
@Column(nullable = false) @Setter
private boolean admin;
/**

View File

@ -115,23 +115,6 @@ public class AccountService {
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)
public FullAccountData getAccountData(User user, long accountId) {
Account account = accountRepository.findById(accountId)
@ -149,6 +132,8 @@ public class AccountService {
account.getNumber(),
account.getName(),
account.isAdmin(),
account.getUser().getId(),
account.getUser().getUsername(),
userAccount.isAdmin(),
account.getUser().getId().equals(user.getId()),
new ExchangeData(

View File

@ -13,6 +13,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
@ -41,6 +42,7 @@ public class ExchangeService {
private final ExchangeInvitationRepository invitationRepository;
private final UserNotificationRepository notificationRepository;
private final JavaMailSender mailSender;
private final PasswordEncoder passwordEncoder;
@Value("${coyote-credit.base-url}")
private String baseUrl;
@ -117,6 +119,23 @@ public class ExchangeService {
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
public void removeAccount(long exchangeId, long accountId, User user) {
Exchange exchange = exchangeRepository.findById(exchangeId)

View File

@ -67,6 +67,9 @@ public class UserService {
if (userRepository.existsByUsername(payload.username())) {
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 = userRepository.save(user);
if (payload.inviteCode() != null) {

View File

@ -25,6 +25,8 @@
<dd class="col-sm-6">
<span class="monospace" th:text="${account.totalBalance()}"></span>&nbsp;<span th:text="${account.exchange().primaryTradeable()}"></span>
</dd>
<dt class="col-sm-6">Username</dt>
<dd class="col-sm-6" th:text="${account.username()}"></dd>
</dl>
</div>
<div class="card-footer">

View File

@ -6,6 +6,9 @@
>
<div id="content" class="container">
<h1 class="display-4">Accounts</h1>
<p class="lead">
View all accounts in the exchange.
</p>
<table class="table table-dark">
<thead>
@ -33,4 +36,5 @@
</table>
<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>

View File

@ -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>