kybernus 2.1.1 → 2.2.1

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 (93) hide show
  1. package/package.json +2 -2
  2. package/templates/java-spring/clean/infra/main.tf.hbs +42 -18
  3. package/templates/java-spring/clean/infra/modules/ecs/main.tf.hbs +217 -6
  4. package/templates/java-spring/clean/infra/modules/rds/main.tf.hbs +15 -15
  5. package/templates/java-spring/clean/infra/modules/vpc/main.tf.hbs +170 -30
  6. package/templates/java-spring/hexagonal/infra/main.tf.hbs +42 -18
  7. package/templates/java-spring/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
  8. package/templates/java-spring/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
  9. package/templates/java-spring/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
  10. package/templates/java-spring/mvc/infra/main.tf.hbs +42 -18
  11. package/templates/java-spring/mvc/infra/modules/ecs/main.tf.hbs +217 -6
  12. package/templates/java-spring/mvc/infra/modules/rds/main.tf.hbs +15 -15
  13. package/templates/java-spring/mvc/infra/modules/vpc/main.tf.hbs +170 -30
  14. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/AuthController.java.hbs +38 -42
  15. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/ItemController.java.hbs +42 -0
  16. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/PaymentsController.java.hbs +65 -22
  17. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/model/Item.java.hbs +38 -0
  18. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/model/User.java.hbs +41 -0
  19. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/repository/ItemRepository.java.hbs +9 -0
  20. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/repository/UserRepository.java.hbs +13 -0
  21. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/AuthService.java.hbs +62 -0
  22. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/StripeService.java.hbs +18 -18
  23. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/{{projectNamePascalCase}}Application.java.hbs +2 -0
  24. package/templates/nestjs/clean/infra/main.tf.hbs +42 -18
  25. package/templates/nestjs/clean/infra/modules/ecs/main.tf.hbs +217 -6
  26. package/templates/nestjs/clean/infra/modules/rds/main.tf.hbs +15 -15
  27. package/templates/nestjs/clean/infra/modules/vpc/main.tf.hbs +170 -30
  28. package/templates/nestjs/hexagonal/infra/main.tf.hbs +42 -18
  29. package/templates/nestjs/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
  30. package/templates/nestjs/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
  31. package/templates/nestjs/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
  32. package/templates/nestjs/mvc/infra/main.tf.hbs +42 -18
  33. package/templates/nestjs/mvc/infra/modules/ecs/main.tf.hbs +217 -6
  34. package/templates/nestjs/mvc/infra/modules/rds/main.tf.hbs +15 -15
  35. package/templates/nestjs/mvc/infra/modules/vpc/main.tf.hbs +170 -30
  36. package/templates/nestjs/mvc/package.json.hbs +6 -2
  37. package/templates/nestjs/mvc/prisma/schema.prisma.hbs +31 -0
  38. package/templates/nestjs/mvc/src/app.module.ts.hbs +3 -1
  39. package/templates/nestjs/mvc/src/auth/auth.service.ts.hbs +34 -31
  40. package/templates/nestjs/mvc/src/payments/payments.service.ts.hbs +26 -6
  41. package/templates/nestjs/mvc/src/prisma/prisma.module.ts.hbs +9 -0
  42. package/templates/nestjs/mvc/src/prisma/prisma.service.ts.hbs +15 -0
  43. package/templates/nestjs/mvc/src/services/items.service.ts.hbs +33 -20
  44. package/templates/nextjs/mvc/infra/main.tf.hbs +42 -18
  45. package/templates/nextjs/mvc/infra/modules/ecs/main.tf.hbs +217 -6
  46. package/templates/nextjs/mvc/infra/modules/rds/main.tf.hbs +15 -15
  47. package/templates/nextjs/mvc/infra/modules/vpc/main.tf.hbs +170 -30
  48. package/templates/nextjs/mvc/package.json.hbs +1 -0
  49. package/templates/nextjs/mvc/prisma/schema.prisma.hbs +60 -6
  50. package/templates/nextjs/mvc/src/app/api/webhook/route.ts.hbs +23 -18
  51. package/templates/nodejs-express/clean/infra/main.tf.hbs +42 -18
  52. package/templates/nodejs-express/clean/infra/modules/ecs/main.tf.hbs +217 -6
  53. package/templates/nodejs-express/clean/infra/modules/rds/main.tf.hbs +15 -15
  54. package/templates/nodejs-express/clean/infra/modules/vpc/main.tf.hbs +170 -30
  55. package/templates/nodejs-express/hexagonal/infra/main.tf.hbs +42 -18
  56. package/templates/nodejs-express/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
  57. package/templates/nodejs-express/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
  58. package/templates/nodejs-express/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
  59. package/templates/nodejs-express/mvc/infra/main.tf.hbs +42 -18
  60. package/templates/nodejs-express/mvc/infra/modules/ecs/main.tf.hbs +217 -6
  61. package/templates/nodejs-express/mvc/infra/modules/rds/main.tf.hbs +15 -15
  62. package/templates/nodejs-express/mvc/infra/modules/vpc/main.tf.hbs +170 -30
  63. package/templates/nodejs-express/mvc/package.json.hbs +8 -4
  64. package/templates/nodejs-express/mvc/prisma/schema.prisma.hbs +31 -0
  65. package/templates/nodejs-express/mvc/src/config/database.ts.hbs +2 -9
  66. package/templates/nodejs-express/mvc/src/controllers/auth.controller.ts.hbs +40 -58
  67. package/templates/nodejs-express/mvc/src/controllers/items.controller.ts.hbs +29 -0
  68. package/templates/nodejs-express/mvc/src/models/README.md.hbs +10 -0
  69. package/templates/nodejs-express/mvc/src/prisma/client.ts.hbs +3 -0
  70. package/templates/nodejs-express/mvc/src/services/auth.service.ts.hbs +71 -0
  71. package/templates/nodejs-express/mvc/src/services/stripe.service.ts.hbs +35 -25
  72. package/templates/python-fastapi/clean/infra/main.tf.hbs +42 -18
  73. package/templates/python-fastapi/clean/infra/modules/ecs/main.tf.hbs +217 -6
  74. package/templates/python-fastapi/clean/infra/modules/rds/main.tf.hbs +15 -15
  75. package/templates/python-fastapi/clean/infra/modules/vpc/main.tf.hbs +170 -30
  76. package/templates/python-fastapi/hexagonal/infra/main.tf.hbs +42 -18
  77. package/templates/python-fastapi/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
  78. package/templates/python-fastapi/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
  79. package/templates/python-fastapi/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
  80. package/templates/python-fastapi/mvc/app/controllers/auth.py.hbs +25 -16
  81. package/templates/python-fastapi/mvc/app/controllers/items.py.hbs +9 -7
  82. package/templates/python-fastapi/mvc/app/controllers/payments.py.hbs +42 -15
  83. package/templates/python-fastapi/mvc/app/database.py.hbs +17 -0
  84. package/templates/python-fastapi/mvc/app/main.py.hbs +4 -0
  85. package/templates/python-fastapi/mvc/app/models/item.py.hbs +11 -8
  86. package/templates/python-fastapi/mvc/app/models/user.py.hbs +15 -0
  87. package/templates/python-fastapi/mvc/app/repositories/item_repository.py.hbs +15 -0
  88. package/templates/python-fastapi/mvc/app/repositories/user_repository.py.hbs +15 -0
  89. package/templates/python-fastapi/mvc/app/services/item_service.py.hbs +17 -19
  90. package/templates/python-fastapi/mvc/infra/main.tf.hbs +42 -18
  91. package/templates/python-fastapi/mvc/infra/modules/ecs/main.tf.hbs +217 -6
  92. package/templates/python-fastapi/mvc/infra/modules/rds/main.tf.hbs +15 -15
  93. package/templates/python-fastapi/mvc/infra/modules/vpc/main.tf.hbs +170 -30
@@ -10,81 +10,213 @@ variable "environment" {
10
10
 
11
11
  # VPC
12
12
  resource "aws_vpc" "main" {
13
- cidr_block = "10.0.0.0/16"
13
+ cidr_block = "10.0.0.0/16"
14
14
  enable_dns_hostnames = true
15
- enable_dns_support = true
15
+ enable_dns_support = true
16
16
 
17
17
  tags = {
18
- Name = "${var.app_name}-${var.environment}-vpc"
18
+ Name = "${var.app_name}-${var.environment}-vpc"
19
19
  Environment = var.environment
20
20
  }
21
21
  }
22
22
 
23
+ # Internet Gateway
24
+ resource "aws_internet_gateway" "main" {
25
+ vpc_id = aws_vpc.main.id
26
+
27
+ tags = {
28
+ Name = "${var.app_name}-${var.environment}-igw"
29
+ Environment = var.environment
30
+ }
31
+ }
32
+
33
+ # Data source for AZs
34
+ data "aws_availability_zones" "available" {
35
+ state = "available"
36
+ }
37
+
23
38
  # Public Subnets
24
39
  resource "aws_subnet" "public" {
25
- count = 2
40
+ count = 2
41
+ vpc_id = aws_vpc.main.id
42
+ cidr_block = "10.0.${count.index + 1}.0/24"
43
+ availability_zone = data.aws_availability_zones.available.names[count.index]
44
+ map_public_ip_on_launch = true
45
+
46
+ tags = {
47
+ Name = "${var.app_name}-${var.environment}-public-${count.index + 1}"
48
+ Environment = var.environment
49
+ }
50
+ }
51
+
52
+ # Route Table for Public Subnets
53
+ resource "aws_route_table" "public" {
26
54
  vpc_id = aws_vpc.main.id
27
- cidr_block = "10.0.${count.index + 1}.0/24"
28
- availability_zone = data.aws_availability_zones.available.names[count.index]
29
55
 
30
- map_public_ip_on_launch = true
56
+ route {
57
+ cidr_block = "0.0.0.0/0"
58
+ gateway_id = aws_internet_gateway.main.id
59
+ }
60
+
61
+ tags = {
62
+ Name = "${var.app_name}-${var.environment}-public-rt"
63
+ Environment = var.environment
64
+ }
65
+ }
66
+
67
+ # Association for Public Subnets
68
+ resource "aws_route_table_association" "public" {
69
+ count = 2
70
+ subnet_id = aws_subnet.public[count.index].id
71
+ route_table_id = aws_route_table.public.id
72
+ }
73
+
74
+ # Elastic IP for NAT Gateway
75
+ resource "aws_eip" "nat" {
76
+ count = 1
77
+ domain = "vpc"
31
78
 
32
79
  tags = {
33
- Name = "${var.app_name}-${var.environment}-public-${count.index + 1}"
80
+ Name = "${var.app_name}-${var.environment}-nat-eip"
81
+ Environment = var.environment
82
+ }
83
+ }
84
+
85
+ # NAT Gateway (single NAT for cost savings, can change to 1 per AZ for production if needed)
86
+ resource "aws_nat_gateway" "main" {
87
+ count = 1
88
+ allocation_id = aws_eip.nat[0].id
89
+ subnet_id = aws_subnet.public[0].id
90
+
91
+ depends_on = [aws_internet_gateway.main]
92
+
93
+ tags = {
94
+ Name = "${var.app_name}-${var.environment}-nat"
34
95
  Environment = var.environment
35
96
  }
36
97
  }
37
98
 
38
99
  # Private Subnets
39
100
  resource "aws_subnet" "private" {
40
- count = 2
41
- vpc_id = aws_vpc.main.id
42
- cidr_block = "10.0.${count.index + 10}.0/24"
101
+ count = 2
102
+ vpc_id = aws_vpc.main.id
103
+ cidr_block = "10.0.${count.index + 10}.0/24"
43
104
  availability_zone = data.aws_availability_zones.available.names[count.index]
44
105
 
45
106
  tags = {
46
- Name = "${var.app_name}-${var.environment}-private-${count.index + 1}"
107
+ Name = "${var.app_name}-${var.environment}-private-${count.index + 1}"
47
108
  Environment = var.environment
48
109
  }
49
110
  }
50
111
 
51
- # Internet Gateway
52
- resource "aws_internet_gateway" "main" {
112
+ # Route Table for Private Subnets
113
+ resource "aws_route_table" "private" {
53
114
  vpc_id = aws_vpc.main.id
54
115
 
116
+ route {
117
+ cidr_block = "0.0.0.0/0"
118
+ nat_gateway_id = aws_nat_gateway.main[0].id
119
+ }
120
+
55
121
  tags = {
56
- Name = "${var.app_name}-${var.environment}-igw"
122
+ Name = "${var.app_name}-${var.environment}-private-rt"
57
123
  Environment = var.environment
58
124
  }
59
125
  }
60
126
 
61
- # Data source for AZs
62
- data "aws_availability_zones" "available" {
63
- state = "available"
127
+ # Association for Private Subnets
128
+ resource "aws_route_table_association" "private" {
129
+ count = 2
130
+ subnet_id = aws_subnet.private[count.index].id
131
+ route_table_id = aws_route_table.private.id
132
+ }
133
+
134
+ # Security Group for Load Balancer (ALB)
135
+ resource "aws_security_group" "alb" {
136
+ name = "${var.app_name}-${var.environment}-alb-sg"
137
+ description = "Security group for ALB"
138
+ vpc_id = aws_vpc.main.id
139
+
140
+ ingress {
141
+ from_port = 80
142
+ to_port = 80
143
+ protocol = "tcp"
144
+ cidr_blocks = ["0.0.0.0/0"]
145
+ description = "Allow HTTP from anywhere"
146
+ }
147
+
148
+ ingress {
149
+ from_port = 443
150
+ to_port = 443
151
+ protocol = "tcp"
152
+ cidr_blocks = ["0.0.0.0/0"]
153
+ description = "Allow HTTPS from anywhere"
154
+ }
155
+
156
+ egress {
157
+ from_port = 0
158
+ to_port = 0
159
+ protocol = "-1"
160
+ cidr_blocks = ["0.0.0.0/0"]
161
+ }
162
+
163
+ tags = {
164
+ Name = "${var.app_name}-${var.environment}-alb-sg"
165
+ Environment = var.environment
166
+ }
64
167
  }
65
168
 
66
- # Security Group for DB
169
+ # Security Group for ECS Tasks
170
+ resource "aws_security_group" "ecs_tasks" {
171
+ name = "${var.app_name}-${var.environment}-ecs-tasks-sg"
172
+ description = "Security group for ECS tasks"
173
+ vpc_id = aws_vpc.main.id
174
+
175
+ ingress {
176
+ from_port = 0
177
+ to_port = 0
178
+ protocol = "-1"
179
+ security_groups = [aws_security_group.alb.id]
180
+ description = "Allow all traffic from ALB"
181
+ }
182
+
183
+ egress {
184
+ from_port = 0
185
+ to_port = 0
186
+ protocol = "-1"
187
+ cidr_blocks = ["0.0.0.0/0"]
188
+ description = "Allow all outbound traffic"
189
+ }
190
+
191
+ tags = {
192
+ Name = "${var.app_name}-${var.environment}-ecs-tasks-sg"
193
+ Environment = var.environment
194
+ }
195
+ }
196
+
197
+ # Security Group for Database (RDS)
67
198
  resource "aws_security_group" "db" {
68
- name = "${var.app_name}-${var.environment}-db-sg"
199
+ name = "${var.app_name}-${var.environment}-db-sg"
69
200
  description = "Security group for database"
70
- vpc_id = aws_vpc.main.id
201
+ vpc_id = aws_vpc.main.id
71
202
 
72
203
  ingress {
73
- from_port = 5432
74
- to_port = 5432
75
- protocol = "tcp"
76
- cidr_blocks = ["10.0.0.0/16"]
204
+ from_port = 5432
205
+ to_port = 5432
206
+ protocol = "tcp"
207
+ security_groups = [aws_security_group.ecs_tasks.id]
208
+ description = "Allow PostgreSQL access from ECS tasks"
77
209
  }
78
210
 
79
211
  egress {
80
- from_port = 0
81
- to_port = 0
82
- protocol = "-1"
212
+ from_port = 0
213
+ to_port = 0
214
+ protocol = "-1"
83
215
  cidr_blocks = ["0.0.0.0/0"]
84
216
  }
85
217
 
86
218
  tags = {
87
- Name = "${var.app_name}-${var.environment}-db-sg"
219
+ Name = "${var.app_name}-${var.environment}-db-sg"
88
220
  Environment = var.environment
89
221
  }
90
222
  }
@@ -104,4 +236,12 @@ output "private_subnet_ids" {
104
236
 
105
237
  output "db_security_group_id" {
106
238
  value = aws_security_group.db.id
107
- }
239
+ }
240
+
241
+ output "alb_security_group_id" {
242
+ value = aws_security_group.alb.id
243
+ }
244
+
245
+ output "ecs_tasks_security_group_id" {
246
+ value = aws_security_group.ecs_tasks.id
247
+ }
@@ -2,68 +2,64 @@ package {{packageName}}.controller;
2
2
 
3
3
  import {{packageName}}.dto.AuthRequest;
4
4
  import {{packageName}}.dto.AuthResponse;
5
- import {{packageName}}.security.JwtTokenProvider;
5
+ import {{packageName}}.model.User;
6
+ import {{packageName}}.service.AuthService;
6
7
  import org.springframework.http.ResponseEntity;
7
- import org.springframework.security.crypto.password.PasswordEncoder;
8
+ import org.springframework.http.HttpStatus;
8
9
  import org.springframework.web.bind.annotation.*;
10
+ import org.springframework.security.core.context.SecurityContextHolder;
11
+ import org.springframework.security.core.Authentication;
9
12
 
10
- import java.util.HashMap;
11
13
  import java.util.Map;
12
- import java.util.UUID;
13
14
 
14
15
  @RestController
15
16
  @RequestMapping("/api/auth")
16
17
  public class AuthController {
17
18
 
18
- private final JwtTokenProvider tokenProvider;
19
- private final PasswordEncoder passwordEncoder;
19
+ private final AuthService authService;
20
20
 
21
- // In-memory user store (replace with repository in production)
22
- private final Map<String, Map<String, String>> users = new HashMap<>();
23
-
24
- public AuthController(JwtTokenProvider tokenProvider, PasswordEncoder passwordEncoder) {
25
- this.tokenProvider = tokenProvider;
26
- this.passwordEncoder = passwordEncoder;
21
+ public AuthController(AuthService authService) {
22
+ this.authService = authService;
27
23
  }
28
24
 
29
25
  @PostMapping("/register")
30
- public ResponseEntity
31
- <?> register(@RequestBody AuthRequest request) {
32
- if (users.containsKey(request.email())) {
33
- return ResponseEntity.badRequest().body(Map.of("error", "User already exists"));
26
+ public ResponseEntity<?> register(@RequestBody AuthRequest request) {
27
+ try {
28
+ AuthResponse response = authService.register(request);
29
+ return ResponseEntity.ok(response);
30
+ } catch (IllegalArgumentException e) {
31
+ return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
34
32
  }
35
-
36
- String userId = UUID.randomUUID().toString();
37
- String hashedPassword = passwordEncoder.encode(request.password());
38
-
39
- users.put(request.email(), Map.of(
40
- "id", userId,
41
- "email", request.email(),
42
- "password", hashedPassword
43
- ));
44
-
45
- String token = tokenProvider.generateToken(userId, request.email());
46
-
47
- return ResponseEntity.ok(new AuthResponse(token, userId, request.email()));
48
33
  }
49
34
 
50
35
  @PostMapping("/login")
51
36
  public ResponseEntity<?> login(@RequestBody AuthRequest request) {
52
- Map<String, String> user = users.get(request.email());
53
-
54
- if (user == null || !passwordEncoder.matches(request.password(), user.get("password"))) {
55
- return ResponseEntity.status(401).body(Map.of("error", "Invalid credentials"));
37
+ try {
38
+ AuthResponse response = authService.login(request);
39
+ return ResponseEntity.ok(response);
40
+ } catch (IllegalArgumentException e) {
41
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", e.getMessage()));
56
42
  }
57
-
58
- String token = tokenProvider.generateToken(user.get("id"), user.get("email"));
59
-
60
- return ResponseEntity.ok(new AuthResponse(token, user.get("id"), user.get("email")));
61
43
  }
62
44
 
63
45
  @GetMapping("/me")
64
- public ResponseEntity
65
- <?> me(@RequestHeader("Authorization") String authHeader) {
66
- // User is already authenticated by JWT filter
67
- return ResponseEntity.ok(Map.of("message", "Authenticated"));
46
+ public ResponseEntity<?> me() {
47
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
48
+ if (authentication == null || !authentication.isAuthenticated()) {
49
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
50
+ }
51
+
52
+ try {
53
+ String userId = (String) authentication.getPrincipal(); // Assuming custom jwt filter sets principal to id
54
+ User user = authService.getMe(userId);
55
+ return ResponseEntity.ok(Map.of(
56
+ "id", user.getId(),
57
+ "email", user.getEmail(),
58
+ "name", user.getName()
59
+ ));
60
+ } catch (Exception e) {
61
+ // Keep original behavior if principal isn't id
62
+ return ResponseEntity.ok(Map.of("message", "Authenticated"));
63
+ }
68
64
  }
69
- }
65
+ }
@@ -0,0 +1,42 @@
1
+ package {{packageName}}.controller;
2
+
3
+ import {{packageName}}.model.Item;
4
+ import {{packageName}}.repository.ItemRepository;
5
+ import org.springframework.http.ResponseEntity;
6
+ import org.springframework.web.bind.annotation.*;
7
+
8
+ import java.util.List;
9
+
10
+ @RestController
11
+ @RequestMapping("/api/items")
12
+ public class ItemController {
13
+
14
+ private final ItemRepository itemRepository;
15
+
16
+ public ItemController(ItemRepository itemRepository) {
17
+ this.itemRepository = itemRepository;
18
+ }
19
+
20
+ @GetMapping
21
+ public List<Item> getAllItems() {
22
+ return itemRepository.findAll();
23
+ }
24
+
25
+ @PostMapping
26
+ public Item createItem(@RequestBody Item item) {
27
+ return itemRepository.save(item);
28
+ }
29
+
30
+ @GetMapping("/{id}")
31
+ public ResponseEntity<Item> getItem(@PathVariable String id) {
32
+ return itemRepository.findById(id)
33
+ .map(ResponseEntity::ok)
34
+ .orElse(ResponseEntity.notFound().build());
35
+ }
36
+
37
+ @DeleteMapping("/{id}")
38
+ public ResponseEntity<Void> deleteItem(@PathVariable String id) {
39
+ itemRepository.deleteById(id);
40
+ return ResponseEntity.ok().build();
41
+ }
42
+ }
@@ -1,49 +1,92 @@
1
1
  package {{packageName}}.controller;
2
2
 
3
+ import com.stripe.exception.SignatureVerificationException;
4
+ import com.stripe.model.Event;
5
+ import com.stripe.net.Webhook;
6
+ import {{packageName}}.model.User;
7
+ import {{packageName}}.repository.UserRepository;
3
8
  import {{packageName}}.service.StripeService;
4
- import com.stripe.exception.StripeException;
9
+ import org.slf4j.Logger;
10
+ import org.slf4j.LoggerFactory;
11
+ import org.springframework.beans.factory.annotation.Value;
12
+ import org.springframework.http.HttpStatus;
5
13
  import org.springframework.http.ResponseEntity;
6
14
  import org.springframework.web.bind.annotation.*;
7
15
 
8
16
  import java.util.Map;
17
+ import java.util.Optional;
9
18
 
10
19
  @RestController
11
20
  @RequestMapping("/api/payments")
12
21
  public class PaymentsController {
13
22
 
23
+ private static final Logger logger = LoggerFactory.getLogger(PaymentsController.class);
24
+
25
+ @Value("${stripe.webhook-secret}")
26
+ private String webhookSecret;
27
+
14
28
  private final StripeService stripeService;
29
+ private final UserRepository userRepository;
15
30
 
16
- public PaymentsController(StripeService stripeService) {
31
+ public PaymentsController(StripeService stripeService, UserRepository userRepository) {
17
32
  this.stripeService = stripeService;
33
+ this.userRepository = userRepository;
18
34
  }
19
35
 
20
36
  @PostMapping("/checkout")
21
- public ResponseEntity
22
- <?> createCheckout(@RequestBody Map<String, String> request) {
37
+ public ResponseEntity<?> createCheckout(@RequestBody Map<String, String> request) {
23
38
  try {
24
- var session = stripeService.createCheckoutSession(request.get("customerId"));
39
+ String customerId = request.get("customerId"); // Optional
40
+ com.stripe.model.checkout.Session session = stripeService.createCheckoutSession(customerId);
25
41
  return ResponseEntity.ok(Map.of("url", session.getUrl()));
26
- } catch (StripeException e) {
27
- return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
42
+ } catch (Exception e) {
43
+ logger.error("Error creating checkout session", e);
44
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
45
+ .body(Map.of("error", e.getMessage()));
28
46
  }
29
47
  }
30
48
 
31
- @PostMapping("/portal")
32
- public ResponseEntity<?> createPortal(@RequestBody Map<String, String> request) {
49
+ @PostMapping("/webhook")
50
+ public ResponseEntity<String> handleWebhook(
51
+ @RequestBody String payload,
52
+ @RequestHeader("Stripe-Signature") String sigHeader) {
53
+
54
+ Event event;
55
+
33
56
  try {
34
- var session = stripeService.createPortalSession(request.get("customerId"));
35
- return ResponseEntity.ok(Map.of("url", session.getUrl()));
36
- } catch (StripeException e) {
37
- return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
57
+ event = Webhook.constructEvent(payload, sigHeader, webhookSecret);
58
+ } catch (SignatureVerificationException e) {
59
+ logger.warn("Invalid signature", e);
60
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid signature");
61
+ } catch (Exception e) {
62
+ logger.error("Error parsing webhook", e);
63
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Error parsing webhook");
38
64
  }
39
- }
40
65
 
41
- @PostMapping("/webhook")
42
- public ResponseEntity
43
- <?> handleWebhook(@RequestBody String payload,
44
- @RequestHeader("Stripe-Signature") String sigHeader) {
45
- // TODO: Implement webhook verification and handling
46
- // Use Stripe.webhooks.constructEvent to verify signature
47
- return ResponseEntity.ok(Map.of("received", true));
66
+ switch (event.getType()) {
67
+ case "checkout.session.completed":
68
+ com.stripe.model.checkout.Session session = (com.stripe.model.checkout.Session) event.getDataObjectDeserializer().getObject().orElse(null);
69
+ if (session != null) {
70
+ logger.info("Checkout completed: {}", session.getId());
71
+ // Match to database user here using session properties
72
+ }
73
+ break;
74
+ case "customer.subscription.updated":
75
+ com.stripe.model.Subscription subscription = (com.stripe.model.Subscription) event.getDataObjectDeserializer().getObject().orElse(null);
76
+ if (subscription != null) {
77
+ logger.info("Subscription updated: {}", subscription.getId());
78
+ }
79
+ break;
80
+ case "customer.subscription.deleted":
81
+ com.stripe.model.Subscription deletedSub = (com.stripe.model.Subscription) event.getDataObjectDeserializer().getObject().orElse(null);
82
+ if (deletedSub != null) {
83
+ logger.info("Subscription deleted: {}", deletedSub.getId());
84
+ }
85
+ break;
86
+ default:
87
+ logger.info("Unhandled event type: {}", event.getType());
88
+ }
89
+
90
+ return ResponseEntity.ok("Received");
48
91
  }
49
- }
92
+ }
@@ -0,0 +1,38 @@
1
+ package {{packageName}}.model;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.*;
5
+ import org.springframework.data.annotation.CreatedDate;
6
+ import org.springframework.data.annotation.LastModifiedDate;
7
+ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
8
+
9
+ import java.time.LocalDateTime;
10
+
11
+ @Entity
12
+ @Table(name = "items")
13
+ @Data
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ @Builder
17
+ @EntityListeners(AuditingEntityListener.class)
18
+ public class Item {
19
+
20
+ @Id
21
+ @GeneratedValue(strategy = GenerationType.UUID)
22
+ private String id;
23
+
24
+ @Column(nullable = false)
25
+ private String name;
26
+
27
+ private String description;
28
+
29
+ private Double price;
30
+
31
+ @CreatedDate
32
+ @Column(name = "created_at", nullable = false, updatable = false)
33
+ private LocalDateTime createdAt;
34
+
35
+ @LastModifiedDate
36
+ @Column(name = "updated_at", nullable = false)
37
+ private LocalDateTime updatedAt;
38
+ }
@@ -0,0 +1,41 @@
1
+ package {{packageName}}.model;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.*;
5
+ import org.springframework.data.annotation.CreatedDate;
6
+ import org.springframework.data.annotation.LastModifiedDate;
7
+ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
8
+
9
+ import java.time.LocalDateTime;
10
+
11
+ @Entity
12
+ @Table(name = "users")
13
+ @Data
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ @Builder
17
+ @EntityListeners(AuditingEntityListener.class)
18
+ public class User {
19
+
20
+ @Id
21
+ @GeneratedValue(strategy = GenerationType.UUID)
22
+ private String id;
23
+
24
+ @Column(unique = true, nullable = false)
25
+ private String email;
26
+
27
+ private String name;
28
+
29
+ private String password;
30
+
31
+ @Column(name = "stripe_customer_id")
32
+ private String stripeCustomerId;
33
+
34
+ @CreatedDate
35
+ @Column(name = "created_at", nullable = false, updatable = false)
36
+ private LocalDateTime createdAt;
37
+
38
+ @LastModifiedDate
39
+ @Column(name = "updated_at", nullable = false)
40
+ private LocalDateTime updatedAt;
41
+ }
@@ -0,0 +1,9 @@
1
+ package {{packageName}}.repository;
2
+
3
+ import {{packageName}}.model.Item;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ @Repository
8
+ public interface ItemRepository extends JpaRepository<Item, String> {
9
+ }
@@ -0,0 +1,13 @@
1
+ package {{packageName}}.repository;
2
+
3
+ import {{packageName}}.model.User;
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 UserRepository extends JpaRepository<User, String> {
11
+ Optional<User> findByEmail(String email);
12
+ Optional<User> findByStripeCustomerId(String stripeCustomerId);
13
+ }