diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 diff --git a/src/main/java/com/example/ledgersystem/DataSeeder.java b/src/main/java/com/example/ledgersystem/DataSeeder.java index eec6b64..94e88fb 100644 --- a/src/main/java/com/example/ledgersystem/DataSeeder.java +++ b/src/main/java/com/example/ledgersystem/DataSeeder.java @@ -10,11 +10,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import java.math.BigDecimal; -import java.util.Arrays; import java.util.List; @Component // <--- Tells Spring to manage this class @@ -40,77 +40,46 @@ public void run(String... args) throws Exception { log.info("Starting database seed..."); - Role userRole = roleRepository - .findByRoleName(AppRoles.ROLE_USER) - .orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_USER))); - log.debug("Role created/found: {}", AppRoles.ROLE_USER); - - Role adminRole = roleRepository - .findByRoleName(AppRoles.ROLE_ADMIN) - .orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_ADMIN))); - log.debug("Role created/found: {}", AppRoles.ROLE_ADMIN); - - User systemAdmin = new User(); - systemAdmin.setUsername("systemAdmin"); - systemAdmin.setPassword(passwordEncoder.encode("pass123")); - systemAdmin.setRole(List.of(userRole, adminRole)); - systemAdmin.setEmail("system@gmail.com"); - userRepository.save(systemAdmin); - log.debug("System admin user created: username=systemAdmin"); - - Account systemAccount = new Account(); - systemAccount.setName("CENTRAL_BANK"); - systemAccount.setBalance(new BigDecimal("0.00")); - systemAccount.setCurrency("INR"); - systemAccount.setUser(systemAdmin); - accountRepository.save(systemAccount); - log.debug("CENTRAL_BANK account created: accountId={}", systemAccount.getAccountId()); - - // ---- CREATE USERS ---- - User aliceUser = new User(); - aliceUser.setEmail("alice@gmail.com"); - aliceUser.setPassword(passwordEncoder.encode("pass123")); - aliceUser.setUsername("alice"); - aliceUser.setRole(List.of(userRole)); - - User bobUser = new User(); - bobUser.setEmail("bob@gmail.com"); - bobUser.setPassword(passwordEncoder.encode("pass123")); - bobUser.setUsername("bob"); - bobUser.setRole(List.of(userRole, adminRole)); - - User charlieUser = new User(); - charlieUser.setEmail("charlie@gmail.com"); - charlieUser.setPassword(passwordEncoder.encode("pass123")); - charlieUser.setUsername("charlie"); - charlieUser.setRole(List.of(userRole)); - - userRepository.saveAll(List.of(aliceUser, bobUser, charlieUser)); - log.debug("Test users created: alice, bob, charlie"); - - // 2. Create Dummy Accounts - Account alice = new Account(); - alice.setName("Alice"); - alice.setCurrency("INR"); - alice.setBalance(new BigDecimal("1000.0000")); // Initial "Gift" - alice.setUser(aliceUser); - - Account bob = new Account(); - bob.setName("Bob"); - bob.setCurrency("INR"); - bob.setBalance(new BigDecimal("1000.0000")); - bob.setUser(bobUser); - - Account charlie = new Account(); - charlie.setName("Charlie"); - charlie.setCurrency("INR"); - charlie.setBalance(new BigDecimal("5000.0000")); - charlie.setUser(charlieUser); - - // 3. Save to DB - accountRepository.saveAll(Arrays.asList(alice, bob, charlie)); - - log.info("Database seeded successfully. Accounts: alice={}, bob={}, charlie={}", - alice.getAccountId(), bob.getAccountId(), charlie.getAccountId()); + try { + Role userRole = roleRepository + .findByRoleName(AppRoles.ROLE_USER) + .orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_USER))); + log.debug("Role created/found: {}", AppRoles.ROLE_USER); + + Role adminRole = roleRepository + .findByRoleName(AppRoles.ROLE_ADMIN) + .orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_ADMIN))); + log.debug("Role created/found: {}", AppRoles.ROLE_ADMIN); + + User systemAdmin = new User(); + systemAdmin.setUsername("systemAdmin"); + systemAdmin.setPassword(passwordEncoder.encode("pass123")); + systemAdmin.setRole(List.of(userRole, adminRole)); + systemAdmin.setEmail("system@admin.com"); + userRepository.save(systemAdmin); + log.debug("System admin user created: username=systemAdmin, email=system@admin.com"); + + Account centralBank = new Account(); + centralBank.setName("CENTRAL_BANK"); + centralBank.setBalance(new BigDecimal("0.00")); + centralBank.setCurrency("INR"); + centralBank.setUser(systemAdmin); + accountRepository.save(centralBank); + log.debug("CENTRAL_BANK account created: accountId={}", centralBank.getAccountId()); + + log.info("Database seeded successfully."); + log.info("====================================="); + log.info("System ready! Available endpoints:"); + log.info(" POST /api/auth/signup - Register a new user"); + log.info(" POST /api/auth/signin - Login"); + log.info(" POST /api/account/create - Create a new account (auth required)"); + log.info(" GET /api/account/list - List your accounts (auth required)"); + log.info(" POST /api/deposit - Deposit funds (auth required)"); + log.info(" POST /api/transfer - Transfer funds (auth required)"); + log.info(" POST /api/withdraw - Withdraw funds (auth required)"); + log.info("====================================="); + } catch (DataIntegrityViolationException e) { + log.warn("Data seeding skipped due to concurrent initialization: {}", e.getMessage()); + } } } diff --git a/src/main/java/com/example/ledgersystem/Payloads/CreateAccountDTO.java b/src/main/java/com/example/ledgersystem/Payloads/CreateAccountDTO.java new file mode 100644 index 0000000..e8ba552 --- /dev/null +++ b/src/main/java/com/example/ledgersystem/Payloads/CreateAccountDTO.java @@ -0,0 +1,22 @@ +package com.example.ledgersystem.Payloads; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CreateAccountDTO { + + @NotBlank + @Size(min = 1, max = 50) + private String accountName; + + @NotBlank + @Pattern(regexp = "^[A-Z]{3}$", message = "Currency must be a valid 3-letter ISO 4217 code (e.g. INR, USD)") + private String currency; +} diff --git a/src/main/java/com/example/ledgersystem/Security/Request/SignUpRequest.java b/src/main/java/com/example/ledgersystem/Security/Request/SignUpRequest.java index bc5e0e7..1e304b0 100644 --- a/src/main/java/com/example/ledgersystem/Security/Request/SignUpRequest.java +++ b/src/main/java/com/example/ledgersystem/Security/Request/SignUpRequest.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.Size; import lombok.Data; +import java.util.Set; + @Data public class SignUpRequest { @Email @@ -20,4 +22,6 @@ public class SignUpRequest { @Size(min = 3, max = 30) private String username; + private Set role; + } diff --git a/src/main/java/com/example/ledgersystem/controller/AccountController.java b/src/main/java/com/example/ledgersystem/controller/AccountController.java index fbfdf6a..8289992 100644 --- a/src/main/java/com/example/ledgersystem/controller/AccountController.java +++ b/src/main/java/com/example/ledgersystem/controller/AccountController.java @@ -2,6 +2,7 @@ import com.example.ledgersystem.Exceptions.APIexception; import com.example.ledgersystem.Payloads.ApiResponse; +import com.example.ledgersystem.Payloads.CreateAccountDTO; import com.example.ledgersystem.Payloads.DepositRequestDTO; import com.example.ledgersystem.Payloads.MoneyTransferDTO; import com.example.ledgersystem.Payloads.StatementResponse; @@ -9,7 +10,9 @@ import com.example.ledgersystem.config.AppConst; import com.example.ledgersystem.enums.RateLimitType; import com.example.ledgersystem.model.Account; +import com.example.ledgersystem.model.User; import com.example.ledgersystem.repositories.AccountRepository; +import com.example.ledgersystem.repositories.UserRepository; import com.example.ledgersystem.service.AccountService; import com.example.ledgersystem.service.RateLimitingService; import com.example.ledgersystem.utils.AuthUtils; @@ -22,6 +25,7 @@ import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -35,6 +39,8 @@ public class AccountController { @Autowired private AccountRepository accountRepository; @Autowired + private UserRepository userRepository; + @Autowired private AuthUtils authUtils; @Autowired private RateLimitingService rateLimitingService; @@ -49,6 +55,56 @@ private boolean isRateLimited(UUID userId, RateLimitType type) { return !bucket.tryConsume(1); } + @PostMapping("/account/create") + public ResponseEntity createAccount(@Valid @RequestBody CreateAccountDTO createAccountDTO) { + UUID userId = authUtils.loggedInUserId(); + log.info("Create account API called: accountName={}, currency={}, user={}", createAccountDTO.getAccountName(), createAccountDTO.getCurrency(), userId); + + // 🛡️ SHIELD + if (isRateLimited(userId, RateLimitType.GENERAL)) { + log.warn("Rate limit exceeded: userId={}, type=GENERAL, endpoint=/api/account/create", userId); + return new ResponseEntity<>( + new ApiResponse("Too many requests! Please wait a minute.", false), + HttpStatus.TOO_MANY_REQUESTS + ); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new APIexception("User not found")); + + Account account = new Account(); + account.setName(createAccountDTO.getAccountName()); + account.setCurrency(createAccountDTO.getCurrency()); + account.setBalance(BigDecimal.ZERO); + account.setUser(user); + accountRepository.save(account); + + log.info("Create account API completed: accountId={}, user={}", account.getAccountId(), userId); + return new ResponseEntity<>(new ApiResponse("Account created successfully!", true), HttpStatus.CREATED); + } + + @GetMapping("/account/list") + public ResponseEntity listAccounts() { + UUID userId = authUtils.loggedInUserId(); + log.info("List accounts API called: user={}", userId); + + // 🛡️ SHIELD + if (isRateLimited(userId, RateLimitType.GENERAL)) { + log.warn("Rate limit exceeded: userId={}, type=GENERAL, endpoint=/api/account/list", userId); + return new ResponseEntity<>( + new ApiResponse("Too many requests! Please wait a minute.", false), + HttpStatus.TOO_MANY_REQUESTS + ); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new APIexception("User not found")); + + List accounts = accountRepository.findAllByUser(user); + log.debug("List accounts API completed: user={}, count={}", userId, accounts.size()); + return new ResponseEntity<>(accounts, HttpStatus.OK); + } + @PostMapping("/transfer") public ResponseEntity transferMoney(@Valid @RequestBody MoneyTransferDTO moneyTransferDTO) { UUID userId = authUtils.loggedInUserId(); diff --git a/src/main/java/com/example/ledgersystem/controller/AuthController.java b/src/main/java/com/example/ledgersystem/controller/AuthController.java index 46c7bce..fa7e30e 100644 --- a/src/main/java/com/example/ledgersystem/controller/AuthController.java +++ b/src/main/java/com/example/ledgersystem/controller/AuthController.java @@ -6,7 +6,10 @@ import com.example.ledgersystem.Security.Response.UserInfoResponse; import com.example.ledgersystem.Security.Services.UserDetailsImpl; import com.example.ledgersystem.Security.jwt.JwtUtils; +import com.example.ledgersystem.model.AppRoles; +import com.example.ledgersystem.model.Role; import com.example.ledgersystem.model.User; +import com.example.ledgersystem.repositories.RoleRepository; import com.example.ledgersystem.repositories.UserRepository; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; @@ -27,7 +30,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; @RestController @@ -44,6 +49,9 @@ public class AuthController { @Autowired private UserRepository userRepository; + @Autowired + private RoleRepository roleRepository; + @Autowired private PasswordEncoder passwordEncoder; @@ -83,28 +91,33 @@ public ResponseEntity authenticateUser(@RequestBody LoginRequestDTO loginRequ public ResponseEntity registerUser(@Valid @RequestBody SignUpRequest signUpRequest){ log.info("Sign-up attempt: username={}, email={}", signUpRequest.getUsername(), signUpRequest.getEmail()); - //Checking for already existing account //Checking for already existing account if(userRepository.existsByUsername(signUpRequest.getUsername())){ log.warn("Sign-up rejected: username already taken, username={}", signUpRequest.getUsername()); - // FIX: Send the object, not the string return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!")); } if(userRepository.existsByEmail(signUpRequest.getEmail())){ log.warn("Sign-up rejected: email already in use, email={}", signUpRequest.getEmail()); - // FIX: Send the object, not the string return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!")); } + // Resolve roles - only ROLE_USER is assignable during self-service signup + List roles = new ArrayList<>(); + Role userRole = roleRepository.findByRoleName(AppRoles.ROLE_USER) + .orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_USER))); + roles.add(userRole); + log.debug("Sign-up: assigned ROLE_USER to username={}", signUpRequest.getUsername()); + //Saving User - User user = new User( //Creating user object for saving + User user = new User( signUpRequest.getUsername(), passwordEncoder.encode(signUpRequest.getPassword()), signUpRequest.getEmail() ); + user.setRole(roles); userRepository.save(user); - log.info("Sign-up successful: username={}, email={}", signUpRequest.getUsername(), signUpRequest.getEmail()); + log.info("Sign-up successful: username={}, email={}, roles={}", signUpRequest.getUsername(), signUpRequest.getEmail(), roles); return ResponseEntity.ok(new MessageResponse("User registered successfully!")); } diff --git a/src/main/java/com/example/ledgersystem/repositories/AccountRepository.java b/src/main/java/com/example/ledgersystem/repositories/AccountRepository.java index 0c7f81e..7b1ce40 100644 --- a/src/main/java/com/example/ledgersystem/repositories/AccountRepository.java +++ b/src/main/java/com/example/ledgersystem/repositories/AccountRepository.java @@ -1,13 +1,17 @@ package com.example.ledgersystem.repositories; import com.example.ledgersystem.model.Account; +import com.example.ledgersystem.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; import java.util.UUID; @Repository public interface AccountRepository extends JpaRepository { Optional findByName(String centralBank); + + List findAllByUser(User user); }