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.
Files changed (106) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/templates/java-spring/clean/.gitignore.hbs +72 -0
  4. package/templates/java-spring/clean/docker-compose.yml.hbs +6 -3
  5. package/templates/java-spring/clean/src/main/java/{{packagePath}}/application/usecase/PaymentUseCase.java.hbs +21 -17
  6. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/entity/UserEntity.java.hbs +52 -0
  7. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/repository/JpaUserRepository.java.hbs +12 -0
  8. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/JwtAuthenticationFilter.java.hbs +64 -0
  9. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/SecurityConfig.java.hbs +36 -0
  10. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/stripe/StripeGateway.java.hbs +63 -0
  11. package/templates/java-spring/clean/src/main/resources/application.properties.hbs +6 -7
  12. package/templates/java-spring/hexagonal/.gitignore.hbs +72 -0
  13. package/templates/java-spring/hexagonal/docker-compose.yml.hbs +6 -3
  14. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/JwtFilter.java.hbs +71 -0
  15. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/SecurityConfig.java.hbs +35 -0
  16. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/core/service/PaymentService.java.hbs +3 -3
  17. package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +4 -4
  18. package/templates/java-spring/mvc/.gitignore.hbs +72 -0
  19. package/templates/java-spring/mvc/docker-compose.yml.hbs +6 -3
  20. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/config/SecurityConfig.java.hbs +13 -12
  21. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/AuthController.java.hbs +9 -8
  22. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/PaymentsController.java.hbs +5 -6
  23. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/StripeService.java.hbs +3 -3
  24. package/templates/java-spring/mvc/src/main/resources/application.yml.hbs +29 -26
  25. package/templates/nestjs/clean/.gitignore.hbs +42 -0
  26. package/templates/nestjs/clean/Dockerfile.hbs +6 -3
  27. package/templates/nestjs/clean/docker-compose.yml.hbs +1 -11
  28. package/templates/nestjs/clean/src/app.module.ts.hbs +2 -1
  29. package/templates/nestjs/clean/src/application/payment.service.ts.hbs +72 -72
  30. package/templates/nestjs/clean/src/domain/entities/user.entity.ts.hbs +2 -2
  31. package/templates/nestjs/clean/src/domain/repositories/user.repository.ts.hbs +2 -2
  32. package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +18 -18
  33. package/templates/nestjs/clean/src/infrastructure/http/health.controller.ts.hbs +9 -0
  34. package/templates/nestjs/clean/src/main.ts.hbs +1 -4
  35. package/templates/nestjs/clean/src/payment.module.ts.hbs +12 -12
  36. package/templates/nestjs/hexagonal/.gitignore.hbs +42 -0
  37. package/templates/nestjs/hexagonal/Dockerfile.hbs +6 -3
  38. package/templates/nestjs/hexagonal/docker-compose.yml.hbs +1 -11
  39. package/templates/nestjs/hexagonal/src/adapters/inbound/health.controller.ts.hbs +9 -0
  40. package/templates/nestjs/hexagonal/src/app.module.ts.hbs +2 -1
  41. package/templates/nestjs/hexagonal/src/core/domain/user.entity.ts.hbs +6 -6
  42. package/templates/nestjs/hexagonal/src/core/ports/ports.ts.hbs +4 -4
  43. package/templates/nestjs/hexagonal/src/main.ts.hbs +1 -4
  44. package/templates/nestjs/mvc/.gitignore.hbs +42 -0
  45. package/templates/nestjs/mvc/Dockerfile.hbs +6 -3
  46. package/templates/nestjs/mvc/docker-compose.yml.hbs +1 -11
  47. package/templates/nestjs/mvc/src/auth/auth.controller.ts.hbs +11 -1
  48. package/templates/nestjs/mvc/src/auth/auth.service.ts.hbs +3 -1
  49. package/templates/nestjs/mvc/src/controllers/health.controller.ts.hbs +6 -6
  50. package/templates/nestjs/mvc/src/main.ts.hbs +1 -4
  51. package/templates/nestjs/mvc/src/models/create-item.dto.ts.hbs +5 -2
  52. package/templates/nestjs/mvc/src/prisma/prisma.service.ts.hbs +1 -0
  53. package/templates/nextjs/mvc/.gitignore.hbs +42 -0
  54. package/templates/nextjs/mvc/Dockerfile.hbs +23 -8
  55. package/templates/nextjs/mvc/docker-compose.yml.hbs +1 -1
  56. package/templates/nodejs-express/clean/.gitignore.hbs +42 -0
  57. package/templates/nodejs-express/clean/Dockerfile.hbs +6 -1
  58. package/templates/nodejs-express/clean/docker-compose.yml.hbs +2 -2
  59. package/templates/nodejs-express/clean/package.json.hbs +69 -69
  60. package/templates/nodejs-express/clean/src/config.ts.hbs +11 -0
  61. package/templates/nodejs-express/clean/src/domain/entities/User.ts.hbs +46 -8
  62. package/templates/nodejs-express/hexagonal/.gitignore.hbs +42 -0
  63. package/templates/nodejs-express/hexagonal/Dockerfile.hbs +1 -1
  64. package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +2 -2
  65. package/templates/nodejs-express/hexagonal/package.json.hbs +69 -69
  66. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/PaymentController.ts.hbs +21 -38
  67. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts.hbs +2 -0
  68. package/templates/nodejs-express/hexagonal/src/config.ts.hbs +9 -0
  69. package/templates/nodejs-express/hexagonal/src/core/AuthService.ts.hbs +5 -5
  70. package/templates/nodejs-express/hexagonal/src/core/PaymentService.ts.hbs +7 -22
  71. package/templates/nodejs-express/hexagonal/src/core/domain/entities/User.ts.hbs +24 -4
  72. package/templates/nodejs-express/mvc/.gitignore.hbs +42 -0
  73. package/templates/nodejs-express/mvc/package.json.hbs +67 -67
  74. package/templates/python-fastapi/clean/.gitignore.hbs +76 -0
  75. package/templates/python-fastapi/clean/app/application/services/payment_service.py.hbs +3 -3
  76. package/templates/python-fastapi/clean/app/config.py.hbs +6 -7
  77. package/templates/python-fastapi/clean/app/domain/usecases/login_user.py.hbs +15 -0
  78. package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +40 -6
  79. package/templates/python-fastapi/clean/app/infrastructure/http/payment_controller.py.hbs +5 -4
  80. package/templates/python-fastapi/clean/app/infrastructure/security/jwt.py.hbs +23 -0
  81. package/templates/python-fastapi/clean/app/main.py.hbs +3 -0
  82. package/templates/python-fastapi/clean/docker-compose.yml.hbs +5 -12
  83. package/templates/python-fastapi/clean/requirements.txt.hbs +3 -0
  84. package/templates/python-fastapi/hexagonal/.gitignore.hbs +76 -0
  85. package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +6 -9
  86. package/templates/python-fastapi/hexagonal/app/adapters/inbound/payment_http_adapter.py.hbs +4 -3
  87. package/templates/python-fastapi/hexagonal/app/adapters/outbound/stripe_adapter.py.hbs +30 -19
  88. package/templates/python-fastapi/hexagonal/app/config.py.hbs +14 -4
  89. package/templates/python-fastapi/hexagonal/app/core/domain/user.py.hbs +3 -1
  90. package/templates/python-fastapi/hexagonal/app/core/payment_service.py.hbs +28 -18
  91. package/templates/python-fastapi/hexagonal/app/core/ports/__init__.py.hbs +3 -0
  92. package/templates/python-fastapi/hexagonal/app/core/ports/user_repository.py.hbs +15 -0
  93. package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +7 -0
  94. package/templates/python-fastapi/hexagonal/app/infrastructure/database/user_repository.py.hbs +53 -0
  95. package/templates/python-fastapi/hexagonal/app/infrastructure/security/__init__.py.hbs +0 -0
  96. package/templates/python-fastapi/hexagonal/app/infrastructure/security/adapters.py.hbs +23 -0
  97. package/templates/python-fastapi/hexagonal/app/infrastructure/security/jwt.py.hbs +23 -0
  98. package/templates/python-fastapi/hexagonal/docker-compose.yml.hbs +5 -12
  99. package/templates/python-fastapi/hexagonal/requirements.txt.hbs +4 -0
  100. package/templates/python-fastapi/mvc/.gitignore.hbs +76 -0
  101. package/templates/python-fastapi/mvc/app/controllers/payments.py.hbs +3 -17
  102. package/templates/python-fastapi/mvc/app/middleware/security.py.hbs +24 -3
  103. package/templates/python-fastapi/mvc/app/schemas/item.py.hbs +3 -1
  104. package/templates/python-fastapi/mvc/docker-compose.yml.hbs +5 -12
  105. package/templates/python-fastapi/mvc/requirements.txt.hbs +3 -1
  106. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +0 -5
@@ -1,7 +1,7 @@
1
1
  spring.application.name={{projectNameKebabCase}}
2
2
 
3
3
  # Database Configuration
4
- spring.datasource.url=jdbc:postgresql://postgres:5432/{{projectNameKebabCase}}
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
@@ -13,13 +13,13 @@ spring.jpa.show-sql=true
13
13
  spring.jpa.properties.hibernate.format_sql=true
14
14
 
15
15
  # JWT
16
- application.security.jwt.secret-key={{jwtSecretKey}}
16
+ application.security.jwt.secret-key=${JWT_SECRET:{{jwtSecretKey}}}
17
17
  application.security.jwt.expiration=86400000
18
18
  application.security.jwt.refresh-token.expiration=604800000
19
19
 
20
20
  # Stripe
21
- stripe.secret-key=${STRIPE_SECRET_KEY}
22
- stripe.webhook-secret=${STRIPE_WEBHOOK_SECRET}
21
+ stripe.secret-key=${STRIPE_SECRET_KEY:sk_test_dummy}
22
+ stripe.webhook-secret=${STRIPE_WEBHOOK_SECRET:whsec_dummy}
23
23
 
24
24
  # Frontend URL (for Stripe redirect URLs)
25
25
  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
- postgres:
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:
@@ -27,18 +27,19 @@ public class SecurityConfig {
27
27
  .csrf(csrf -> csrf.disable())
28
28
  .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
29
29
  .authorizeHttpRequests(auth -> auth
30
- .requestMatchers("/api/auth/**").permitAll()
31
- .requestMatchers("/api/payments/webhook").permitAll()
32
- .requestMatchers("/actuator/health").permitAll()
33
- .anyRequest().authenticated()
34
- )
35
- .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
30
+ .requestMatchers("/api/auth/**").permitAll()
31
+ .requestMatchers("/api/payments/webhook").permitAll()
32
+ .requestMatchers("/api/health").permitAll()
33
+ .requestMatchers("/actuator/health").permitAll()
34
+ .anyRequest().authenticated()
35
+ )
36
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
36
37
 
37
- return http.build();
38
- }
38
+ return http.build();
39
+ }
39
40
 
40
- @Bean
41
- public PasswordEncoder passwordEncoder() {
42
- return new BCryptPasswordEncoder();
41
+ @Bean
42
+ public PasswordEncoder passwordEncoder() {
43
+ return new BCryptPasswordEncoder();
44
+ }
43
45
  }
44
- }
@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.*;
10
10
  import org.springframework.security.core.context.SecurityContextHolder;
11
11
  import org.springframework.security.core.Authentication;
12
12
 
13
+ import java.util.HashMap;
13
14
  import java.util.Map;
14
15
 
15
16
  @RestController
@@ -48,17 +49,17 @@ public class AuthController {
48
49
  if (authentication == null || !authentication.isAuthenticated()) {
49
50
  return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
50
51
  }
51
-
52
+
52
53
  try {
53
- String userId = (String) authentication.getPrincipal(); // Assuming custom jwt filter sets principal to id
54
+ String userId = (String) authentication.getPrincipal();
54
55
  User user = authService.getMe(userId);
55
- return ResponseEntity.ok(Map.of(
56
- "id", user.getId(),
57
- "email", user.getEmail(),
58
- "name", user.getName()
59
- ));
56
+ // Use HashMap instead of Map.of() — tolerates null values (e.g. name may be null)
57
+ Map<String, Object> result = new HashMap<>();
58
+ result.put("id", user.getId());
59
+ result.put("email", user.getEmail());
60
+ result.put("name", user.getName());
61
+ return ResponseEntity.ok(result);
60
62
  } catch (Exception e) {
61
- // Keep original behavior if principal isn't id
62
63
  return ResponseEntity.ok(Map.of("message", "Authenticated"));
63
64
  }
64
65
  }
@@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory;
6
6
  import org.springframework.http.HttpStatus;
7
7
  import org.springframework.http.ResponseEntity;
8
8
  import org.springframework.security.core.annotation.AuthenticationPrincipal;
9
- import org.springframework.security.core.userdetails.UserDetails;
10
9
  import org.springframework.web.bind.annotation.*;
11
10
 
12
11
  import java.util.Map;
@@ -29,16 +28,16 @@ public class PaymentsController {
29
28
  */
30
29
  @PostMapping("/checkout")
31
30
  public ResponseEntity<?> createCheckout(
32
- @AuthenticationPrincipal UserDetails userDetails,
31
+ @AuthenticationPrincipal String userId,
33
32
  @RequestBody Map<String, String> body) {
34
33
  try {
35
34
  String priceId = body.get("priceId");
36
35
  if (priceId == null || priceId.isEmpty()) {
37
36
  return ResponseEntity.badRequest().body(Map.of("error", "priceId is required"));
38
37
  }
39
- // userDetails.getUsername() returns the user ID (set in JWT filter)
38
+ // userId is set directly in SecurityContextHolder by JwtAuthenticationFilter
40
39
  com.stripe.model.checkout.Session session =
41
- stripeService.createCheckoutSession(userDetails.getUsername(), priceId);
40
+ stripeService.createCheckoutSession(userId, priceId);
42
41
  return ResponseEntity.ok(Map.of("url", session.getUrl()));
43
42
  } catch (Exception e) {
44
43
  logger.error("Error creating checkout session", e);
@@ -51,10 +50,10 @@ public class PaymentsController {
51
50
  * Requires authentication. Opens Stripe Billing Portal.
52
51
  */
53
52
  @PostMapping("/portal")
54
- public ResponseEntity<?> createPortal(@AuthenticationPrincipal UserDetails userDetails) {
53
+ public ResponseEntity<?> createPortal(@AuthenticationPrincipal String userId) {
55
54
  try {
56
55
  com.stripe.model.billingportal.Session session =
57
- stripeService.createPortalSession(userDetails.getUsername());
56
+ stripeService.createPortalSession(userId);
58
57
  return ResponseEntity.ok(Map.of("url", session.getUrl()));
59
58
  } catch (Exception e) {
60
59
  logger.error("Error creating portal session", e);
@@ -45,7 +45,7 @@ public class StripeService {
45
45
  * Retrieve or create a Stripe customer for the given user, then open a checkout session.
46
46
  */
47
47
  public Session createCheckoutSession(String userId, String priceId) throws StripeException {
48
- User user = userRepository.findById(java.util.UUID.fromString(userId))
48
+ User user = userRepository.findById(userId)
49
49
  .orElseThrow(() -> new RuntimeException("User not found"));
50
50
 
51
51
  String customerId = user.getStripeCustomerId();
@@ -81,7 +81,7 @@ public class StripeService {
81
81
  * Open the Stripe Billing Portal for the given customer.
82
82
  */
83
83
  public com.stripe.model.billingportal.Session createPortalSession(String userId) throws StripeException {
84
- User user = userRepository.findById(java.util.UUID.fromString(userId))
84
+ User user = userRepository.findById(userId)
85
85
  .orElseThrow(() -> new RuntimeException("User not found"));
86
86
 
87
87
  if (user.getStripeCustomerId() == null) {
@@ -113,7 +113,7 @@ public class StripeService {
113
113
  Session session = (Session) event.getDataObjectDeserializer().getObject().orElse(null);
114
114
  if (session != null && session.getClientReferenceId() != null) {
115
115
  String userId = session.getClientReferenceId();
116
- userRepository.findById(java.util.UUID.fromString(userId)).ifPresent(user -> {
116
+ userRepository.findById(userId).ifPresent(user -> {
117
117
  user.setStripeCustomerId(session.getCustomer());
118
118
  userRepository.save(user);
119
119
  });
@@ -1,32 +1,35 @@
1
1
  spring:
2
2
  application:
3
3
  name: {{projectNameKebabCase}}
4
-
5
- datasource:
6
- url: jdbc:postgresql://localhost:5432/{{projectNameSnakeCase}}
7
- username: postgres
8
- password: postgres
9
- driver-class-name: org.postgresql.Driver
10
-
11
- jpa:
12
- hibernate:
13
- ddl-auto: update
14
- show-sql: true
15
- properties:
16
- hibernate:
17
- format_sql: true
18
- dialect: org.hibernate.dialect.PostgreSQLDialect
4
+ datasource:
5
+ url: jdbc:postgresql://localhost:5432/{{projectNameSnakeCase}}
6
+ username: postgres
7
+ password: postgres
8
+ driver-class-name: org.postgresql.Driver
9
+ jpa:
10
+ hibernate:
11
+ ddl-auto: update
12
+ show-sql: true
13
+ properties:
14
+ hibernate:
15
+ format_sql: true
16
+ dialect: org.hibernate.dialect.PostgreSQLDialect
19
17
 
20
- # H2 Console (for development)
21
- h2:
22
- console:
23
- enabled: true
24
- path: /h2-console
18
+ server:
19
+ port: 8080
25
20
 
26
- server:
27
- port: 8080
21
+ logging:
22
+ level:
23
+ root: INFO
24
+ {{packageName}}: DEBUG
28
25
 
29
- logging:
30
- level:
31
- root: INFO
32
- {{packageName}}: DEBUG
26
+ jwt:
27
+ secret: ${JWT_SECRET:{{jwtSecretKey}}}
28
+ expiration: 604800000
29
+
30
+ stripe:
31
+ secret-key: ${STRIPE_SECRET_KEY:sk_test_dummy}
32
+ webhook-secret: ${STRIPE_WEBHOOK_SECRET:whsec_dummy}
33
+
34
+ frontend:
35
+ url: ${FRONTEND_URL:http://localhost:3000}
@@ -0,0 +1,42 @@
1
+ # Dependencies
2
+ node_modules/
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Build outputs
7
+ dist/
8
+ build/
9
+
10
+ # Environment variables
11
+ .env
12
+ .env.local
13
+ .env.development.local
14
+ .env.test.local
15
+ .env.production.local
16
+ !.env.example
17
+
18
+ # Logs
19
+ logs/
20
+ *.log
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
24
+ pnpm-debug.log*
25
+ lerna-debug.log*
26
+
27
+ # Coverage
28
+ coverage/
29
+ .nyc_output
30
+
31
+ # TypeScript
32
+ *.tsbuildinfo
33
+
34
+ # OS
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Editor
39
+ .vscode/
40
+ .idea/
41
+ *.swp
42
+ *.swo
@@ -9,7 +9,7 @@ WORKDIR /app
9
9
  COPY package*.json ./
10
10
 
11
11
  # Install dependencies
12
- RUN npm ci --only=production
12
+ RUN npm ci
13
13
 
14
14
  # Copy source code
15
15
  COPY . .
@@ -26,6 +26,9 @@ WORKDIR /app
26
26
  RUN addgroup -g 1001 -S nodejs && \
27
27
  adduser -S nodejs -u 1001
28
28
 
29
+ # Add openssl
30
+ RUN apk add --no-cache openssl
31
+
29
32
  # Copy built application
30
33
  COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
31
34
  COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
@@ -39,7 +42,7 @@ EXPOSE 3000
39
42
 
40
43
  # Health check
41
44
  HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
42
- CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
45
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
43
46
 
44
47
  # Start application
45
- CMD ["node", "dist/index.js"]
48
+ CMD node dist/main.js
@@ -1,16 +1,6 @@
1
1
  version: '3.8'
2
2
 
3
3
  services:
4
- app:
5
- build: .
6
- container_name: {{kebabCase projectName}}-app
7
- ports:
8
- - "3000:3000"
9
- environment:
10
- - DATABASE_URL=postgresql://postgres:postgres@db:5432/{{snakeCase projectName}}
11
- depends_on:
12
- - db
13
-
14
4
  db:
15
5
  image: postgres:15-alpine
16
6
  container_name: {{kebabCase projectName}}-db
@@ -19,7 +9,7 @@ services:
19
9
  POSTGRES_PASSWORD: postgres
20
10
  POSTGRES_DB: {{snakeCase projectName}}
21
11
  ports:
22
- - "5432:5432"
12
+ - "5434:5432"
23
13
  volumes:
24
14
  - postgres_data:/var/lib/postgresql/data
25
15
  restart: unless-stopped
@@ -3,6 +3,7 @@ import { ConfigModule } from '@nestjs/config';
3
3
  import { AuthModule } from './auth.module';
4
4
  import { PaymentModule } from './payment.module';
5
5
  import { PrismaService } from './infrastructure/database/prisma.service';
6
+ import { HealthController } from './infrastructure/http/health.controller';
6
7
 
7
8
  @Module({
8
9
  imports: [
@@ -12,7 +13,7 @@ import { PrismaService } from './infrastructure/database/prisma.service';
12
13
  AuthModule,
13
14
  PaymentModule,
14
15
  ],
15
- controllers: [],
16
+ controllers: [HealthController],
16
17
  providers: [PrismaService],
17
18
  exports: [PrismaService],
18
19
  })
@@ -6,85 +6,85 @@ import { StripeProvider } from '../infrastructure/stripe.provider';
6
6
 
7
7
  @Injectable()
8
8
  export class PaymentService {
9
- constructor(
10
- private readonly userRepository: UserRepository,
11
- private readonly stripeProvider: StripeProvider,
12
- private readonly configService: ConfigService,
13
- ) {}
9
+ constructor(
10
+ private readonly userRepository: UserRepository,
11
+ private readonly stripeProvider: StripeProvider,
12
+ private readonly configService: ConfigService,
13
+ ) {}
14
14
 
15
- async createCheckoutSession(userId: string, priceId: string) {
16
- const user = await this.userRepository.findById(userId);
17
- if (!user) throw new NotFoundException('User not found');
15
+ async createCheckoutSession(userId: string, priceId: string) {
16
+ const user = await this.userRepository.findById(userId);
17
+ if (!user) throw new NotFoundException('User not found');
18
18
 
19
- let customerId = user.stripeCustomerId;
19
+ let customerId = user.stripeCustomerId;
20
20
 
21
- if (!customerId) {
22
- const customer = await this.stripeProvider.createCustomer(user.email, userId);
23
- customerId = customer.id;
24
- user.stripeCustomerId = customerId;
25
- await this.userRepository.save(user);
26
- }
21
+ if (!customerId) {
22
+ const customer = await this.stripeProvider.createCustomer(user.email, userId);
23
+ customerId = customer.id;
24
+ user.stripeCustomerId = customerId;
25
+ await this.userRepository.save(user);
26
+ }
27
27
 
28
- return this.stripeProvider.createCheckoutSession(
29
- customerId,
30
- priceId,
31
- userId,
32
- `${this.configService.get('FRONTEND_URL')}/success?session_id={CHECKOUT_SESSION_ID}`,
33
- `${this.configService.get('FRONTEND_URL')}/cancel`,
34
- );
35
- }
28
+ return this.stripeProvider.createCheckoutSession(
29
+ customerId,
30
+ priceId,
31
+ userId,
32
+ `${this.configService.get('FRONTEND_URL')}/success?session_id={CHECKOUT_SESSION_ID}`,
33
+ `${this.configService.get('FRONTEND_URL')}/cancel`,
34
+ );
35
+ }
36
36
 
37
- async createPortalSession(userId: string) {
38
- const user = await this.userRepository.findById(userId);
39
- if (!user?.stripeCustomerId) throw new NotFoundException('No Stripe customer found for this user');
37
+ async createPortalSession(userId: string) {
38
+ const user = await this.userRepository.findById(userId);
39
+ if (!user?.stripeCustomerId) throw new NotFoundException('No Stripe customer found for this user');
40
40
 
41
- return this.stripeProvider.createPortalSession(
42
- user.stripeCustomerId,
43
- `${this.configService.get('FRONTEND_URL')}/dashboard`,
44
- );
45
- }
41
+ return this.stripeProvider.createPortalSession(
42
+ user.stripeCustomerId,
43
+ `${this.configService.get('FRONTEND_URL')}/dashboard`,
44
+ );
45
+ }
46
46
 
47
- async handleWebhook(payload: Buffer, signature: string) {
48
- let event: Stripe.Event;
49
- try {
50
- event = this.stripeProvider.constructEvent(payload, signature);
51
- } catch (err: any) {
52
- throw new Error(`Webhook signature verification failed: ${err.message}`);
53
- }
47
+ async handleWebhook(payload: Buffer, signature: string) {
48
+ let event: Stripe.Event;
49
+ try {
50
+ event = this.stripeProvider.constructEvent(payload, signature);
51
+ } catch (err: any) {
52
+ throw new Error(`Webhook signature verification failed: ${err.message}`);
53
+ }
54
54
 
55
- switch (event.type) {
56
- case 'checkout.session.completed': {
57
- const session = event.data.object as Stripe.Checkout.Session;
58
- const userId = session.client_reference_id;
59
- if (userId && session.customer) {
60
- const user = await this.userRepository.findById(userId);
61
- if (user) {
62
- user.stripeCustomerId = session.customer as string;
63
- await this.userRepository.save(user);
64
- }
65
- }
66
- console.log('Checkout completed for user:', userId);
67
- break;
68
- }
69
- case 'customer.subscription.updated': {
70
- const sub = event.data.object as Stripe.Subscription;
71
- console.log('Subscription updated:', sub.id, '| Status:', sub.status);
72
- break;
73
- }
74
- case 'customer.subscription.deleted': {
75
- const sub = event.data.object as Stripe.Subscription;
76
- console.log('Subscription deleted:', sub.id);
77
- break;
78
- }
79
- case 'invoice.payment_failed': {
80
- const invoice = event.data.object as Stripe.Invoice;
81
- console.log('Payment failed for invoice:', invoice.id);
82
- break;
83
- }
84
- default:
85
- console.log('Unhandled Stripe event:', event.type);
86
- }
55
+ switch (event.type) {
56
+ case 'checkout.session.completed': {
57
+ const session = event.data.object as Stripe.Checkout.Session;
58
+ const userId = session.client_reference_id;
59
+ if (userId && session.customer) {
60
+ const user = await this.userRepository.findById(userId);
61
+ if (user) {
62
+ user.stripeCustomerId = session.customer as string;
63
+ await this.userRepository.save(user);
64
+ }
65
+ }
66
+ console.log('Checkout completed for user:', userId);
67
+ break;
68
+ }
69
+ case 'customer.subscription.updated': {
70
+ const sub = event.data.object as Stripe.Subscription;
71
+ console.log('Subscription updated:', sub.id, '| Status:', sub.status);
72
+ break;
73
+ }
74
+ case 'customer.subscription.deleted': {
75
+ const sub = event.data.object as Stripe.Subscription;
76
+ console.log('Subscription deleted:', sub.id);
77
+ break;
78
+ }
79
+ case 'invoice.payment_failed': {
80
+ const invoice = event.data.object as Stripe.Invoice;
81
+ console.log('Payment failed for invoice:', invoice.id);
82
+ break;
83
+ }
84
+ default:
85
+ console.log('Unhandled Stripe event:', event.type);
86
+ }
87
87
 
88
- return { received: true };
89
- }
88
+ return { received: true };
90
89
  }
90
+ }
@@ -4,9 +4,9 @@ export class User {
4
4
  public readonly email: string,
5
5
  public readonly name: string,
6
6
  public readonly password: string,
7
- public readonly stripeCustomerId?: string,
7
+ public stripeCustomerId?: string,
8
8
  public readonly createdAt: Date = new Date(),
9
9
  ) {
10
10
  if (!email.includes('@')) throw new Error('Invalid email');
11
11
  }
12
- }
12
+ }
@@ -1,6 +1,6 @@
1
1
  import { User } from '../entities/user.entity';
2
2
 
3
3
  export abstract class IUserRepository {
4
- abstract findByEmail(email: string): Promise<User | null>;
4
+ abstract findByEmail(email: string): Promise<User | null>;
5
5
  abstract save(user: User): Promise<User>;
6
- }
6
+ }
@@ -5,28 +5,28 @@ import { PrismaService } from '../prisma.service';
5
5
 
6
6
  @Injectable()
7
7
  export class PrismaUserRepository implements IUserRepository {
8
- constructor(private prisma: PrismaService) {}
8
+ constructor(private prisma: PrismaService) {}
9
9
 
10
- async findByEmail(email: string): Promise<User | null> {
11
- const user = await this.prisma.user.findUnique({ where: { email } });
12
- if (!user) return null;
13
- return new User(user.id, user.name, user.email, user.password);
10
+ async findByEmail(email: string): Promise<User | null> {
11
+ const user = await this.prisma.user.findUnique({ where: { email } });
12
+ if (!user) return null;
13
+ return new User(user.id, user.name, user.email, user.password);
14
14
  }
15
15
 
16
16
  async save(user: User): Promise<User> {
17
17
  const savedUser = await this.prisma.user.upsert({
18
- where: { email: user.email },
19
- update: {
20
- name: user.name,
21
- password: user.password,
22
- },
23
- create: {
24
- id: user.id,
25
- name: user.name,
26
- email: user.email,
27
- password: user.password,
28
- },
18
+ where: { email: user.email },
19
+ update: {
20
+ name: user.name,
21
+ password: user.password,
22
+ },
23
+ create: {
24
+ id: user.id,
25
+ name: user.name,
26
+ email: user.email,
27
+ password: user.password,
28
+ },
29
29
  });
30
30
  return new User(savedUser.id, savedUser.name, savedUser.email, savedUser.password);
31
- }
32
- }
31
+ }
32
+ }