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.
- package/package.json +2 -2
- package/templates/java-spring/clean/infra/main.tf.hbs +42 -18
- package/templates/java-spring/clean/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/java-spring/clean/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/java-spring/clean/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/java-spring/hexagonal/infra/main.tf.hbs +42 -18
- package/templates/java-spring/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/java-spring/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/java-spring/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/java-spring/mvc/infra/main.tf.hbs +42 -18
- package/templates/java-spring/mvc/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/java-spring/mvc/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/java-spring/mvc/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/AuthController.java.hbs +38 -42
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/ItemController.java.hbs +42 -0
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/PaymentsController.java.hbs +65 -22
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/model/Item.java.hbs +38 -0
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/model/User.java.hbs +41 -0
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/repository/ItemRepository.java.hbs +9 -0
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/repository/UserRepository.java.hbs +13 -0
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/AuthService.java.hbs +62 -0
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/StripeService.java.hbs +18 -18
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/{{projectNamePascalCase}}Application.java.hbs +2 -0
- package/templates/nestjs/clean/infra/main.tf.hbs +42 -18
- package/templates/nestjs/clean/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nestjs/clean/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nestjs/clean/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nestjs/hexagonal/infra/main.tf.hbs +42 -18
- package/templates/nestjs/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nestjs/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nestjs/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nestjs/mvc/infra/main.tf.hbs +42 -18
- package/templates/nestjs/mvc/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nestjs/mvc/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nestjs/mvc/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nestjs/mvc/package.json.hbs +6 -2
- package/templates/nestjs/mvc/prisma/schema.prisma.hbs +31 -0
- package/templates/nestjs/mvc/src/app.module.ts.hbs +3 -1
- package/templates/nestjs/mvc/src/auth/auth.service.ts.hbs +34 -31
- package/templates/nestjs/mvc/src/payments/payments.service.ts.hbs +26 -6
- package/templates/nestjs/mvc/src/prisma/prisma.module.ts.hbs +9 -0
- package/templates/nestjs/mvc/src/prisma/prisma.service.ts.hbs +15 -0
- package/templates/nestjs/mvc/src/services/items.service.ts.hbs +33 -20
- package/templates/nextjs/mvc/infra/main.tf.hbs +42 -18
- package/templates/nextjs/mvc/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nextjs/mvc/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nextjs/mvc/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nextjs/mvc/package.json.hbs +1 -0
- package/templates/nextjs/mvc/prisma/schema.prisma.hbs +60 -6
- package/templates/nextjs/mvc/src/app/api/webhook/route.ts.hbs +23 -18
- package/templates/nodejs-express/clean/infra/main.tf.hbs +42 -18
- package/templates/nodejs-express/clean/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nodejs-express/clean/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nodejs-express/clean/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nodejs-express/hexagonal/infra/main.tf.hbs +42 -18
- package/templates/nodejs-express/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nodejs-express/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nodejs-express/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nodejs-express/mvc/infra/main.tf.hbs +42 -18
- package/templates/nodejs-express/mvc/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/nodejs-express/mvc/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/nodejs-express/mvc/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/nodejs-express/mvc/package.json.hbs +8 -4
- package/templates/nodejs-express/mvc/prisma/schema.prisma.hbs +31 -0
- package/templates/nodejs-express/mvc/src/config/database.ts.hbs +2 -9
- package/templates/nodejs-express/mvc/src/controllers/auth.controller.ts.hbs +40 -58
- package/templates/nodejs-express/mvc/src/controllers/items.controller.ts.hbs +29 -0
- package/templates/nodejs-express/mvc/src/models/README.md.hbs +10 -0
- package/templates/nodejs-express/mvc/src/prisma/client.ts.hbs +3 -0
- package/templates/nodejs-express/mvc/src/services/auth.service.ts.hbs +71 -0
- package/templates/nodejs-express/mvc/src/services/stripe.service.ts.hbs +35 -25
- package/templates/python-fastapi/clean/infra/main.tf.hbs +42 -18
- package/templates/python-fastapi/clean/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/python-fastapi/clean/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/python-fastapi/clean/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/python-fastapi/hexagonal/infra/main.tf.hbs +42 -18
- package/templates/python-fastapi/hexagonal/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/python-fastapi/hexagonal/infra/modules/rds/main.tf.hbs +15 -15
- package/templates/python-fastapi/hexagonal/infra/modules/vpc/main.tf.hbs +170 -30
- package/templates/python-fastapi/mvc/app/controllers/auth.py.hbs +25 -16
- package/templates/python-fastapi/mvc/app/controllers/items.py.hbs +9 -7
- package/templates/python-fastapi/mvc/app/controllers/payments.py.hbs +42 -15
- package/templates/python-fastapi/mvc/app/database.py.hbs +17 -0
- package/templates/python-fastapi/mvc/app/main.py.hbs +4 -0
- package/templates/python-fastapi/mvc/app/models/item.py.hbs +11 -8
- package/templates/python-fastapi/mvc/app/models/user.py.hbs +15 -0
- package/templates/python-fastapi/mvc/app/repositories/item_repository.py.hbs +15 -0
- package/templates/python-fastapi/mvc/app/repositories/user_repository.py.hbs +15 -0
- package/templates/python-fastapi/mvc/app/services/item_service.py.hbs +17 -19
- package/templates/python-fastapi/mvc/infra/main.tf.hbs +42 -18
- package/templates/python-fastapi/mvc/infra/modules/ecs/main.tf.hbs +217 -6
- package/templates/python-fastapi/mvc/infra/modules/rds/main.tf.hbs +15 -15
- 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
|
|
13
|
+
cidr_block = "10.0.0.0/16"
|
|
14
14
|
enable_dns_hostnames = true
|
|
15
|
-
enable_dns_support
|
|
15
|
+
enable_dns_support = true
|
|
16
16
|
|
|
17
17
|
tags = {
|
|
18
|
-
Name
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
41
|
-
vpc_id
|
|
42
|
-
cidr_block
|
|
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
|
|
107
|
+
Name = "${var.app_name}-${var.environment}-private-${count.index + 1}"
|
|
47
108
|
Environment = var.environment
|
|
48
109
|
}
|
|
49
110
|
}
|
|
50
111
|
|
|
51
|
-
#
|
|
52
|
-
resource "
|
|
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
|
|
122
|
+
Name = "${var.app_name}-${var.environment}-private-rt"
|
|
57
123
|
Environment = var.environment
|
|
58
124
|
}
|
|
59
125
|
}
|
|
60
126
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
|
199
|
+
name = "${var.app_name}-${var.environment}-db-sg"
|
|
69
200
|
description = "Security group for database"
|
|
70
|
-
vpc_id
|
|
201
|
+
vpc_id = aws_vpc.main.id
|
|
71
202
|
|
|
72
203
|
ingress {
|
|
73
|
-
from_port
|
|
74
|
-
to_port
|
|
75
|
-
protocol
|
|
76
|
-
|
|
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
|
|
81
|
-
to_port
|
|
82
|
-
protocol
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
33
|
-
return {"token": token, "user": {"id": user
|
|
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 =
|
|
38
|
-
|
|
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
|
|
42
|
-
return {"token": token, "user": {"id": user
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
4
|
+
from app.repositories.item_repository import ItemRepository
|
|
4
5
|
|
|
5
6
|
class ItemService:
|
|
6
7
|
def __init__(self):
|
|
7
|
-
self.
|
|
8
|
-
|
|
9
|
-
def list_items(self
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
def create_item(self, item_data: ItemCreate
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return self.
|
|
19
|
-
|
|
20
|
-
def
|
|
21
|
-
|
|
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)
|