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>
 | 
			
		||||
	<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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
	Optional<User> findByEmail(String email);
 | 
			
		||||
	boolean existsByEmail(String email);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,8 @@
 | 
			
		|||
                    <dd class="col-sm-6">
 | 
			
		||||
                        <span class="monospace" th:text="${account.totalBalance()}"></span> <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">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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