kybernus 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/templates/java-spring/clean/.gitignore.hbs +72 -0
- package/templates/java-spring/clean/docker-compose.yml.hbs +6 -3
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/application/usecase/PaymentUseCase.java.hbs +21 -17
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/entity/UserEntity.java.hbs +52 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/repository/JpaUserRepository.java.hbs +12 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/JwtAuthenticationFilter.java.hbs +64 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/SecurityConfig.java.hbs +36 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/stripe/StripeGateway.java.hbs +63 -0
- package/templates/java-spring/clean/src/main/resources/application.properties.hbs +6 -7
- package/templates/java-spring/hexagonal/.gitignore.hbs +72 -0
- package/templates/java-spring/hexagonal/docker-compose.yml.hbs +6 -3
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/JwtFilter.java.hbs +71 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/SecurityConfig.java.hbs +35 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/core/service/PaymentService.java.hbs +3 -3
- package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +4 -4
- package/templates/java-spring/mvc/.gitignore.hbs +72 -0
- package/templates/java-spring/mvc/docker-compose.yml.hbs +6 -3
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/config/SecurityConfig.java.hbs +13 -12
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/AuthController.java.hbs +9 -8
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/PaymentsController.java.hbs +5 -6
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/StripeService.java.hbs +3 -3
- package/templates/java-spring/mvc/src/main/resources/application.yml.hbs +29 -26
- package/templates/nestjs/clean/.gitignore.hbs +42 -0
- package/templates/nestjs/clean/Dockerfile.hbs +6 -3
- package/templates/nestjs/clean/docker-compose.yml.hbs +1 -11
- package/templates/nestjs/clean/src/app.module.ts.hbs +2 -1
- package/templates/nestjs/clean/src/application/payment.service.ts.hbs +72 -72
- package/templates/nestjs/clean/src/domain/entities/user.entity.ts.hbs +2 -2
- package/templates/nestjs/clean/src/domain/repositories/user.repository.ts.hbs +2 -2
- package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +18 -18
- package/templates/nestjs/clean/src/infrastructure/http/health.controller.ts.hbs +9 -0
- package/templates/nestjs/clean/src/main.ts.hbs +1 -4
- package/templates/nestjs/clean/src/payment.module.ts.hbs +12 -12
- package/templates/nestjs/hexagonal/.gitignore.hbs +42 -0
- package/templates/nestjs/hexagonal/Dockerfile.hbs +6 -3
- package/templates/nestjs/hexagonal/docker-compose.yml.hbs +1 -11
- package/templates/nestjs/hexagonal/src/adapters/inbound/health.controller.ts.hbs +9 -0
- package/templates/nestjs/hexagonal/src/app.module.ts.hbs +2 -1
- package/templates/nestjs/hexagonal/src/core/domain/user.entity.ts.hbs +6 -6
- package/templates/nestjs/hexagonal/src/core/ports/ports.ts.hbs +4 -4
- package/templates/nestjs/hexagonal/src/main.ts.hbs +1 -4
- package/templates/nestjs/mvc/.gitignore.hbs +42 -0
- package/templates/nestjs/mvc/Dockerfile.hbs +6 -3
- package/templates/nestjs/mvc/docker-compose.yml.hbs +1 -11
- package/templates/nestjs/mvc/src/auth/auth.controller.ts.hbs +11 -1
- package/templates/nestjs/mvc/src/auth/auth.service.ts.hbs +3 -1
- package/templates/nestjs/mvc/src/controllers/health.controller.ts.hbs +6 -6
- package/templates/nestjs/mvc/src/main.ts.hbs +1 -4
- package/templates/nestjs/mvc/src/models/create-item.dto.ts.hbs +5 -2
- package/templates/nestjs/mvc/src/prisma/prisma.service.ts.hbs +1 -0
- package/templates/nextjs/mvc/.gitignore.hbs +42 -0
- package/templates/nextjs/mvc/Dockerfile.hbs +23 -8
- package/templates/nextjs/mvc/docker-compose.yml.hbs +1 -1
- package/templates/nodejs-express/clean/.gitignore.hbs +42 -0
- package/templates/nodejs-express/clean/Dockerfile.hbs +6 -1
- package/templates/nodejs-express/clean/docker-compose.yml.hbs +2 -2
- package/templates/nodejs-express/clean/package.json.hbs +69 -69
- package/templates/nodejs-express/clean/src/config.ts.hbs +11 -0
- package/templates/nodejs-express/clean/src/domain/entities/User.ts.hbs +46 -8
- package/templates/nodejs-express/hexagonal/.gitignore.hbs +42 -0
- package/templates/nodejs-express/hexagonal/Dockerfile.hbs +1 -1
- package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +2 -2
- package/templates/nodejs-express/hexagonal/package.json.hbs +69 -69
- package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/PaymentController.ts.hbs +21 -38
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts.hbs +2 -0
- package/templates/nodejs-express/hexagonal/src/config.ts.hbs +9 -0
- package/templates/nodejs-express/hexagonal/src/core/AuthService.ts.hbs +5 -5
- package/templates/nodejs-express/hexagonal/src/core/PaymentService.ts.hbs +7 -22
- package/templates/nodejs-express/hexagonal/src/core/domain/entities/User.ts.hbs +24 -4
- package/templates/nodejs-express/mvc/.gitignore.hbs +42 -0
- package/templates/nodejs-express/mvc/package.json.hbs +67 -67
- package/templates/python-fastapi/clean/.gitignore.hbs +76 -0
- package/templates/python-fastapi/clean/app/application/services/payment_service.py.hbs +3 -3
- package/templates/python-fastapi/clean/app/config.py.hbs +6 -7
- package/templates/python-fastapi/clean/app/domain/usecases/login_user.py.hbs +15 -0
- package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +40 -6
- package/templates/python-fastapi/clean/app/infrastructure/http/payment_controller.py.hbs +5 -4
- package/templates/python-fastapi/clean/app/infrastructure/security/jwt.py.hbs +23 -0
- package/templates/python-fastapi/clean/app/main.py.hbs +3 -0
- package/templates/python-fastapi/clean/docker-compose.yml.hbs +5 -12
- package/templates/python-fastapi/clean/requirements.txt.hbs +3 -0
- package/templates/python-fastapi/hexagonal/.gitignore.hbs +76 -0
- package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +6 -9
- package/templates/python-fastapi/hexagonal/app/adapters/inbound/payment_http_adapter.py.hbs +4 -3
- package/templates/python-fastapi/hexagonal/app/adapters/outbound/stripe_adapter.py.hbs +30 -19
- package/templates/python-fastapi/hexagonal/app/config.py.hbs +14 -4
- package/templates/python-fastapi/hexagonal/app/core/domain/user.py.hbs +3 -1
- package/templates/python-fastapi/hexagonal/app/core/payment_service.py.hbs +28 -18
- package/templates/python-fastapi/hexagonal/app/core/ports/__init__.py.hbs +3 -0
- package/templates/python-fastapi/hexagonal/app/core/ports/user_repository.py.hbs +15 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +7 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/database/user_repository.py.hbs +53 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/security/__init__.py.hbs +0 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/security/adapters.py.hbs +23 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/security/jwt.py.hbs +23 -0
- package/templates/python-fastapi/hexagonal/docker-compose.yml.hbs +5 -12
- package/templates/python-fastapi/hexagonal/requirements.txt.hbs +4 -0
- package/templates/python-fastapi/mvc/.gitignore.hbs +76 -0
- package/templates/python-fastapi/mvc/app/controllers/payments.py.hbs +3 -17
- package/templates/python-fastapi/mvc/app/middleware/security.py.hbs +24 -3
- package/templates/python-fastapi/mvc/app/schemas/item.py.hbs +3 -1
- package/templates/python-fastapi/mvc/docker-compose.yml.hbs +5 -12
- package/templates/python-fastapi/mvc/requirements.txt.hbs +3 -1
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +0 -5
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Compiled class files
|
|
2
|
+
*.class
|
|
3
|
+
|
|
4
|
+
# Log files
|
|
5
|
+
*.log
|
|
6
|
+
|
|
7
|
+
# Package files
|
|
8
|
+
*.jar
|
|
9
|
+
*.war
|
|
10
|
+
*.nar
|
|
11
|
+
*.ear
|
|
12
|
+
*.zip
|
|
13
|
+
*.tar.gz
|
|
14
|
+
*.rar
|
|
15
|
+
|
|
16
|
+
# Virtual machine crash logs
|
|
17
|
+
hs_err_pid*
|
|
18
|
+
replay_pid*
|
|
19
|
+
|
|
20
|
+
# Maven
|
|
21
|
+
target/
|
|
22
|
+
.mvn/wrapper/maven-wrapper.jar
|
|
23
|
+
!**/src/main/**/target/
|
|
24
|
+
!**/src/test/**/target/
|
|
25
|
+
|
|
26
|
+
# Gradle
|
|
27
|
+
.gradle
|
|
28
|
+
build/
|
|
29
|
+
!gradle/wrapper/gradle-wrapper.jar
|
|
30
|
+
!**/src/main/**/build/
|
|
31
|
+
!**/src/test/**/build/
|
|
32
|
+
|
|
33
|
+
# Spring Boot
|
|
34
|
+
spring-shell.log
|
|
35
|
+
|
|
36
|
+
# STS / Eclipse
|
|
37
|
+
.apt_generated
|
|
38
|
+
.classpath
|
|
39
|
+
.factorypath
|
|
40
|
+
.project
|
|
41
|
+
.settings
|
|
42
|
+
.springBeans
|
|
43
|
+
.sts4-cache
|
|
44
|
+
|
|
45
|
+
# IntelliJ IDEA
|
|
46
|
+
.idea/
|
|
47
|
+
*.iws
|
|
48
|
+
*.iml
|
|
49
|
+
*.ipr
|
|
50
|
+
|
|
51
|
+
# VS Code
|
|
52
|
+
.vscode/
|
|
53
|
+
|
|
54
|
+
# NetBeans
|
|
55
|
+
/nbproject/private/
|
|
56
|
+
/nbbuild/
|
|
57
|
+
/nbdist/
|
|
58
|
+
/.nb-gradle/
|
|
59
|
+
|
|
60
|
+
# Environment
|
|
61
|
+
.env
|
|
62
|
+
.env.local
|
|
63
|
+
.env.*.local
|
|
64
|
+
!.env.example
|
|
65
|
+
application-local.yml
|
|
66
|
+
application-local.properties
|
|
67
|
+
application-dev.yml
|
|
68
|
+
application-dev.properties
|
|
69
|
+
|
|
70
|
+
# OS
|
|
71
|
+
.DS_Store
|
|
72
|
+
Thumbs.db
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
version: '3.8'
|
|
2
|
-
|
|
3
1
|
services:
|
|
4
|
-
|
|
2
|
+
db:
|
|
5
3
|
image: postgres:15-alpine
|
|
6
4
|
container_name: {{projectNameKebabCase}}-db
|
|
7
5
|
environment:
|
|
@@ -13,6 +11,11 @@ services:
|
|
|
13
11
|
volumes:
|
|
14
12
|
- postgres_data:/var/lib/postgresql/data
|
|
15
13
|
restart: unless-stopped
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
16
|
+
interval: 10s
|
|
17
|
+
timeout: 5s
|
|
18
|
+
retries: 5
|
|
16
19
|
|
|
17
20
|
volumes:
|
|
18
21
|
postgres_data:
|
|
@@ -22,23 +22,24 @@ public class PaymentUseCase {
|
|
|
22
22
|
this.stripeGateway = stripeGateway;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
public String createCheckoutSession(String userId, String
|
|
26
|
-
|
|
27
|
-
import {{packageName}}.domain.l.UUID.fromString(userId))
|
|
25
|
+
public String createCheckoutSession(String userId, String priceId) throws Exception {
|
|
26
|
+
User user = userRepository.findById(userId)
|
|
28
27
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
|
29
28
|
|
|
30
29
|
String customerId = user.getStripeCustomerId();
|
|
31
30
|
|
|
32
31
|
if (customerId == null || customerId.isEmpty()) {
|
|
33
|
-
|
|
32
|
+
customerId = stripeGateway.createCustomer(user.getEmail(), userId).getId();
|
|
34
33
|
user.setStripeCustomerId(customerId);
|
|
35
34
|
userRepository.save(user);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
Session session = stripeGateway.createCheckoutSession(customerId, priceId, userId);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
return session.getUrl();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public String createPortalSession(String userId) throws Exception {
|
|
42
|
+
User user = userRepository.findById(userId)
|
|
42
43
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
|
43
44
|
|
|
44
45
|
if (user.getStripeCustomerId() == null) {
|
|
@@ -56,7 +57,7 @@ import {{packageName}}.domain.l.UUID.fromString(userId))
|
|
|
56
57
|
Session session = (Session) event.getDataObjectDeserializer().getObject().orElse(null);
|
|
57
58
|
if (session != null && session.getClientReferenceId() != null) {
|
|
58
59
|
String userId = session.getClientReferenceId();
|
|
59
|
-
userRepository.findById(
|
|
60
|
+
userRepository.findById(userId).ifPresent(user -> {
|
|
60
61
|
user.setStripeCustomerId(session.getCustomer());
|
|
61
62
|
userRepository.save(user);
|
|
62
63
|
});
|
|
@@ -65,21 +66,24 @@ import {{packageName}}.domain.l.UUID.fromString(userId))
|
|
|
65
66
|
break;
|
|
66
67
|
}
|
|
67
68
|
case "customer.subscription.updated": {
|
|
68
|
-
com.stripe.model.Subscription sub =
|
|
69
|
-
|
|
70
|
-
if (sub != null)
|
|
69
|
+
com.stripe.model.Subscription sub = (com.stripe.model.Subscription) event.getDataObjectDeserializer()
|
|
70
|
+
.getObject().orElse(null);
|
|
71
|
+
if (sub != null)
|
|
72
|
+
logger.info("Subscription updated: {} | Status: {}", sub.getId(), sub.getStatus());
|
|
71
73
|
break;
|
|
72
74
|
}
|
|
73
75
|
case "customer.subscription.deleted": {
|
|
74
|
-
com.stripe.model.Subscription sub =
|
|
75
|
-
|
|
76
|
-
if (sub != null)
|
|
76
|
+
com.stripe.model.Subscription sub = (com.stripe.model.Subscription) event.getDataObjectDeserializer()
|
|
77
|
+
.getObject().orElse(null);
|
|
78
|
+
if (sub != null)
|
|
79
|
+
logger.info("Subscription deleted: {}", sub.getId());
|
|
77
80
|
break;
|
|
78
81
|
}
|
|
79
82
|
case "invoice.payment_failed": {
|
|
80
|
-
com.stripe.model.Invoice invoice =
|
|
81
|
-
|
|
82
|
-
if (invoice != null)
|
|
83
|
+
com.stripe.model.Invoice invoice = (com.stripe.model.Invoice) event.getDataObjectDeserializer()
|
|
84
|
+
.getObject().orElse(null);
|
|
85
|
+
if (invoice != null)
|
|
86
|
+
logger.info("Payment failed for invoice: {}", invoice.getId());
|
|
83
87
|
break;
|
|
84
88
|
}
|
|
85
89
|
default:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package {{packageName}}.infrastructure.persistence.entity;
|
|
2
|
+
|
|
3
|
+
import {{packageName}}.domain.entity.User;
|
|
4
|
+
import jakarta.persistence.Entity;
|
|
5
|
+
import jakarta.persistence.Id;
|
|
6
|
+
import jakarta.persistence.Table;
|
|
7
|
+
import lombok.AllArgsConstructor;
|
|
8
|
+
import lombok.Builder;
|
|
9
|
+
import lombok.Data;
|
|
10
|
+
import lombok.NoArgsConstructor;
|
|
11
|
+
|
|
12
|
+
import java.time.LocalDateTime;
|
|
13
|
+
|
|
14
|
+
@Entity
|
|
15
|
+
@Table(name = "users")
|
|
16
|
+
@Data
|
|
17
|
+
@NoArgsConstructor
|
|
18
|
+
@AllArgsConstructor
|
|
19
|
+
@Builder
|
|
20
|
+
public class UserEntity {
|
|
21
|
+
|
|
22
|
+
@Id
|
|
23
|
+
private String id;
|
|
24
|
+
|
|
25
|
+
private String email;
|
|
26
|
+
private String name;
|
|
27
|
+
private String password;
|
|
28
|
+
private String stripeCustomerId;
|
|
29
|
+
private LocalDateTime createdAt;
|
|
30
|
+
|
|
31
|
+
public static UserEntity fromDomain(User user) {
|
|
32
|
+
return UserEntity.builder()
|
|
33
|
+
.id(user.getId())
|
|
34
|
+
.email(user.getEmail())
|
|
35
|
+
.name(user.getName())
|
|
36
|
+
.password(user.getPassword())
|
|
37
|
+
.stripeCustomerId(user.getStripeCustomerId())
|
|
38
|
+
.createdAt(user.getCreatedAt())
|
|
39
|
+
.build();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public User toDomain() {
|
|
43
|
+
return User.restore(
|
|
44
|
+
this.id,
|
|
45
|
+
this.email,
|
|
46
|
+
this.name,
|
|
47
|
+
this.password,
|
|
48
|
+
this.stripeCustomerId,
|
|
49
|
+
this.createdAt
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package {{packageName}}.infrastructure.persistence.repository;
|
|
2
|
+
|
|
3
|
+
import {{packageName}}.infrastructure.persistence.entity.UserEntity;
|
|
4
|
+
import org.springframework.data.jpa.repository.JpaRepository;
|
|
5
|
+
import org.springframework.stereotype.Repository;
|
|
6
|
+
|
|
7
|
+
import java.util.Optional;
|
|
8
|
+
|
|
9
|
+
@Repository
|
|
10
|
+
public interface JpaUserRepository extends JpaRepository<UserEntity, String> {
|
|
11
|
+
Optional<UserEntity> findByEmail(String email);
|
|
12
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package {{packageName}}.infrastructure.security;
|
|
2
|
+
|
|
3
|
+
import io.jsonwebtoken.Claims;
|
|
4
|
+
import io.jsonwebtoken.Jwts;
|
|
5
|
+
import io.jsonwebtoken.security.Keys;
|
|
6
|
+
import jakarta.servlet.FilterChain;
|
|
7
|
+
import jakarta.servlet.ServletException;
|
|
8
|
+
import jakarta.servlet.http.HttpServletRequest;
|
|
9
|
+
import jakarta.servlet.http.HttpServletResponse;
|
|
10
|
+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
11
|
+
import org.springframework.security.core.context.SecurityContextHolder;
|
|
12
|
+
import org.springframework.security.core.userdetails.User;
|
|
13
|
+
import org.springframework.security.core.userdetails.UserDetails;
|
|
14
|
+
import org.springframework.stereotype.Component;
|
|
15
|
+
import org.springframework.web.filter.OncePerRequestFilter;
|
|
16
|
+
|
|
17
|
+
import javax.crypto.SecretKey;
|
|
18
|
+
import java.io.IOException;
|
|
19
|
+
import java.nio.charset.StandardCharsets;
|
|
20
|
+
import java.util.ArrayList;
|
|
21
|
+
|
|
22
|
+
@Component
|
|
23
|
+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
24
|
+
|
|
25
|
+
private final String jwtSecret;
|
|
26
|
+
|
|
27
|
+
public JwtAuthenticationFilter(
|
|
28
|
+
@org.springframework.beans.factory.annotation.Value("${jwt.secret:your-super-secret-jwt-key-change-in-production}") String jwtSecret) {
|
|
29
|
+
this.jwtSecret = jwtSecret;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private SecretKey getSigningKey() {
|
|
33
|
+
return Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Override
|
|
37
|
+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
38
|
+
throws ServletException, IOException {
|
|
39
|
+
String header = request.getHeader("Authorization");
|
|
40
|
+
|
|
41
|
+
if (header != null && header.startsWith("Bearer ")) {
|
|
42
|
+
String token = header.substring(7);
|
|
43
|
+
try {
|
|
44
|
+
Claims claims = Jwts.parser()
|
|
45
|
+
.verifyWith(getSigningKey())
|
|
46
|
+
.build()
|
|
47
|
+
.parseSignedClaims(token)
|
|
48
|
+
.getPayload();
|
|
49
|
+
|
|
50
|
+
String userId = claims.getSubject();
|
|
51
|
+
if (userId != null) {
|
|
52
|
+
UserDetails userDetails = new User(userId, "", new ArrayList<>());
|
|
53
|
+
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
|
54
|
+
userDetails, null, userDetails.getAuthorities());
|
|
55
|
+
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
56
|
+
}
|
|
57
|
+
} catch (Exception e) {
|
|
58
|
+
logger.warn("Invalid JWT token: " + e.getMessage());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
filterChain.doFilter(request, response);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package {{packageName}}.infrastructure.security;
|
|
2
|
+
|
|
3
|
+
import org.springframework.context.annotation.Bean;
|
|
4
|
+
import org.springframework.context.annotation.Configuration;
|
|
5
|
+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
6
|
+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
7
|
+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
8
|
+
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
9
|
+
import org.springframework.security.web.SecurityFilterChain;
|
|
10
|
+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
11
|
+
|
|
12
|
+
@Configuration
|
|
13
|
+
@EnableWebSecurity
|
|
14
|
+
public class SecurityConfig {
|
|
15
|
+
|
|
16
|
+
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
|
17
|
+
|
|
18
|
+
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
|
19
|
+
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Bean
|
|
23
|
+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
24
|
+
http
|
|
25
|
+
.csrf(AbstractHttpConfigurer::disable)
|
|
26
|
+
.authorizeHttpRequests(auth -> auth
|
|
27
|
+
.requestMatchers("/api/auth/**").permitAll()
|
|
28
|
+
.requestMatchers("/api/payments/webhook").permitAll()
|
|
29
|
+
.requestMatchers("/actuator/**").permitAll()
|
|
30
|
+
.anyRequest().authenticated()
|
|
31
|
+
)
|
|
32
|
+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
33
|
+
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
|
34
|
+
return http.build();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
package {{packageName}}.infrastructure.stripe;
|
|
2
|
+
|
|
3
|
+
import com.stripe.model.Customer;
|
|
4
|
+
import com.stripe.model.Event;
|
|
5
|
+
import com.stripe.model.checkout.Session;
|
|
6
|
+
import com.stripe.param.CustomerCreateParams;
|
|
7
|
+
import com.stripe.param.checkout.SessionCreateParams;
|
|
8
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
9
|
+
import org.springframework.stereotype.Service;
|
|
10
|
+
|
|
11
|
+
@Service
|
|
12
|
+
public class StripeGateway {
|
|
13
|
+
|
|
14
|
+
@Value("${stripe.secret-key:sk_test_dummy}")
|
|
15
|
+
private String secretKey;
|
|
16
|
+
|
|
17
|
+
@Value("${stripe.webhook-secret:whsec_dummy}")
|
|
18
|
+
private String webhookSecret;
|
|
19
|
+
|
|
20
|
+
@Value("${frontend.url:http://localhost:3000}")
|
|
21
|
+
private String frontendUrl;
|
|
22
|
+
|
|
23
|
+
@jakarta.annotation.PostConstruct
|
|
24
|
+
public void init() {
|
|
25
|
+
com.stripe.Stripe.apiKey = secretKey;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public Customer createCustomer(String email, String userId) throws Exception {
|
|
29
|
+
CustomerCreateParams params = CustomerCreateParams.builder()
|
|
30
|
+
.setEmail(email)
|
|
31
|
+
.putMetadata("userId", userId)
|
|
32
|
+
.build();
|
|
33
|
+
return Customer.create(params);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public Session createCheckoutSession(String customerId, String priceId, String userId) throws Exception {
|
|
37
|
+
SessionCreateParams params = SessionCreateParams.builder()
|
|
38
|
+
.setCustomer(customerId)
|
|
39
|
+
.setSuccessUrl(frontendUrl + "/success?session_id={CHECKOUT_SESSION_ID}")
|
|
40
|
+
.setCancelUrl(frontendUrl + "/cancel")
|
|
41
|
+
.addLineItem(SessionCreateParams.LineItem.builder()
|
|
42
|
+
.setPrice(priceId)
|
|
43
|
+
.setQuantity(1L)
|
|
44
|
+
.build())
|
|
45
|
+
.setMode(SessionCreateParams.Mode.SUBSCRIPTION)
|
|
46
|
+
.setClientReferenceId(userId)
|
|
47
|
+
.build();
|
|
48
|
+
return Session.create(params);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public com.stripe.model.billingportal.Session createPortalSession(String customerId) throws Exception {
|
|
52
|
+
com.stripe.param.billingportal.SessionCreateParams params =
|
|
53
|
+
com.stripe.param.billingportal.SessionCreateParams.builder()
|
|
54
|
+
.setCustomer(customerId)
|
|
55
|
+
.setReturnUrl(frontendUrl + "/account")
|
|
56
|
+
.build();
|
|
57
|
+
return com.stripe.model.billingportal.Session.create(params);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public Event constructWebhookEvent(String payload, String sigHeader) throws Exception {
|
|
61
|
+
return com.stripe.net.Webhook.constructEvent(payload, sigHeader, webhookSecret);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
spring.application.name={{projectNameKebabCase}}
|
|
2
2
|
|
|
3
3
|
# Database Configuration
|
|
4
|
-
spring.datasource.url=jdbc:postgresql
|
|
4
|
+
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:5432/{{projectNameKebabCase}}
|
|
5
5
|
spring.datasource.username=postgres
|
|
6
6
|
spring.datasource.password=postgres
|
|
7
7
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
|
@@ -12,14 +12,13 @@ spring.jpa.hibernate.ddl-auto=update
|
|
|
12
12
|
spring.jpa.show-sql=true
|
|
13
13
|
spring.jpa.properties.hibernate.format_sql=true
|
|
14
14
|
|
|
15
|
-
# JWT
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
application.security.jwt.refresh-token.expiration=604800000
|
|
15
|
+
# JWT (property name matches SecurityAdapters.java @Value)
|
|
16
|
+
jwt.secret=${JWT_SECRET:{{jwtSecretKey}}}
|
|
17
|
+
jwt.expiration=86400000
|
|
19
18
|
|
|
20
19
|
# Stripe
|
|
21
|
-
stripe.secret-key=${STRIPE_SECRET_KEY}
|
|
22
|
-
stripe.webhook-secret=${STRIPE_WEBHOOK_SECRET}
|
|
20
|
+
stripe.secret-key=${STRIPE_SECRET_KEY:sk_test_dummy}
|
|
21
|
+
stripe.webhook-secret=${STRIPE_WEBHOOK_SECRET:whsec_dummy}
|
|
23
22
|
|
|
24
23
|
# Frontend URL (for Stripe redirect URLs)
|
|
25
24
|
frontend.url=${FRONTEND_URL:http://localhost:3000}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Compiled class files
|
|
2
|
+
*.class
|
|
3
|
+
|
|
4
|
+
# Log files
|
|
5
|
+
*.log
|
|
6
|
+
|
|
7
|
+
# Package files
|
|
8
|
+
*.jar
|
|
9
|
+
*.war
|
|
10
|
+
*.nar
|
|
11
|
+
*.ear
|
|
12
|
+
*.zip
|
|
13
|
+
*.tar.gz
|
|
14
|
+
*.rar
|
|
15
|
+
|
|
16
|
+
# Virtual machine crash logs
|
|
17
|
+
hs_err_pid*
|
|
18
|
+
replay_pid*
|
|
19
|
+
|
|
20
|
+
# Maven
|
|
21
|
+
target/
|
|
22
|
+
.mvn/wrapper/maven-wrapper.jar
|
|
23
|
+
!**/src/main/**/target/
|
|
24
|
+
!**/src/test/**/target/
|
|
25
|
+
|
|
26
|
+
# Gradle
|
|
27
|
+
.gradle
|
|
28
|
+
build/
|
|
29
|
+
!gradle/wrapper/gradle-wrapper.jar
|
|
30
|
+
!**/src/main/**/build/
|
|
31
|
+
!**/src/test/**/build/
|
|
32
|
+
|
|
33
|
+
# Spring Boot
|
|
34
|
+
spring-shell.log
|
|
35
|
+
|
|
36
|
+
# STS / Eclipse
|
|
37
|
+
.apt_generated
|
|
38
|
+
.classpath
|
|
39
|
+
.factorypath
|
|
40
|
+
.project
|
|
41
|
+
.settings
|
|
42
|
+
.springBeans
|
|
43
|
+
.sts4-cache
|
|
44
|
+
|
|
45
|
+
# IntelliJ IDEA
|
|
46
|
+
.idea/
|
|
47
|
+
*.iws
|
|
48
|
+
*.iml
|
|
49
|
+
*.ipr
|
|
50
|
+
|
|
51
|
+
# VS Code
|
|
52
|
+
.vscode/
|
|
53
|
+
|
|
54
|
+
# NetBeans
|
|
55
|
+
/nbproject/private/
|
|
56
|
+
/nbbuild/
|
|
57
|
+
/nbdist/
|
|
58
|
+
/.nb-gradle/
|
|
59
|
+
|
|
60
|
+
# Environment
|
|
61
|
+
.env
|
|
62
|
+
.env.local
|
|
63
|
+
.env.*.local
|
|
64
|
+
!.env.example
|
|
65
|
+
application-local.yml
|
|
66
|
+
application-local.properties
|
|
67
|
+
application-dev.yml
|
|
68
|
+
application-dev.properties
|
|
69
|
+
|
|
70
|
+
# OS
|
|
71
|
+
.DS_Store
|
|
72
|
+
Thumbs.db
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
version: '3.8'
|
|
2
|
-
|
|
3
1
|
services:
|
|
4
|
-
|
|
2
|
+
db:
|
|
5
3
|
image: postgres:15-alpine
|
|
6
4
|
container_name: {{projectNameKebabCase}}-db
|
|
7
5
|
environment:
|
|
@@ -13,6 +11,11 @@ services:
|
|
|
13
11
|
volumes:
|
|
14
12
|
- postgres_data:/var/lib/postgresql/data
|
|
15
13
|
restart: unless-stopped
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
16
|
+
interval: 10s
|
|
17
|
+
timeout: 5s
|
|
18
|
+
retries: 5
|
|
16
19
|
|
|
17
20
|
volumes:
|
|
18
21
|
postgres_data:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package {{packageName}}.adapters.outbound.security;
|
|
2
|
+
|
|
3
|
+
import io.jsonwebtoken.Claims;
|
|
4
|
+
import io.jsonwebtoken.Jwts;
|
|
5
|
+
import io.jsonwebtoken.security.Keys;
|
|
6
|
+
import jakarta.servlet.FilterChain;
|
|
7
|
+
import jakarta.servlet.ServletException;
|
|
8
|
+
import jakarta.servlet.http.HttpServletRequest;
|
|
9
|
+
import jakarta.servlet.http.HttpServletResponse;
|
|
10
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
11
|
+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
12
|
+
import org.springframework.security.core.context.SecurityContextHolder;
|
|
13
|
+
import org.springframework.security.core.userdetails.User;
|
|
14
|
+
import org.springframework.security.core.userdetails.UserDetails;
|
|
15
|
+
import org.springframework.stereotype.Component;
|
|
16
|
+
import org.springframework.web.filter.OncePerRequestFilter;
|
|
17
|
+
|
|
18
|
+
import javax.crypto.SecretKey;
|
|
19
|
+
import java.io.IOException;
|
|
20
|
+
import java.nio.charset.StandardCharsets;
|
|
21
|
+
import java.util.Collections;
|
|
22
|
+
|
|
23
|
+
@Component
|
|
24
|
+
public class JwtFilter extends OncePerRequestFilter {
|
|
25
|
+
|
|
26
|
+
@Value("${application.security.jwt.secret-key}")
|
|
27
|
+
private String jwtSecret;
|
|
28
|
+
|
|
29
|
+
private SecretKey getSigningKey() {
|
|
30
|
+
return Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Override
|
|
34
|
+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
35
|
+
throws ServletException, IOException {
|
|
36
|
+
|
|
37
|
+
final String authHeader = request.getHeader("Authorization");
|
|
38
|
+
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
|
39
|
+
filterChain.doFilter(request, response);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
final String jwt = authHeader.substring(7);
|
|
44
|
+
try {
|
|
45
|
+
Claims claims = Jwts.parser()
|
|
46
|
+
.verifyWith(getSigningKey())
|
|
47
|
+
.build()
|
|
48
|
+
.parseSignedClaims(jwt)
|
|
49
|
+
.getPayload();
|
|
50
|
+
|
|
51
|
+
String userId = claims.getSubject();
|
|
52
|
+
|
|
53
|
+
if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
|
54
|
+
UserDetails userDetails = User.builder()
|
|
55
|
+
.username(userId)
|
|
56
|
+
.password("")
|
|
57
|
+
.authorities(Collections.emptyList())
|
|
58
|
+
.build();
|
|
59
|
+
|
|
60
|
+
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
|
61
|
+
userDetails, null, userDetails.getAuthorities());
|
|
62
|
+
|
|
63
|
+
SecurityContextHolder.getContext().setAuthentication(authToken);
|
|
64
|
+
}
|
|
65
|
+
} catch (Exception e) {
|
|
66
|
+
// Token invalid or expired — continue without setting authentication
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
filterChain.doFilter(request, response);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package {{packageName}}.adapters.outbound.security;
|
|
2
|
+
|
|
3
|
+
import org.springframework.context.annotation.Bean;
|
|
4
|
+
import org.springframework.context.annotation.Configuration;
|
|
5
|
+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
6
|
+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
7
|
+
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
8
|
+
import org.springframework.security.web.SecurityFilterChain;
|
|
9
|
+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
10
|
+
|
|
11
|
+
@Configuration
|
|
12
|
+
@EnableWebSecurity
|
|
13
|
+
public class SecurityConfig {
|
|
14
|
+
|
|
15
|
+
private final JwtFilter jwtFilter;
|
|
16
|
+
|
|
17
|
+
public SecurityConfig(JwtFilter jwtFilter) {
|
|
18
|
+
this.jwtFilter = jwtFilter;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Bean
|
|
22
|
+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
23
|
+
return http
|
|
24
|
+
.csrf(csrf -> csrf.disable())
|
|
25
|
+
.authorizeHttpRequests(auth -> auth
|
|
26
|
+
.requestMatchers("/api/auth/**").permitAll()
|
|
27
|
+
.requestMatchers("/api/payments/webhook").permitAll()
|
|
28
|
+
.requestMatchers("/actuator/**").permitAll()
|
|
29
|
+
.anyRequest().authenticated()
|
|
30
|
+
)
|
|
31
|
+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
32
|
+
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
|
33
|
+
.build();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -23,7 +23,7 @@ public class PaymentService {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
public String createCheckoutSession(String userId, String priceId) throws Exception {
|
|
26
|
-
User user = userRepository.findById(
|
|
26
|
+
User user = userRepository.findById(userId)
|
|
27
27
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
|
28
28
|
|
|
29
29
|
String customerId = user.getStripeCustomerId();
|
|
@@ -39,7 +39,7 @@ public class PaymentService {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
public String createPortalSession(String userId) throws Exception {
|
|
42
|
-
User user = userRepository.findById(
|
|
42
|
+
User user = userRepository.findById(userId)
|
|
43
43
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
|
44
44
|
|
|
45
45
|
if (user.getStripeCustomerId() == null) {
|
|
@@ -57,7 +57,7 @@ public class PaymentService {
|
|
|
57
57
|
Session session = (Session) event.getDataObjectDeserializer().getObject().orElse(null);
|
|
58
58
|
if (session != null && session.getClientReferenceId() != null) {
|
|
59
59
|
String userId = session.getClientReferenceId();
|
|
60
|
-
userRepository.findById(
|
|
60
|
+
userRepository.findById(userId).ifPresent(user -> {
|
|
61
61
|
user.setStripeCustomerId(session.getCustomer());
|
|
62
62
|
userRepository.save(user);
|
|
63
63
|
});
|