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