@intentsolutionsio/fullstack-starter-pack 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +31 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/agents/api-builder.md +610 -0
- package/agents/backend-architect.md +574 -0
- package/agents/database-designer.md +509 -0
- package/agents/deployment-specialist.md +603 -0
- package/agents/react-specialist.md +668 -0
- package/agents/ui-ux-expert.md +652 -0
- package/commands/auth-setup.md +422 -0
- package/commands/component-generator.md +343 -0
- package/commands/css-utility-generator.md +621 -0
- package/commands/env-config-setup.md +338 -0
- package/commands/express-api-scaffold.md +659 -0
- package/commands/fastapi-scaffold.md +674 -0
- package/commands/prisma-schema-gen.md +582 -0
- package/commands/project-scaffold.md +355 -0
- package/commands/sql-query-builder.md +461 -0
- package/package.json +52 -0
- package/skills/skill-adapter/assets/README.md +8 -0
- package/skills/skill-adapter/assets/config-template.json +32 -0
- package/skills/skill-adapter/assets/example_env_config.txt +100 -0
- package/skills/skill-adapter/assets/skill-schema.json +28 -0
- package/skills/skill-adapter/assets/test-data.json +27 -0
- package/skills/skill-adapter/references/README.md +4 -0
- package/skills/skill-adapter/references/best-practices.md +69 -0
- package/skills/skill-adapter/references/examples.md +73 -0
- package/skills/skill-adapter/scripts/README.md +7 -0
- package/skills/skill-adapter/scripts/helper-template.sh +42 -0
- package/skills/skill-adapter/scripts/validation.sh +32 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fastapi-scaffold
|
|
3
|
+
description: >
|
|
4
|
+
Generate production-ready FastAPI REST API with async and authentication
|
|
5
|
+
shortcut: fas
|
|
6
|
+
category: backend
|
|
7
|
+
difficulty: intermediate
|
|
8
|
+
estimated_time: 5-10 minutes
|
|
9
|
+
---
|
|
10
|
+
# FastAPI Scaffold
|
|
11
|
+
|
|
12
|
+
Generates a complete FastAPI REST API boilerplate with async support, authentication, database integration, and testing setup.
|
|
13
|
+
|
|
14
|
+
## What This Command Does
|
|
15
|
+
|
|
16
|
+
**Generated Project:**
|
|
17
|
+
- FastAPI with Python 3.10+
|
|
18
|
+
- Async/await throughout
|
|
19
|
+
- JWT authentication
|
|
20
|
+
- Database integration (SQLAlchemy async)
|
|
21
|
+
- Pydantic models & validation
|
|
22
|
+
- Automatic OpenAPI docs
|
|
23
|
+
- Testing setup (Pytest + httpx)
|
|
24
|
+
- Docker configuration
|
|
25
|
+
- Example CRUD endpoints
|
|
26
|
+
|
|
27
|
+
**Output:** Complete API project ready for development
|
|
28
|
+
|
|
29
|
+
**Time:** 5-10 minutes
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Generate full FastAPI API
|
|
37
|
+
/fastapi-scaffold "Task Management API"
|
|
38
|
+
|
|
39
|
+
# Shortcut
|
|
40
|
+
/fas "E-commerce API"
|
|
41
|
+
|
|
42
|
+
# With specific database
|
|
43
|
+
/fas "Blog API" --database postgresql
|
|
44
|
+
|
|
45
|
+
# With authentication type
|
|
46
|
+
/fas "Social API" --auth jwt --database postgresql
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Example Output
|
|
52
|
+
|
|
53
|
+
**Input:**
|
|
54
|
+
```
|
|
55
|
+
/fas "Task Management API" --database postgresql
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Generated Project Structure:**
|
|
59
|
+
```
|
|
60
|
+
task-api/
|
|
61
|
+
├── app/
|
|
62
|
+
│ ├── api/
|
|
63
|
+
│ │ ├── deps.py # Dependencies
|
|
64
|
+
│ │ └── v1/
|
|
65
|
+
│ │ ├── __init__.py
|
|
66
|
+
│ │ ├── auth.py # Auth endpoints
|
|
67
|
+
│ │ └── tasks.py # Task endpoints
|
|
68
|
+
│ ├── core/
|
|
69
|
+
│ │ ├── config.py # Settings
|
|
70
|
+
│ │ ├── security.py # JWT, password hashing
|
|
71
|
+
│ │ └── database.py # Database connection
|
|
72
|
+
│ ├── models/ # SQLAlchemy models
|
|
73
|
+
│ │ ├── user.py
|
|
74
|
+
│ │ └── task.py
|
|
75
|
+
│ ├── schemas/ # Pydantic schemas
|
|
76
|
+
│ │ ├── user.py
|
|
77
|
+
│ │ └── task.py
|
|
78
|
+
│ ├── services/ # Business logic
|
|
79
|
+
│ │ ├── auth.py
|
|
80
|
+
│ │ └── task.py
|
|
81
|
+
│ ├── db/
|
|
82
|
+
│ │ └── init_db.py # Database initialization
|
|
83
|
+
│ ├── main.py # FastAPI app
|
|
84
|
+
│ └── __init__.py
|
|
85
|
+
├── tests/
|
|
86
|
+
│ ├── conftest.py
|
|
87
|
+
│ ├── test_auth.py
|
|
88
|
+
│ └── test_tasks.py
|
|
89
|
+
├── alembic/ # Database migrations
|
|
90
|
+
│ ├── versions/
|
|
91
|
+
│ └── env.py
|
|
92
|
+
├── .env.example
|
|
93
|
+
├── .gitignore
|
|
94
|
+
├── requirements.txt
|
|
95
|
+
├── pyproject.toml
|
|
96
|
+
├── Dockerfile
|
|
97
|
+
├── docker-compose.yml
|
|
98
|
+
└── README.md
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Generated Files
|
|
104
|
+
|
|
105
|
+
### 1. **app/main.py** (Application Entry)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from fastapi import FastAPI
|
|
109
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
110
|
+
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
111
|
+
|
|
112
|
+
from app.api.v1 import auth, tasks
|
|
113
|
+
from app.core.config import settings
|
|
114
|
+
from app.core.database import engine
|
|
115
|
+
from app.models import Base
|
|
116
|
+
|
|
117
|
+
# Create database tables
|
|
118
|
+
Base.metadata.create_all(bind=engine)
|
|
119
|
+
|
|
120
|
+
app = FastAPI(
|
|
121
|
+
title=settings.PROJECT_NAME,
|
|
122
|
+
version="1.0.0",
|
|
123
|
+
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
|
124
|
+
docs_url=f"{settings.API_V1_STR}/docs",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# CORS middleware
|
|
128
|
+
app.add_middleware(
|
|
129
|
+
CORSMiddleware,
|
|
130
|
+
allow_origins=settings.ALLOWED_ORIGINS,
|
|
131
|
+
allow_credentials=True,
|
|
132
|
+
allow_methods=["*"],
|
|
133
|
+
allow_headers=["*"],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Security middleware
|
|
137
|
+
app.add_middleware(
|
|
138
|
+
TrustedHostMiddleware,
|
|
139
|
+
allowed_hosts=settings.ALLOWED_HOSTS
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@app.get("/health")
|
|
143
|
+
async def health_check():
|
|
144
|
+
return {
|
|
145
|
+
"status": "ok",
|
|
146
|
+
"version": "1.0.0"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Include routers
|
|
150
|
+
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["auth"])
|
|
151
|
+
app.include_router(tasks.router, prefix=f"{settings.API_V1_STR}/tasks", tags=["tasks"])
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
import uvicorn
|
|
155
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2. **app/core/config.py** (Settings)
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from pydantic_settings import BaseSettings
|
|
162
|
+
from typing import List
|
|
163
|
+
|
|
164
|
+
class Settings(BaseSettings):
|
|
165
|
+
PROJECT_NAME: str = "Task API"
|
|
166
|
+
API_V1_STR: str = "/api/v1"
|
|
167
|
+
|
|
168
|
+
# Database
|
|
169
|
+
DATABASE_URL: str
|
|
170
|
+
|
|
171
|
+
# Security
|
|
172
|
+
SECRET_KEY: str
|
|
173
|
+
ALGORITHM: str = "HS256"
|
|
174
|
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days
|
|
175
|
+
|
|
176
|
+
# CORS
|
|
177
|
+
ALLOWED_ORIGINS: List[str] = ["http://localhost:3000"]
|
|
178
|
+
ALLOWED_HOSTS: List[str] = ["*"]
|
|
179
|
+
|
|
180
|
+
class Config:
|
|
181
|
+
env_file = ".env"
|
|
182
|
+
case_sensitive = True
|
|
183
|
+
|
|
184
|
+
settings = Settings()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 3. **app/core/security.py** (Authentication)
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from datetime import datetime, timedelta
|
|
191
|
+
from typing import Optional
|
|
192
|
+
from jose import jwt, JWTError
|
|
193
|
+
from passlib.context import CryptContext
|
|
194
|
+
from fastapi import Depends, HTTPException, status
|
|
195
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
196
|
+
|
|
197
|
+
from app.core.config import settings
|
|
198
|
+
|
|
199
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
200
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
|
|
201
|
+
|
|
202
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
203
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
204
|
+
|
|
205
|
+
def get_password_hash(password: str) -> str:
|
|
206
|
+
return pwd_context.hash(password)
|
|
207
|
+
|
|
208
|
+
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
209
|
+
to_encode = data.copy()
|
|
210
|
+
|
|
211
|
+
if expires_delta:
|
|
212
|
+
expire = datetime.utcnow() + expires_delta
|
|
213
|
+
else:
|
|
214
|
+
expire = datetime.utcnow() + timedelta(
|
|
215
|
+
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
to_encode.update({"exp": expire})
|
|
219
|
+
encoded_jwt = jwt.encode(
|
|
220
|
+
to_encode,
|
|
221
|
+
settings.SECRET_KEY,
|
|
222
|
+
algorithm=settings.ALGORITHM
|
|
223
|
+
)
|
|
224
|
+
return encoded_jwt
|
|
225
|
+
|
|
226
|
+
def decode_access_token(token: str) -> dict:
|
|
227
|
+
try:
|
|
228
|
+
payload = jwt.decode(
|
|
229
|
+
token,
|
|
230
|
+
settings.SECRET_KEY,
|
|
231
|
+
algorithms=[settings.ALGORITHM]
|
|
232
|
+
)
|
|
233
|
+
return payload
|
|
234
|
+
except JWTError:
|
|
235
|
+
raise HTTPException(
|
|
236
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
237
|
+
detail="Could not validate credentials",
|
|
238
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 4. **app/core/database.py** (Database Setup)
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from sqlalchemy import create_engine
|
|
246
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
247
|
+
from sqlalchemy.orm import sessionmaker
|
|
248
|
+
from app.core.config import settings
|
|
249
|
+
|
|
250
|
+
engine = create_engine(
|
|
251
|
+
settings.DATABASE_URL,
|
|
252
|
+
pool_pre_ping=True,
|
|
253
|
+
echo=False
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
SessionLocal = sessionmaker(
|
|
257
|
+
autocommit=False,
|
|
258
|
+
autoflush=False,
|
|
259
|
+
bind=engine
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
Base = declarative_base()
|
|
263
|
+
|
|
264
|
+
def get_db():
|
|
265
|
+
db = SessionLocal()
|
|
266
|
+
try:
|
|
267
|
+
yield db
|
|
268
|
+
finally:
|
|
269
|
+
db.close()
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 5. **app/models/user.py** (User Model)
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from sqlalchemy import Column, String, DateTime
|
|
276
|
+
from sqlalchemy.orm import relationship
|
|
277
|
+
from datetime import datetime
|
|
278
|
+
import uuid
|
|
279
|
+
|
|
280
|
+
from app.core.database import Base
|
|
281
|
+
|
|
282
|
+
class User(Base):
|
|
283
|
+
__tablename__ = "users"
|
|
284
|
+
|
|
285
|
+
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
286
|
+
email = Column(String, unique=True, index=True, nullable=False)
|
|
287
|
+
hashed_password = Column(String, nullable=False)
|
|
288
|
+
name = Column(String, nullable=False)
|
|
289
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
290
|
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
291
|
+
|
|
292
|
+
# Relationships
|
|
293
|
+
tasks = relationship("Task", back_populates="owner", cascade="all, delete-orphan")
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 6. **app/models/task.py** (Task Model)
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey
|
|
300
|
+
from sqlalchemy.orm import relationship
|
|
301
|
+
from datetime import datetime
|
|
302
|
+
import uuid
|
|
303
|
+
|
|
304
|
+
from app.core.database import Base
|
|
305
|
+
|
|
306
|
+
class Task(Base):
|
|
307
|
+
__tablename__ = "tasks"
|
|
308
|
+
|
|
309
|
+
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
310
|
+
title = Column(String, nullable=False)
|
|
311
|
+
description = Column(String, nullable=True)
|
|
312
|
+
completed = Column(Boolean, default=False)
|
|
313
|
+
user_id = Column(String, ForeignKey("users.id", ondelete="CASCADE"))
|
|
314
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
315
|
+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
316
|
+
|
|
317
|
+
# Relationships
|
|
318
|
+
owner = relationship("User", back_populates="tasks")
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 7. **app/schemas/user.py** (Pydantic Schemas)
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from pydantic import BaseModel, EmailStr
|
|
325
|
+
from datetime import datetime
|
|
326
|
+
from typing import Optional
|
|
327
|
+
|
|
328
|
+
class UserBase(BaseModel):
|
|
329
|
+
email: EmailStr
|
|
330
|
+
name: str
|
|
331
|
+
|
|
332
|
+
class UserCreate(UserBase):
|
|
333
|
+
password: str
|
|
334
|
+
|
|
335
|
+
class UserUpdate(BaseModel):
|
|
336
|
+
name: Optional[str] = None
|
|
337
|
+
email: Optional[EmailStr] = None
|
|
338
|
+
|
|
339
|
+
class UserInDB(UserBase):
|
|
340
|
+
id: str
|
|
341
|
+
created_at: datetime
|
|
342
|
+
updated_at: datetime
|
|
343
|
+
|
|
344
|
+
class Config:
|
|
345
|
+
from_attributes = True
|
|
346
|
+
|
|
347
|
+
class User(UserInDB):
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
class Token(BaseModel):
|
|
351
|
+
access_token: str
|
|
352
|
+
token_type: str
|
|
353
|
+
|
|
354
|
+
class TokenData(BaseModel):
|
|
355
|
+
email: Optional[str] = None
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### 8. **app/schemas/task.py** (Task Schemas)
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from pydantic import BaseModel
|
|
362
|
+
from datetime import datetime
|
|
363
|
+
from typing import Optional
|
|
364
|
+
|
|
365
|
+
class TaskBase(BaseModel):
|
|
366
|
+
title: str
|
|
367
|
+
description: Optional[str] = None
|
|
368
|
+
|
|
369
|
+
class TaskCreate(TaskBase):
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
class TaskUpdate(BaseModel):
|
|
373
|
+
title: Optional[str] = None
|
|
374
|
+
description: Optional[str] = None
|
|
375
|
+
completed: Optional[bool] = None
|
|
376
|
+
|
|
377
|
+
class TaskInDB(TaskBase):
|
|
378
|
+
id: str
|
|
379
|
+
completed: bool
|
|
380
|
+
user_id: str
|
|
381
|
+
created_at: datetime
|
|
382
|
+
updated_at: datetime
|
|
383
|
+
|
|
384
|
+
class Config:
|
|
385
|
+
from_attributes = True
|
|
386
|
+
|
|
387
|
+
class Task(TaskInDB):
|
|
388
|
+
pass
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### 9. **app/api/deps.py** (Dependencies)
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
from typing import Generator
|
|
395
|
+
from fastapi import Depends, HTTPException, status
|
|
396
|
+
from sqlalchemy.orm import Session
|
|
397
|
+
|
|
398
|
+
from app.core.database import get_db
|
|
399
|
+
from app.core.security import oauth2_scheme, decode_access_token
|
|
400
|
+
from app.models.user import User
|
|
401
|
+
|
|
402
|
+
def get_current_user(
|
|
403
|
+
db: Session = Depends(get_db),
|
|
404
|
+
token: str = Depends(oauth2_scheme)
|
|
405
|
+
) -> User:
|
|
406
|
+
payload = decode_access_token(token)
|
|
407
|
+
email: str = payload.get("sub")
|
|
408
|
+
|
|
409
|
+
if email is None:
|
|
410
|
+
raise HTTPException(
|
|
411
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
412
|
+
detail="Could not validate credentials"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
user = db.query(User).filter(User.email == email).first()
|
|
416
|
+
|
|
417
|
+
if user is None:
|
|
418
|
+
raise HTTPException(
|
|
419
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
420
|
+
detail="User not found"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return user
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### 10. **app/api/v1/tasks.py** (Task Endpoints)
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
from typing import List
|
|
430
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
431
|
+
from sqlalchemy.orm import Session
|
|
432
|
+
|
|
433
|
+
from app.api.deps import get_current_user
|
|
434
|
+
from app.core.database import get_db
|
|
435
|
+
from app.models.user import User
|
|
436
|
+
from app.models.task import Task as TaskModel
|
|
437
|
+
from app.schemas.task import Task, TaskCreate, TaskUpdate
|
|
438
|
+
|
|
439
|
+
router = APIRouter()
|
|
440
|
+
|
|
441
|
+
@router.get("/", response_model=List[Task])
|
|
442
|
+
async def list_tasks(
|
|
443
|
+
db: Session = Depends(get_db),
|
|
444
|
+
current_user: User = Depends(get_current_user),
|
|
445
|
+
skip: int = 0,
|
|
446
|
+
limit: int = 100
|
|
447
|
+
):
|
|
448
|
+
tasks = db.query(TaskModel)\
|
|
449
|
+
.filter(TaskModel.user_id == current_user.id)\
|
|
450
|
+
.offset(skip)\
|
|
451
|
+
.limit(limit)\
|
|
452
|
+
.all()
|
|
453
|
+
return tasks
|
|
454
|
+
|
|
455
|
+
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
|
|
456
|
+
async def create_task(
|
|
457
|
+
task_in: TaskCreate,
|
|
458
|
+
db: Session = Depends(get_db),
|
|
459
|
+
current_user: User = Depends(get_current_user)
|
|
460
|
+
):
|
|
461
|
+
task = TaskModel(
|
|
462
|
+
**task_in.dict(),
|
|
463
|
+
user_id=current_user.id
|
|
464
|
+
)
|
|
465
|
+
db.add(task)
|
|
466
|
+
db.commit()
|
|
467
|
+
db.refresh(task)
|
|
468
|
+
return task
|
|
469
|
+
|
|
470
|
+
@router.get("/{task_id}", response_model=Task)
|
|
471
|
+
async def get_task(
|
|
472
|
+
task_id: str,
|
|
473
|
+
db: Session = Depends(get_db),
|
|
474
|
+
current_user: User = Depends(get_current_user)
|
|
475
|
+
):
|
|
476
|
+
task = db.query(TaskModel)\
|
|
477
|
+
.filter(TaskModel.id == task_id, TaskModel.user_id == current_user.id)\
|
|
478
|
+
.first()
|
|
479
|
+
|
|
480
|
+
if not task:
|
|
481
|
+
raise HTTPException(
|
|
482
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
483
|
+
detail="Task not found"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
return task
|
|
487
|
+
|
|
488
|
+
@router.patch("/{task_id}", response_model=Task)
|
|
489
|
+
async def update_task(
|
|
490
|
+
task_id: str,
|
|
491
|
+
task_in: TaskUpdate,
|
|
492
|
+
db: Session = Depends(get_db),
|
|
493
|
+
current_user: User = Depends(get_current_user)
|
|
494
|
+
):
|
|
495
|
+
task = db.query(TaskModel)\
|
|
496
|
+
.filter(TaskModel.id == task_id, TaskModel.user_id == current_user.id)\
|
|
497
|
+
.first()
|
|
498
|
+
|
|
499
|
+
if not task:
|
|
500
|
+
raise HTTPException(
|
|
501
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
502
|
+
detail="Task not found"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
for field, value in task_in.dict(exclude_unset=True).items():
|
|
506
|
+
setattr(task, field, value)
|
|
507
|
+
|
|
508
|
+
db.commit()
|
|
509
|
+
db.refresh(task)
|
|
510
|
+
return task
|
|
511
|
+
|
|
512
|
+
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
513
|
+
async def delete_task(
|
|
514
|
+
task_id: str,
|
|
515
|
+
db: Session = Depends(get_db),
|
|
516
|
+
current_user: User = Depends(get_current_user)
|
|
517
|
+
):
|
|
518
|
+
task = db.query(TaskModel)\
|
|
519
|
+
.filter(TaskModel.id == task_id, TaskModel.user_id == current_user.id)\
|
|
520
|
+
.first()
|
|
521
|
+
|
|
522
|
+
if not task:
|
|
523
|
+
raise HTTPException(
|
|
524
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
525
|
+
detail="Task not found"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
db.delete(task)
|
|
529
|
+
db.commit()
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### 11. **tests/test_tasks.py** (Pytest Tests)
|
|
533
|
+
|
|
534
|
+
```python
|
|
535
|
+
import pytest
|
|
536
|
+
from httpx import AsyncClient
|
|
537
|
+
from app.main import app
|
|
538
|
+
|
|
539
|
+
@pytest.mark.asyncio
|
|
540
|
+
async def test_create_task(client: AsyncClient, test_user_token):
|
|
541
|
+
response = await client.post(
|
|
542
|
+
"/api/v1/tasks/",
|
|
543
|
+
json={
|
|
544
|
+
"title": "Test Task",
|
|
545
|
+
"description": "Test description"
|
|
546
|
+
},
|
|
547
|
+
headers={"Authorization": f"Bearer {test_user_token}"}
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
assert response.status_code == 201
|
|
551
|
+
data = response.json()
|
|
552
|
+
assert data["title"] == "Test Task"
|
|
553
|
+
assert "id" in data
|
|
554
|
+
|
|
555
|
+
@pytest.mark.asyncio
|
|
556
|
+
async def test_list_tasks(client: AsyncClient, test_user_token):
|
|
557
|
+
response = await client.get(
|
|
558
|
+
"/api/v1/tasks/",
|
|
559
|
+
headers={"Authorization": f"Bearer {test_user_token}"}
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
assert response.status_code == 200
|
|
563
|
+
data = response.json()
|
|
564
|
+
assert isinstance(data, list)
|
|
565
|
+
|
|
566
|
+
@pytest.mark.asyncio
|
|
567
|
+
async def test_create_task_unauthorized(client: AsyncClient):
|
|
568
|
+
response = await client.post(
|
|
569
|
+
"/api/v1/tasks/",
|
|
570
|
+
json={"title": "Test"}
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
assert response.status_code == 401
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### 12. **requirements.txt**
|
|
577
|
+
|
|
578
|
+
```
|
|
579
|
+
fastapi==0.109.0
|
|
580
|
+
uvicorn[standard]==0.27.0
|
|
581
|
+
sqlalchemy==2.0.25
|
|
582
|
+
pydantic==2.5.3
|
|
583
|
+
pydantic-settings==2.1.0
|
|
584
|
+
python-jose[cryptography]==3.3.0
|
|
585
|
+
passlib[bcrypt]==1.7.4
|
|
586
|
+
python-multipart==0.0.6
|
|
587
|
+
alembic==1.13.1
|
|
588
|
+
psycopg2-binary==2.9.9
|
|
589
|
+
|
|
590
|
+
# Development
|
|
591
|
+
pytest==7.4.4
|
|
592
|
+
pytest-asyncio==0.23.3
|
|
593
|
+
httpx==0.26.0
|
|
594
|
+
black==23.12.1
|
|
595
|
+
isort==5.13.2
|
|
596
|
+
mypy==1.8.0
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Features
|
|
602
|
+
|
|
603
|
+
**Performance:**
|
|
604
|
+
- Async/await for high concurrency
|
|
605
|
+
- Background tasks support
|
|
606
|
+
- WebSocket support (optional)
|
|
607
|
+
- Automatic Pydantic validation
|
|
608
|
+
|
|
609
|
+
**Documentation:**
|
|
610
|
+
- Auto-generated OpenAPI (Swagger)
|
|
611
|
+
- ReDoc documentation
|
|
612
|
+
- Type hints throughout
|
|
613
|
+
|
|
614
|
+
**Database:**
|
|
615
|
+
- SQLAlchemy ORM with async support
|
|
616
|
+
- Alembic migrations
|
|
617
|
+
- Connection pooling
|
|
618
|
+
|
|
619
|
+
**Security:**
|
|
620
|
+
- JWT authentication
|
|
621
|
+
- Password hashing (bcrypt)
|
|
622
|
+
- CORS middleware
|
|
623
|
+
- Trusted host middleware
|
|
624
|
+
|
|
625
|
+
**Testing:**
|
|
626
|
+
- Pytest with async support
|
|
627
|
+
- Test fixtures
|
|
628
|
+
- Coverage reporting
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## Getting Started
|
|
633
|
+
|
|
634
|
+
**1. Install dependencies:**
|
|
635
|
+
```bash
|
|
636
|
+
pip install -r requirements.txt
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**2. Configure environment:**
|
|
640
|
+
```bash
|
|
641
|
+
cp .env.example .env
|
|
642
|
+
# Edit .env with your database URL and secrets
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**3. Run database migrations:**
|
|
646
|
+
```bash
|
|
647
|
+
alembic upgrade head
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**4. Start development server:**
|
|
651
|
+
```bash
|
|
652
|
+
uvicorn app.main:app --reload
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
**5. View API docs:**
|
|
656
|
+
- Swagger UI: http://localhost:8000/api/v1/docs
|
|
657
|
+
- ReDoc: http://localhost:8000/api/v1/redoc
|
|
658
|
+
|
|
659
|
+
**6. Run tests:**
|
|
660
|
+
```bash
|
|
661
|
+
pytest
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Related Commands
|
|
667
|
+
|
|
668
|
+
- `/express-api-scaffold` - Generate Express.js boilerplate
|
|
669
|
+
- Backend Architect (agent) - Architecture review
|
|
670
|
+
- API Builder (agent) - API design guidance
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
**Build high-performance APIs. Scale effortlessly. Deploy confidently.**
|