This file is the single source of truth for any AI agent working on this project. Read it completely before generating, modifying, or reviewing any code.
A production-grade REST API for a digital wallet. Academic/portfolio focus — security and traceability are first-class concerns.
Core priorities (in order):
- Security — OWASP Top 10 (2025) compliance throughout
- Correctness — ACID financial transactions, no money lost
- Auditability — every sensitive operation logged
- Maintainability — clean, documented, testable code
| Component | Technology | Version |
|---|---|---|
| Framework | Spring Boot | 3.4.2 |
| Language | Java | 17 |
| Security | Spring Security + JWT | jjwt 0.12.6 |
| Database | PostgreSQL | 16 |
| ORM | Spring Data JPA / Hibernate | — |
| Logging | Log4j2 | 2.25.3 |
| Validation | Spring Bean Validation | — |
| Boilerplate | Lombok | 1.18.36 |
| Docs | SpringDoc OpenAPI | 2.8.8 |
| Tests | JUnit 5 + Mockito | — |
| CI/CD | GitHub Actions + CodeQL | — |
com.wallet.secure/
├── config/ # SecurityConfig, OpenApiConfig, AuditConfig
├── auth/ # Authentication: AuthService, JwtService, SessionService
│ ├── controller, dto, entity, repository, security, service
├── user/ # Users and profiles
├── wallet/ # Wallets and balances
├── transaction/ # Transactions + TransactionHistory (business core)
├── audit/ # AuditService, AuditLog — security event trail
└── common/ # ApiResponse<T>, exceptions, enums, validators
Rule: NEVER create root-level folders by type. Each domain contains its own controller/, dto/, entity/, repository/, service/.
Every endpoint returns ApiResponse<T> from common/response/:
return ResponseEntity.ok(ApiResponse.ok("Message", data));
return ResponseEntity.ok(ApiResponse.error("Message"));Never return raw objects or entities directly.
ddl-auto: validate— Hibernate NEVER creates or modifies tables- Schema is managed exclusively via SQL scripts in
/database/ - Before adding any entity field, add the column to the SQL script first
- Migrations go in
database/08-migrations.sql
// CORRECT — identity from trusted JWT:
@AuthenticationPrincipal UserDetails userDetails
UUID userId = resolveUserId(userDetails.getUsername());
// WRONG — never trust userId from request body or path for ownership:
@RequestBody SomeRequest request // where request contains userIdFinancial operations MUST be @Transactional:
@Transactional
public ApiResponse<TransactionResponse> deposit(...) { ... }Pessimistic locking for balance updates: walletRepository.findByIdWithLock(id)
Every security decision must reference OWASP:
// OWASP A07: brute force detection — 5 failures in 15 min → CRITICAL alert
// OWASP A01: userId from JWT — user cannot access another user's data
// OWASP A02: SHA-256 for token hashing — BCrypt unnecessary for random tokens- Entities: singular PascalCase →
User,Wallet,Transaction - Request DTOs:
[Action]Request→LoginRequest,DepositRequest - Response DTOs:
[Entity]Response→UserResponse,WalletResponse - Services:
[Domain]Service→AuthService,TransactionHistoryService
Document the WHY, not the what:
// WHY @Async: audit writes must never slow down a financial transaction
// WHY Propagation.REQUIRES_NEW: failure audit must survive parent transaction rollbackUse @Getter, @Builder, @RequiredArgsConstructor, @NoArgsConstructor, @AllArgsConstructor.
Never use @Data on entities — too broad, causes issues with JPA proxies.
@ExtendWith(MockitoExtension.class) // ← always this, never @SpringBootTest
class SomeServiceTest {
@Mock private SomeDependency dep;
@InjectMocks private SomeService service;
}Before writing any test, read these existing tests as reference:
AuditServiceTest.java→ how to test a service withArgumentCaptorAuthServiceTest.java→ how to mockHttpServletRequestTransactionServiceTest.java→ how to test financial operations with@Nested
@Test
@DisplayName("action_condition_expectedResult")
void methodName_condition_expectation() {
// GIVEN
// WHEN
// THEN
}- Happy path ✅
- Failure path (insufficient balance, not found, unauthorized) ✅
- Balance unchanged after failed operation (ACID) ✅
- Exception type AND message ✅
assertThatNoException()for fire-and-forget operations ✅
@Asyncbehavior → requires Spring context@Transactionalbehavior → requires Spring context- Hibernate queries → requires DB
- Never
ddl-auto: createorupdatein any environment - Stack traces never reach the client —
GlobalExceptionHandlerhandles all - Passwords never logged, never returned in any response
- JWT secret always from environment variable
JWT_SECRET - Financial operations always inside
@Transactional - Every sensitive operation generates an
audit_logsentry viaAuditService - Input validation required on ALL endpoints (
@Validin controller) userIdalways from JWT — never from request body for ownership checks- SHA-256 for token hashing (not BCrypt — tokens are random, not passwords)
- Raw tokens never stored in DB — only their SHA-256 hash
// Called after any auth event, wallet change, or transaction:
auditService.logLoginSuccess(userId, email, ip, userAgent);
auditService.logTransactionSuccess(userId, txId, type, amount, currency, ip, ua);
auditService.logWalletCreated(userId, walletId, currency, ip, ua);// Already implemented in AuthService — copy this pattern:
private String extractIp(HttpServletRequest request) { ... }
private String extractUserAgent(HttpServletRequest request) { ... }// AuthService.refresh() — dual validation pattern:
if (!refreshToken.equals(user.getRefreshToken())) { throw ... } // legacy
sessionService.validateSession(refreshToken); // new// User can only access their own resources:
transactionRepository.findByIdAndUserId(id, userId)
.orElseThrow(() -> new ResourceNotFoundException("Not found"));
// Returns 404 (not 403) — prevents resource enumeration (OWASP A01)| Domain | Entity | Key Service | Status |
|---|---|---|---|
| auth | — | AuthService, SessionService | ✅ Complete |
| user | User | UserService | ✅ Complete |
| wallet | Wallet | WalletService | ✅ Complete |
| transaction | Transaction | TransactionService | ✅ Complete |
| history | TransactionHistory | TransactionHistoryService | ✅ Complete |
| audit | AuditLog | AuditService | ✅ Complete |
| sessions | Session | SessionService | ✅ Complete |
| docs | — | OpenApiConfig | ✅ Complete |
main ← Production. Protected. Merge via PR only.
feature/* ← New features: feature/descriptive-name
fix/* ← Bug fixes: fix/bug-name
Commit format (Conventional Commits):
feat(domain): short description
fix(domain): short description
test(domain): short description
docs: short description
refactor(domain): short description
Before opening a PR, verify:
- Unit tests for all new business logic
- No hardcoded secrets — environment variables only
- All exceptions handled via
GlobalExceptionHandler -
@Validon all controller endpoints -
AuditServicecalled for sensitive operations -
@Transactionalon financial operations - OWASP comments on security decisions
-
@Schemaon new DTOs for Swagger -
@Tagon new controllers for Swagger grouping