██╗ ███████╗ █████╗ ██████╗ ███╗ ██╗ ██╗ ██╗ █████╗ ██╗ ██╗██╗ ████████╗
██║ ██╔════╝██╔══██╗██╔══██╗████╗ ██║ ██║ ██║██╔══██╗██║ ██║██║ ╚══██╔══╝
██║ █████╗ ███████║██████╔╝██╔██╗ ██║ ██║ ██║███████║██║ ██║██║ ██║
██║ ██╔══╝ ██╔══██║██╔══██╗██║╚██╗██║ ╚██╗ ██╔╝██╔══██║██║ ██║██║ ██║
███████╗███████╗██║ ██║██║ ██║██║ ╚████║ ╚████╔╝ ██║ ██║╚██████╔╝███████╗██║
╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝
My Spring Boot learning sandbox — where patterns are forged before production.
LearnVault is a full-featured e-learning backend — and my personal lab for mastering production-grade Java development before building Junaidify v2.
Every pattern implemented here — auth flows, payment integration, JPA design, exception handling — gets battle-tested in this repo first, then shipped to production. No tutorial hell. Real architecture decisions, real tradeoffs.
┌─────────────────────────────────────────────────────────────┐
│ CLIENT (React / Postman) │
└──────────────────────────┬──────────────────────────────────┘
│ HTTP Requests
┌──────────────────────────▼──────────────────────────────────┐
│ Spring Security Filter Chain │
│ JwtAuthFilter → SecurityFilterChain → RBAC │
└──────────────────────────┬──────────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ AuthController│ │CourseController│ │PaymentController│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────▼──────────────────────────────────▼──────┐
│ Service Layer │
│ AuthService │ CourseService │ PaymentService │
└──────┬──────────────────────────────────┬──────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ JPA / ORM │ │ Razorpay │
│ (Hibernate) │ │ API │
└──────┬──────┘ └─────────────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ Database │
└─────────────┘
| Layer | Technology | Purpose |
|---|---|---|
| Runtime | Java 17 + Spring Boot 3.3.6 | Core application framework |
| Security | Spring Security 6 + JWT (JJWT 0.12.6) | Stateless auth + RBAC |
| OAuth2 | Google OAuth2 + Custom Oauth2SuccessHandler |
Social login flow |
| Database | PostgreSQL + Spring Data JPA | Relational persistence |
| ORM | Hibernate + @OneToMany / @ManyToOne |
Entity relationships |
| Payments | Razorpay SDK | Order creation + payment verification |
| Validation | Jakarta Bean Validation (@NotBlank, @Email) |
Request validation |
| Build | Gradle 8.x | Dependency management |
| Utilities | Lombok (DTOs only), MapStruct | Boilerplate reduction |
Complete stateless auth implementation with dual login support:
POST /api/auth/register → Register user (BCrypt password)
POST /api/auth/login → Login → returns JWT access token
GET /oauth2/authorization/google → Initiate Google OAuth2
GET /oauth2/callback/google → Oauth2SuccessHandler → JWT issued
Security Stack:
SecurityFilterChainwith stateless session managementJwtFilter→ validates token on every request before controllerCustomUserDetailsService→ loads user from DB by emailDaoAuthenticationProvider(Spring Security 6 constructor)@PreAuthorize("hasRole('ADMIN')")for role-based endpoint protectionGlobalExceptionHandlervia@RestControllerAdvice→ standardized error responses
Razorpay backend integration — fully functional order lifecycle:
POST /api/payments/create-order → Creates Razorpay order, returns orderId
POST /api/payments/verify → Verifies HMAC-SHA256 signature
POST /api/payments/webhook → Handles async payment events
Flow:
Client → POST /create-order → RazorpayClient.orders().create()
← { orderId, amount, currency, key }
Client pays via Razorpay checkout ↓
Client → POST /verify → HMAC(orderId + paymentId, secret)
→ EnrollmentService.enroll(userId, courseId)
← { success: true }
Core JPA entities with proper relationship modeling:
// UserEntity ─── @OneToMany ──► EnrollmentEntity
// CourseEntity ── @OneToMany ──► EnrollmentEntity
// EnrollmentEntity has @ManyToOne to both User and Course
@Entity
public class EnrollmentEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private UserEntity user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "course_id")
private CourseEntity course;
@Enumerated(EnumType.STRING)
private PaymentStatus paymentStatus;
}Tables: users · courses · enrollments · payments
Learn-vault/
├── src/
│ └── main/
│ ├── java/com/learnvault/
│ │ ├── config/
│ │ │ ├── SecurityConfig.java # SecurityFilterChain
│ │ │ └── RazorpayConfig.java # RazorpayClient bean
│ │ ├── controller/
│ │ │ ├── AuthController.java
│ │ │ ├── CourseController.java
│ │ │ └── PaymentController.java
│ │ ├── service/
│ │ │ ├── AuthService.java
│ │ │ ├── CourseService.java
│ │ │ └── PaymentService.java
│ │ ├── entity/
│ │ │ ├── UserEntity.java
│ │ │ ├── CourseEntity.java
│ │ │ ├── EnrollmentEntity.java
│ │ │ └── PaymentEntity.java
│ │ ├── dto/
│ │ │ ├── request/ # Incoming DTOs
│ │ │ └── response/ # Outgoing DTOs
│ │ ├── security/
│ │ │ ├── JwtFilter.java
│ │ │ ├── JwtUtils.java # JJWT 0.12.6
│ │ │ ├── CustomUserDetailsService.java
│ │ │ └── Oauth2SuccessHandler.java
│ │ ├── exception/
│ │ │ └── GlobalExceptionHandler.java # @RestControllerAdvice
│ │ └── repository/
│ │ ├── UserRepository.java
│ │ ├── CourseRepository.java
│ │ └── EnrollmentRepository.java
│ └── resources/
│ └── application.yml
├── build.gradle
└── README.md
- Java 17+
- PostgreSQL running locally
- Razorpay test API keys (optional for payment testing)
# Clone the repo
git clone https://github.com/junaidify/Learn-vault.git
cd Learn-vault
# Configure environment
cp src/main/resources/application.yml.example src/main/resources/application.yml
# Edit application.yml → set DB credentials, JWT secret, Razorpay keys
# Run
./gradlew bootRunspring:
datasource:
url: jdbc:postgresql://localhost:5432/learnvault
username: your_db_user
password: your_db_password
jpa:
hibernate:
ddl-auto: update
show-sql: true
app:
jwt:
secret: your_jwt_secret_here
expiration: 86400000 # 24h in ms
razorpay:
key-id: rzp_test_xxxx
key-secret: your_razorpay_secret| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
❌ | Register new user |
POST |
/api/auth/login |
❌ | Login → JWT |
GET |
/api/auth/me |
✅ JWT | Get current user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/courses |
❌ | List all courses |
GET |
/api/courses/{id} |
❌ | Get course details |
POST |
/api/courses |
✅ ADMIN | Create course |
PUT |
/api/courses/{id} |
✅ ADMIN | Update course |
DELETE |
/api/courses/{id} |
✅ ADMIN | Delete course |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/payments/create-order |
✅ JWT | Create Razorpay order |
POST |
/api/payments/verify |
✅ JWT | Verify payment + enroll |
| Concept | Key Insight |
|---|---|
| Spring Security 6 | DaoAuthenticationProvider constructor changed — must call setUserDetailsService() explicitly |
| JJWT 0.12.6 | Jwts.parser().verifyWith(key) replaces old .setSigningKey() API |
| JPA Relationships | Never use raw FK fields — use @ManyToOne with @JoinColumn, lazy fetch by default |
| OAuth2 + JWT | Oauth2SuccessHandler must generate JWT after OAuth2 success to stay stateless |
| Exception Handling | @RestControllerAdvice + typed @ExceptionHandler keeps error responses consistent |
| DTO Pattern | Service layer returns DTOs, never entities — decouples persistence from API contract |
This repo is the testing ground for Junaidify v2 — a production paid course platform.
Patterns proven here → shipped to:
pgvector+ OpenAI → RAG "Ask the Course" AI assistant- Redis → session cache + rate limiting
- AWS S3 → video upload pipeline
- Docker + GitHub Actions → CI/CD
- WhatsApp API → enrollment notifications
Junaid — Java Full-Stack Developer
"Built as a sandbox. Designed like production."