βββ ββββββββ ββββββ βββββββ ββββ βββ βββ βββ ββββββ βββ ββββββ βββββββββ
βββ βββββββββββββββββββββββββββββ βββ βββ ββββββββββββββ ββββββ βββββββββ
βββ ββββββ ββββββββββββββββββββββ βββ βββ ββββββββββββββ ββββββ βββ
βββ ββββββ ββββββββββββββββββββββββββ ββββ βββββββββββββββ ββββββ βββ
βββββββββββββββββββ ββββββ ββββββ ββββββ βββββββ βββ βββββββββββββββββββββββ
βββββββββββββββββββ ββββββ ββββββ βββββ βββββ βββ βββ βββββββ βββββββββββ
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."