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
+ }
@@ -1,11 +1,13 @@
1
1
  from fastapi import APIRouter, HTTPException, Depends
2
+ from sqlalchemy.orm import Session
2
3
  from pydantic import BaseModel, EmailStr
3
4
  from app.middleware.security import hash_password, verify_password, create_access_token, get_current_user
5
+ from app.database import get_db
6
+ from app.models.user import User
7
+ from app.repositories.user_repository import UserRepository
4
8
 
5
9
  router = APIRouter()
6
-
7
- # In-memory user store (replace with database)
8
- users_db: dict = {}
10
+ user_repo = UserRepository()
9
11
 
10
12
  class UserRegister(BaseModel):
11
13
  email: EmailStr
@@ -21,26 +23,33 @@ class AuthResponse(BaseModel):
21
23
  user: dict
22
24
 
23
25
  @router.post("/register", response_model=AuthResponse)
24
- async def register(data: UserRegister):
25
- if data.email in users_db:
26
+ async def register(data: UserRegister, db: Session = Depends(get_db)):
27
+ existing_user = user_repo.get_by_email(db, data.email)
28
+ if existing_user:
26
29
  raise HTTPException(status_code=400, detail="User already exists")
27
30
 
28
31
  hashed = hash_password(data.password)
29
- user = {"id": str(len(users_db) + 1), "email": data.email, "name": data.name, "password": hashed}
30
- users_db[data.email] = user
32
+ new_user = User(email=data.email, name=data.name, password=hashed)
33
+
34
+ user = user_repo.create(db, new_user)
31
35
 
32
- token = create_access_token({"sub": user["id"], "email": user["email"]})
33
- return {"token": token, "user": {"id": user["id"], "email": user["email"], "name": user["name"]}}
36
+ token = create_access_token({"sub": user.id, "email": user.email})
37
+ return {"token": token, "user": {"id": user.id, "email": user.email, "name": user.name}}
34
38
 
35
39
  @router.post("/login", response_model=AuthResponse)
36
- async def login(data: UserLogin):
37
- user = users_db.get(data.email)
38
- if not user or not verify_password(data.password, user["password"]):
40
+ async def login(data: UserLogin, db: Session = Depends(get_db)):
41
+ user = user_repo.get_by_email(db, data.email)
42
+
43
+ if not user or not verify_password(data.password, user.password):
39
44
  raise HTTPException(status_code=401, detail="Invalid credentials")
40
45
 
41
- token = create_access_token({"sub": user["id"], "email": user["email"]})
42
- return {"token": token, "user": {"id": user["id"], "email": user["email"], "name": user["name"]}}
46
+ token = create_access_token({"sub": user.id, "email": user.email})
47
+ return {"token": token, "user": {"id": user.id, "email": user.email, "name": user.name}}
43
48
 
44
49
  @router.get("/me")
45
- async def get_me(current_user: dict = Depends(get_current_user)):
46
- return {"user": current_user}
50
+ async def get_me(current_user: dict = Depends(get_current_user), db: Session = Depends(get_db)):
51
+ user = user_repo.get_by_id(db, current_user["id"])
52
+ if not user:
53
+ raise HTTPException(status_code=404, detail="User not found")
54
+
55
+ return {"user": {"id": user.id, "email": user.email, "name": user.name}}
@@ -1,21 +1,23 @@
1
- from fastapi import APIRouter, HTTPException
1
+ from fastapi import APIRouter, HTTPException, Depends
2
+ from sqlalchemy.orm import Session
2
3
  from app.services.item_service import ItemService
3
4
  from app.schemas.item import ItemCreate, ItemResponse
5
+ from app.database import get_db
4
6
 
5
7
  router = APIRouter()
6
8
  service = ItemService()
7
9
 
8
10
  @router.get("/", response_model=list[ItemResponse])
9
- async def list_items():
10
- return service.list_items()
11
+ async def list_items(db: Session = Depends(get_db)):
12
+ return service.list_items(db)
11
13
 
12
14
  @router.post("/", response_model=ItemResponse, status_code=201)
13
- async def create_item(item: ItemCreate):
14
- return service.create_item(item)
15
+ async def create_item(item: ItemCreate, db: Session = Depends(get_db)):
16
+ return service.create_item(item, db)
15
17
 
16
18
  @router.get("/{item_id}", response_model=ItemResponse)
17
- async def get_item(item_id: str):
18
- item = service.get_item(item_id)
19
+ async def get_item(item_id: str, db: Session = Depends(get_db)):
20
+ item = service.get_item(item_id, db)
19
21
  if not item:
20
22
  raise HTTPException(status_code=404, detail="Item not found")
21
23
  return item
@@ -1,32 +1,59 @@
1
- from fastapi import APIRouter, HTTPException
1
+ from fastapi import APIRouter, HTTPException, Request, Depends
2
2
  from pydantic import BaseModel
3
+ from sqlalchemy.orm import Session
4
+ import stripe
3
5
  import os
6
+ from app.database import get_db
4
7
 
5
8
  router = APIRouter()
6
9
 
10
+ stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
11
+ webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
12
+
7
13
  class CheckoutRequest(BaseModel):
8
14
  price_id: str
9
15
  customer_id: str | None = None
10
16
 
11
17
  @router.post("/checkout")
12
18
  async def create_checkout(data: CheckoutRequest):
13
- # Stripe integration would go here
14
- # For now, return mock data
15
- return {
16
- "checkout_url": f"https://checkout.stripe.com/session_{data.price_id}",
17
- "session_id": "cs_test_1234567890"
18
- }
19
+ try:
20
+ session = stripe.checkout.Session.create(
21
+ mode="subscription",
22
+ payment_method_types=["card"],
23
+ line_items=[{"price": data.price_id, "quantity": 1}],
24
+ customer=data.customer_id,
25
+ success_url=f"{os.getenv('FRONTEND_URL')}/success?session_id={{CHECKOUT_SESSION_ID}}",
26
+ cancel_url=f"{os.getenv('FRONTEND_URL')}/cancel",
27
+ )
28
+ return {"checkout_url": session.url, "session_id": session.id}
29
+ except Exception as e:
30
+ raise HTTPException(status_code=500, detail=str(e))
19
31
 
20
32
  @router.post("/webhook")
21
- async def stripe_webhook(payload: dict):
22
- # Handle Stripe webhook events
23
- event_type = payload.get("type")
33
+ async def stripe_webhook(request: Request, db: Session = Depends(get_db)):
34
+ payload = await request.body()
35
+ sig_header = request.headers.get("stripe-signature")
36
+
37
+ try:
38
+ event = stripe.Webhook.construct_event(
39
+ payload, sig_header, webhook_secret
40
+ )
41
+ except ValueError as e:
42
+ raise HTTPException(status_code=400, detail="Invalid payload")
43
+ except stripe.error.SignatureVerificationError as e:
44
+ raise HTTPException(status_code=400, detail="Invalid signature")
45
+
46
+ event_type = event.get("type")
24
47
 
25
48
  if event_type == "checkout.session.completed":
26
- # Handle successful checkout
27
- pass
49
+ session = event.get("data").get("object")
50
+ print(f"Checkout completed: {session.get('id')}")
51
+ # Update user in db
28
52
  elif event_type == "customer.subscription.updated":
29
- # Handle subscription update
30
- pass
31
-
53
+ subscription = event.get("data").get("object")
54
+ print(f"Subscription updated: {subscription.get('id')}")
55
+ elif event_type == "customer.subscription.deleted":
56
+ subscription = event.get("data").get("object")
57
+ print(f"Subscription deleted: {subscription.get('id')}")
58
+
32
59
  return {"received": True}
@@ -0,0 +1,17 @@
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.orm import sessionmaker, declarative_base
3
+ import os
4
+
5
+ DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/{{projectNameSnakeCase}}")
6
+
7
+ engine = create_engine(DATABASE_URL)
8
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
9
+
10
+ Base = declarative_base()
11
+
12
+ def get_db():
13
+ db = SessionLocal()
14
+ try:
15
+ yield db
16
+ finally:
17
+ db.close()
@@ -1,5 +1,9 @@
1
1
  from fastapi import FastAPI
2
2
  from app.controllers import health, auth, items, payments
3
+ from app.database import engine, Base
4
+
5
+ # Create tables (In production, use Alembic migrations instead)
6
+ Base.metadata.create_all(bind=engine)
3
7
 
4
8
  app = FastAPI(title="{{projectName}}")
5
9
 
@@ -1,11 +1,14 @@
1
- from dataclasses import dataclass, field
1
+ from sqlalchemy import Column, String, Float, DateTime
2
+ from app.database import Base
2
3
  from datetime import datetime
3
- from typing import Optional
4
4
  import uuid
5
5
 
6
- @dataclass
7
- class Item:
8
- id: str = field(default_factory=lambda: str(uuid.uuid4()))
9
- name: str = ""
10
- description: Optional[str] = None
11
- created_at: datetime = field(default_factory=datetime.now)
6
+ class Item(Base):
7
+ __tablename__ = "items"
8
+
9
+ id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
10
+ name = Column(String, nullable=False)
11
+ description = Column(String, nullable=True)
12
+ price = Column(Float, nullable=True)
13
+ created_at = Column(DateTime, default=datetime.utcnow)
14
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@@ -0,0 +1,15 @@
1
+ from sqlalchemy import Column, String, DateTime
2
+ from app.database import Base
3
+ from datetime import datetime
4
+ import uuid
5
+
6
+ class User(Base):
7
+ __tablename__ = "users"
8
+
9
+ id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
10
+ email = Column(String, unique=True, index=True, nullable=False)
11
+ name = Column(String, nullable=True)
12
+ password = Column(String, nullable=False)
13
+ stripe_customer_id = Column(String, nullable=True)
14
+ created_at = Column(DateTime, default=datetime.utcnow)
15
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@@ -0,0 +1,15 @@
1
+ from sqlalchemy.orm import Session
2
+ from app.models.item import Item
3
+
4
+ class ItemRepository:
5
+ def get_all(self, db: Session) -> list[Item]:
6
+ return db.query(Item).all()
7
+
8
+ def get_by_id(self, db: Session, item_id: str) -> Item | None:
9
+ return db.query(Item).filter(Item.id == item_id).first()
10
+
11
+ def create(self, db: Session, item: Item) -> Item:
12
+ db.add(item)
13
+ db.commit()
14
+ db.refresh(item)
15
+ return item
@@ -0,0 +1,15 @@
1
+ from sqlalchemy.orm import Session
2
+ from app.models.user import User
3
+
4
+ class UserRepository:
5
+ def get_by_email(self, db: Session, email: str) -> User | None:
6
+ return db.query(User).filter(User.email == email).first()
7
+
8
+ def get_by_id(self, db: Session, user_id: str) -> User | None:
9
+ return db.query(User).filter(User.id == user_id).first()
10
+
11
+ def create(self, db: Session, user: User) -> User:
12
+ db.add(user)
13
+ db.commit()
14
+ db.refresh(user)
15
+ return user
@@ -1,24 +1,22 @@
1
+ from sqlalchemy.orm import Session
1
2
  from app.models.item import Item
2
3
  from app.schemas.item import ItemCreate
3
- from typing import List, Optional
4
+ from app.repositories.item_repository import ItemRepository
4
5
 
5
6
  class ItemService:
6
7
  def __init__(self):
7
- self.items: dict[str, Item] = {}
8
-
9
- def list_items(self) -> List[Item]:
10
- return list(self.items.values())
11
-
12
- def create_item(self, item_data: ItemCreate) -> Item:
13
- item = Item(name=item_data.name, description=item_data.description)
14
- self.items[item.id] = item
15
- return item
16
-
17
- def get_item(self, item_id: str) -> Optional[Item]:
18
- return self.items.get(item_id)
19
-
20
- def delete_item(self, item_id: str) -> bool:
21
- if item_id in self.items:
22
- del self.items[item_id]
23
- return True
24
- return False
8
+ self.item_repo = ItemRepository()
9
+
10
+ def list_items(self, db: Session):
11
+ return self.item_repo.get_all(db)
12
+
13
+ def create_item(self, item_data: ItemCreate, db: Session):
14
+ db_item = Item(
15
+ name=item_data.name,
16
+ description=item_data.description,
17
+ price=item_data.price
18
+ )
19
+ return self.item_repo.create(db, db_item)
20
+
21
+ def get_item(self, item_id: str, db: Session):
22
+ return self.item_repo.get_by_id(db, item_id)