mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 09:37:15 +00:00
Repository Restructure: - Move all 83 agent .md files to agents/ subdirectory - Add 15 workflow orchestrators from commands repo to workflows/ - Add 42 development tools from commands repo to tools/ - Update README for unified repository structure This prepares the repository for unified plugin marketplace integration. The commands repository functionality is now fully integrated, providing complete workflow orchestration and development tooling alongside agents. Directory Structure: - agents/ - 83 specialized AI agents - workflows/ - 15 multi-agent orchestration commands - tools/ - 42 focused development utilities No breaking changes to agent functionality - all agents remain accessible with same names and behavior. Adds workflow and tool commands for enhanced multi-agent coordination capabilities.
1777 lines
46 KiB
Markdown
1777 lines
46 KiB
Markdown
---
|
|
model: claude-sonnet-4-0
|
|
---
|
|
|
|
# API Scaffold Generator
|
|
|
|
You are an API development expert specializing in creating production-ready, scalable REST APIs with modern frameworks. Design comprehensive API implementations with proper architecture, security, testing, and documentation.
|
|
|
|
## Context
|
|
The user needs to create a new API endpoint or service with complete implementation including models, validation, security, testing, and deployment configuration. Focus on production-ready code that follows industry best practices.
|
|
|
|
## Requirements
|
|
$ARGUMENTS
|
|
|
|
## Instructions
|
|
|
|
### 1. API Framework Selection
|
|
|
|
Choose the appropriate framework based on requirements:
|
|
|
|
**Framework Comparison Matrix**
|
|
```python
|
|
def select_framework(requirements):
|
|
"""Select optimal API framework based on requirements"""
|
|
|
|
frameworks = {
|
|
'fastapi': {
|
|
'best_for': ['high_performance', 'async_operations', 'type_safety', 'modern_python'],
|
|
'strengths': ['Auto OpenAPI docs', 'Type hints', 'Async support', 'Fast performance'],
|
|
'use_cases': ['Microservices', 'Data APIs', 'ML APIs', 'Real-time systems'],
|
|
'example_stack': 'FastAPI + Pydantic + SQLAlchemy + PostgreSQL'
|
|
},
|
|
'django_rest': {
|
|
'best_for': ['rapid_development', 'orm_integration', 'admin_interface', 'large_teams'],
|
|
'strengths': ['Batteries included', 'ORM', 'Admin panel', 'Mature ecosystem'],
|
|
'use_cases': ['CRUD applications', 'Content management', 'Enterprise systems'],
|
|
'example_stack': 'Django + DRF + PostgreSQL + Redis'
|
|
},
|
|
'express': {
|
|
'best_for': ['node_ecosystem', 'real_time', 'frontend_integration', 'javascript_teams'],
|
|
'strengths': ['NPM ecosystem', 'JSON handling', 'WebSocket support', 'Fast development'],
|
|
'use_cases': ['Real-time apps', 'API gateways', 'Serverless functions'],
|
|
'example_stack': 'Express + TypeScript + Prisma + PostgreSQL'
|
|
},
|
|
'spring_boot': {
|
|
'best_for': ['enterprise', 'java_teams', 'complex_business_logic', 'microservices'],
|
|
'strengths': ['Enterprise features', 'Dependency injection', 'Security', 'Monitoring'],
|
|
'use_cases': ['Enterprise APIs', 'Financial systems', 'Complex microservices'],
|
|
'example_stack': 'Spring Boot + JPA + PostgreSQL + Redis'
|
|
}
|
|
}
|
|
|
|
# Selection logic based on requirements
|
|
if 'high_performance' in requirements:
|
|
return frameworks['fastapi']
|
|
elif 'enterprise' in requirements:
|
|
return frameworks['spring_boot']
|
|
elif 'rapid_development' in requirements:
|
|
return frameworks['django_rest']
|
|
elif 'real_time' in requirements:
|
|
return frameworks['express']
|
|
|
|
return frameworks['fastapi'] # Default recommendation
|
|
```
|
|
|
|
### 2. FastAPI Implementation
|
|
|
|
Complete FastAPI API implementation:
|
|
|
|
**Project Structure**
|
|
```
|
|
project/
|
|
├── app/
|
|
│ ├── __init__.py
|
|
│ ├── main.py
|
|
│ ├── core/
|
|
│ │ ├── config.py
|
|
│ │ ├── security.py
|
|
│ │ └── database.py
|
|
│ ├── api/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── deps.py
|
|
│ │ └── v1/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── endpoints/
|
|
│ │ │ ├── users.py
|
|
│ │ │ └── items.py
|
|
│ │ └── api.py
|
|
│ ├── models/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── user.py
|
|
│ │ └── item.py
|
|
│ ├── schemas/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── user.py
|
|
│ │ └── item.py
|
|
│ ├── services/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── user_service.py
|
|
│ │ └── item_service.py
|
|
│ └── tests/
|
|
│ ├── conftest.py
|
|
│ ├── test_users.py
|
|
│ └── test_items.py
|
|
├── alembic/
|
|
├── requirements.txt
|
|
├── Dockerfile
|
|
└── docker-compose.yml
|
|
```
|
|
|
|
**Core Configuration**
|
|
```python
|
|
# app/core/config.py
|
|
from pydantic import BaseSettings, validator
|
|
from typing import Optional, Dict, Any
|
|
import secrets
|
|
|
|
class Settings(BaseSettings):
|
|
API_V1_STR: str = "/api/v1"
|
|
SECRET_KEY: str = secrets.token_urlsafe(32)
|
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days
|
|
SERVER_NAME: str = "localhost"
|
|
SERVER_HOST: str = "0.0.0.0"
|
|
|
|
# Database
|
|
POSTGRES_SERVER: str = "localhost"
|
|
POSTGRES_USER: str = "postgres"
|
|
POSTGRES_PASSWORD: str = ""
|
|
POSTGRES_DB: str = "app"
|
|
DATABASE_URL: Optional[str] = None
|
|
|
|
@validator("DATABASE_URL", pre=True)
|
|
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
|
|
if isinstance(v, str):
|
|
return v
|
|
return f"postgresql://{values.get('POSTGRES_USER')}:{values.get('POSTGRES_PASSWORD')}@{values.get('POSTGRES_SERVER')}/{values.get('POSTGRES_DB')}"
|
|
|
|
# Redis
|
|
REDIS_URL: str = "redis://localhost:6379"
|
|
|
|
# Security
|
|
BACKEND_CORS_ORIGINS: list = ["http://localhost:3000", "http://localhost:8000"]
|
|
|
|
# Rate Limiting
|
|
RATE_LIMIT_REQUESTS: int = 100
|
|
RATE_LIMIT_WINDOW: int = 60
|
|
|
|
# Monitoring
|
|
SENTRY_DSN: Optional[str] = None
|
|
LOG_LEVEL: str = "INFO"
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
|
|
settings = Settings()
|
|
```
|
|
|
|
**Database Setup**
|
|
```python
|
|
# app/core/database.py
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy.pool import StaticPool
|
|
import redis
|
|
|
|
from app.core.config import settings
|
|
|
|
# PostgreSQL
|
|
engine = create_engine(
|
|
settings.DATABASE_URL,
|
|
poolclass=StaticPool,
|
|
pool_size=20,
|
|
max_overflow=30,
|
|
pool_pre_ping=True,
|
|
echo=settings.LOG_LEVEL == "DEBUG"
|
|
)
|
|
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
Base = declarative_base()
|
|
|
|
# Redis
|
|
redis_client = redis.from_url(settings.REDIS_URL, decode_responses=True)
|
|
|
|
def get_db():
|
|
"""Dependency to get database session"""
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
def get_redis():
|
|
"""Dependency to get Redis connection"""
|
|
return redis_client
|
|
```
|
|
|
|
**Security Implementation**
|
|
```python
|
|
# app/core/security.py
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
import jwt
|
|
from passlib.context import CryptContext
|
|
from fastapi import HTTPException, status
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
|
|
from app.core.config import settings
|
|
|
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
security = HTTPBearer()
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
"""Create JWT access token"""
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.utcnow() + expires_delta
|
|
else:
|
|
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
|
|
to_encode.update({"exp": expire})
|
|
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")
|
|
return encoded_jwt
|
|
|
|
def verify_token(credentials: HTTPAuthorizationCredentials) -> dict:
|
|
"""Verify JWT token"""
|
|
try:
|
|
payload = jwt.decode(
|
|
credentials.credentials,
|
|
settings.SECRET_KEY,
|
|
algorithms=["HS256"]
|
|
)
|
|
username: str = payload.get("sub")
|
|
if username is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Could not validate credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
return payload
|
|
except jwt.PyJWTError:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Could not validate credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Verify password against hash"""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
def get_password_hash(password: str) -> str:
|
|
"""Generate password hash"""
|
|
return pwd_context.hash(password)
|
|
```
|
|
|
|
**Models Implementation**
|
|
```python
|
|
# app/models/user.py
|
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Text
|
|
from sqlalchemy.sql import func
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.core.database import Base
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
email = Column(String, unique=True, index=True, nullable=False)
|
|
username = Column(String, unique=True, index=True, nullable=False)
|
|
hashed_password = Column(String, nullable=False)
|
|
full_name = Column(String)
|
|
is_active = Column(Boolean, default=True)
|
|
is_superuser = Column(Boolean, default=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
items = relationship("Item", back_populates="owner")
|
|
|
|
# app/models/item.py
|
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Text, ForeignKey
|
|
from sqlalchemy.sql import func
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.core.database import Base
|
|
|
|
class Item(Base):
|
|
__tablename__ = "items"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
title = Column(String, index=True, nullable=False)
|
|
description = Column(Text)
|
|
is_active = Column(Boolean, default=True)
|
|
owner_id = Column(Integer, ForeignKey("users.id"))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
owner = relationship("User", back_populates="items")
|
|
```
|
|
|
|
**Pydantic Schemas**
|
|
```python
|
|
# app/schemas/user.py
|
|
from pydantic import BaseModel, EmailStr, validator
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
class UserBase(BaseModel):
|
|
email: EmailStr
|
|
username: str
|
|
full_name: Optional[str] = None
|
|
|
|
class UserCreate(UserBase):
|
|
password: str
|
|
|
|
@validator('password')
|
|
def validate_password(cls, v):
|
|
if len(v) < 8:
|
|
raise ValueError('Password must be at least 8 characters')
|
|
return v
|
|
|
|
class UserUpdate(BaseModel):
|
|
email: Optional[EmailStr] = None
|
|
username: Optional[str] = None
|
|
full_name: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
|
|
class UserInDB(UserBase):
|
|
id: int
|
|
is_active: bool
|
|
is_superuser: bool
|
|
created_at: datetime
|
|
updated_at: Optional[datetime]
|
|
|
|
class Config:
|
|
orm_mode = True
|
|
|
|
class User(UserInDB):
|
|
pass
|
|
|
|
# app/schemas/item.py
|
|
from pydantic import BaseModel, validator
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
class ItemBase(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
|
|
class ItemCreate(ItemBase):
|
|
@validator('title')
|
|
def validate_title(cls, v):
|
|
if len(v.strip()) < 3:
|
|
raise ValueError('Title must be at least 3 characters')
|
|
return v.strip()
|
|
|
|
class ItemUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
|
|
class ItemInDB(ItemBase):
|
|
id: int
|
|
is_active: bool
|
|
owner_id: int
|
|
created_at: datetime
|
|
updated_at: Optional[datetime]
|
|
|
|
class Config:
|
|
orm_mode = True
|
|
|
|
class Item(ItemInDB):
|
|
pass
|
|
```
|
|
|
|
**Service Layer**
|
|
```python
|
|
# app/services/user_service.py
|
|
from typing import Optional, List
|
|
from sqlalchemy.orm import Session
|
|
from fastapi import HTTPException, status
|
|
|
|
from app.models.user import User
|
|
from app.schemas.user import UserCreate, UserUpdate
|
|
from app.core.security import get_password_hash, verify_password
|
|
|
|
class UserService:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
|
|
def get_user(self, user_id: int) -> Optional[User]:
|
|
"""Get user by ID"""
|
|
return self.db.query(User).filter(User.id == user_id).first()
|
|
|
|
def get_user_by_email(self, email: str) -> Optional[User]:
|
|
"""Get user by email"""
|
|
return self.db.query(User).filter(User.email == email).first()
|
|
|
|
def get_users(self, skip: int = 0, limit: int = 100) -> List[User]:
|
|
"""Get list of users"""
|
|
return self.db.query(User).offset(skip).limit(limit).all()
|
|
|
|
def create_user(self, user_create: UserCreate) -> User:
|
|
"""Create new user"""
|
|
# Check if user exists
|
|
if self.get_user_by_email(user_create.email):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Email already registered"
|
|
)
|
|
|
|
# Create user
|
|
hashed_password = get_password_hash(user_create.password)
|
|
db_user = User(
|
|
email=user_create.email,
|
|
username=user_create.username,
|
|
full_name=user_create.full_name,
|
|
hashed_password=hashed_password
|
|
)
|
|
self.db.add(db_user)
|
|
self.db.commit()
|
|
self.db.refresh(db_user)
|
|
return db_user
|
|
|
|
def update_user(self, user_id: int, user_update: UserUpdate) -> User:
|
|
"""Update user"""
|
|
db_user = self.get_user(user_id)
|
|
if not db_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
update_data = user_update.dict(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(db_user, field, value)
|
|
|
|
self.db.commit()
|
|
self.db.refresh(db_user)
|
|
return db_user
|
|
|
|
def authenticate_user(self, email: str, password: str) -> Optional[User]:
|
|
"""Authenticate user"""
|
|
user = self.get_user_by_email(email)
|
|
if not user or not verify_password(password, user.hashed_password):
|
|
return None
|
|
return user
|
|
```
|
|
|
|
**Rate Limiting Middleware**
|
|
```python
|
|
# app/core/rate_limiting.py
|
|
import time
|
|
from typing import Callable
|
|
from fastapi import Request, HTTPException, status
|
|
from fastapi.responses import JSONResponse
|
|
import redis
|
|
|
|
from app.core.config import settings
|
|
from app.core.database import get_redis
|
|
|
|
class RateLimiter:
|
|
def __init__(self, redis_client: redis.Redis):
|
|
self.redis = redis_client
|
|
self.requests = settings.RATE_LIMIT_REQUESTS
|
|
self.window = settings.RATE_LIMIT_WINDOW
|
|
|
|
async def __call__(self, request: Request, call_next: Callable):
|
|
# Get client identifier
|
|
client_ip = request.client.host
|
|
user_id = getattr(request.state, 'user_id', None)
|
|
key = f"rate_limit:{user_id or client_ip}"
|
|
|
|
# Check rate limit
|
|
current = self.redis.get(key)
|
|
if current is None:
|
|
# First request in window
|
|
self.redis.setex(key, self.window, 1)
|
|
else:
|
|
current = int(current)
|
|
if current >= self.requests:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail=f"Rate limit exceeded. Try again in {self.redis.ttl(key)} seconds",
|
|
headers={"Retry-After": str(self.redis.ttl(key))}
|
|
)
|
|
self.redis.incr(key)
|
|
|
|
response = await call_next(request)
|
|
|
|
# Add rate limit headers
|
|
remaining = max(0, self.requests - int(self.redis.get(key) or 0))
|
|
response.headers["X-RateLimit-Limit"] = str(self.requests)
|
|
response.headers["X-RateLimit-Remaining"] = str(remaining)
|
|
response.headers["X-RateLimit-Reset"] = str(int(time.time()) + self.redis.ttl(key))
|
|
|
|
return response
|
|
```
|
|
|
|
**API Endpoints**
|
|
```python
|
|
# app/api/v1/endpoints/users.py
|
|
from typing import List
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.core.security import verify_token, security
|
|
from app.schemas.user import User, UserCreate, UserUpdate
|
|
from app.services.user_service import UserService
|
|
|
|
router = APIRouter()
|
|
|
|
@router.post("/", response_model=User, status_code=status.HTTP_201_CREATED)
|
|
async def create_user(
|
|
user_create: UserCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create new user"""
|
|
service = UserService(db)
|
|
return service.create_user(user_create)
|
|
|
|
@router.get("/me", response_model=User)
|
|
async def get_current_user(
|
|
token: dict = Depends(verify_token),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get current user profile"""
|
|
service = UserService(db)
|
|
user = service.get_user_by_email(token["sub"])
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
return user
|
|
|
|
@router.get("/{user_id}", response_model=User)
|
|
async def get_user(
|
|
user_id: int,
|
|
db: Session = Depends(get_db),
|
|
token: dict = Depends(verify_token)
|
|
):
|
|
"""Get user by ID"""
|
|
service = UserService(db)
|
|
user = service.get_user(user_id)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
return user
|
|
|
|
@router.put("/{user_id}", response_model=User)
|
|
async def update_user(
|
|
user_id: int,
|
|
user_update: UserUpdate,
|
|
db: Session = Depends(get_db),
|
|
token: dict = Depends(verify_token)
|
|
):
|
|
"""Update user"""
|
|
service = UserService(db)
|
|
return service.update_user(user_id, user_update)
|
|
|
|
@router.get("/", response_model=List[User])
|
|
async def list_users(
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
db: Session = Depends(get_db),
|
|
token: dict = Depends(verify_token)
|
|
):
|
|
"""List users"""
|
|
service = UserService(db)
|
|
return service.get_users(skip=skip, limit=limit)
|
|
```
|
|
|
|
**Main Application**
|
|
```python
|
|
# app/main.py
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
import logging
|
|
import time
|
|
import uuid
|
|
|
|
from app.core.config import settings
|
|
from app.core.rate_limiting import RateLimiter
|
|
from app.core.database import get_redis
|
|
from app.api.v1.api import api_router
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=getattr(logging, settings.LOG_LEVEL),
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(
|
|
title="Production API",
|
|
description="A production-ready API with FastAPI",
|
|
version="1.0.0",
|
|
openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
|
)
|
|
|
|
# Middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.BACKEND_CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.add_middleware(
|
|
TrustedHostMiddleware,
|
|
allowed_hosts=["localhost", "127.0.0.1", settings.SERVER_NAME]
|
|
)
|
|
|
|
# Rate limiting middleware
|
|
rate_limiter = RateLimiter(get_redis())
|
|
app.middleware("http")(rate_limiter)
|
|
|
|
@app.middleware("http")
|
|
async def add_process_time_header(request: Request, call_next):
|
|
"""Add processing time and correlation ID"""
|
|
correlation_id = str(uuid.uuid4())
|
|
request.state.correlation_id = correlation_id
|
|
|
|
start_time = time.time()
|
|
response = await call_next(request)
|
|
process_time = time.time() - start_time
|
|
|
|
response.headers["X-Process-Time"] = str(process_time)
|
|
response.headers["X-Correlation-ID"] = correlation_id
|
|
return response
|
|
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request: Request, exc: Exception):
|
|
"""Global exception handler"""
|
|
correlation_id = getattr(request.state, 'correlation_id', 'unknown')
|
|
|
|
logger.error(
|
|
f"Unhandled exception: {exc}",
|
|
extra={"correlation_id": correlation_id, "path": request.url.path}
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"detail": "Internal server error",
|
|
"correlation_id": correlation_id
|
|
}
|
|
)
|
|
|
|
# Include routers
|
|
app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
"""Health check endpoint"""
|
|
return {"status": "healthy", "timestamp": time.time()}
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
"""Root endpoint"""
|
|
return {"message": "Welcome to the Production API"}
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(
|
|
"app.main:app",
|
|
host=settings.SERVER_HOST,
|
|
port=8000,
|
|
reload=True
|
|
)
|
|
```
|
|
|
|
### 3. Express.js Implementation
|
|
|
|
Complete Express.js TypeScript implementation:
|
|
|
|
**Project Structure & Setup**
|
|
```typescript
|
|
// package.json
|
|
{
|
|
"name": "express-api",
|
|
"version": "1.0.0",
|
|
"scripts": {
|
|
"dev": "nodemon src/index.ts",
|
|
"build": "tsc",
|
|
"start": "node dist/index.js",
|
|
"test": "jest",
|
|
"test:watch": "jest --watch"
|
|
},
|
|
"dependencies": {
|
|
"express": "^4.18.2",
|
|
"express-rate-limit": "^6.10.0",
|
|
"helmet": "^7.0.0",
|
|
"cors": "^2.8.5",
|
|
"compression": "^1.7.4",
|
|
"morgan": "^1.10.0",
|
|
"joi": "^17.9.2",
|
|
"jsonwebtoken": "^9.0.2",
|
|
"bcryptjs": "^2.4.3",
|
|
"prisma": "^5.1.0",
|
|
"@prisma/client": "^5.1.0",
|
|
"redis": "^4.6.7",
|
|
"winston": "^3.10.0"
|
|
},
|
|
"devDependencies": {
|
|
"@types/express": "^4.17.17",
|
|
"@types/node": "^20.4.5",
|
|
"typescript": "^5.1.6",
|
|
"nodemon": "^3.0.1",
|
|
"jest": "^29.6.1",
|
|
"@types/jest": "^29.5.3",
|
|
"supertest": "^6.3.3"
|
|
}
|
|
}
|
|
|
|
// src/types/index.ts
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
username: string;
|
|
fullName?: string;
|
|
isActive: boolean;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
export interface CreateUserRequest {
|
|
email: string;
|
|
username: string;
|
|
password: string;
|
|
fullName?: string;
|
|
}
|
|
|
|
export interface AuthTokenPayload {
|
|
userId: string;
|
|
email: string;
|
|
iat: number;
|
|
exp: number;
|
|
}
|
|
|
|
// src/config/index.ts
|
|
import { config as dotenvConfig } from 'dotenv';
|
|
|
|
dotenvConfig();
|
|
|
|
export const config = {
|
|
port: parseInt(process.env.PORT || '3000'),
|
|
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
|
|
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
|
|
redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
databaseUrl: process.env.DATABASE_URL || 'postgresql://user:pass@localhost:5432/db',
|
|
nodeEnv: process.env.NODE_ENV || 'development',
|
|
corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'],
|
|
rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '100'),
|
|
rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes
|
|
};
|
|
|
|
// src/middleware/validation.ts
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import Joi from 'joi';
|
|
|
|
export const validateRequest = (schema: Joi.ObjectSchema) => {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
const { error } = schema.validate(req.body);
|
|
|
|
if (error) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Validation error',
|
|
details: error.details.map(detail => ({
|
|
field: detail.path.join('.'),
|
|
message: detail.message
|
|
}))
|
|
});
|
|
}
|
|
|
|
next();
|
|
};
|
|
};
|
|
|
|
// Validation schemas
|
|
export const userSchemas = {
|
|
create: Joi.object({
|
|
email: Joi.string().email().required(),
|
|
username: Joi.string().alphanum().min(3).max(30).required(),
|
|
password: Joi.string().min(8).required(),
|
|
fullName: Joi.string().max(100).optional()
|
|
}),
|
|
|
|
update: Joi.object({
|
|
email: Joi.string().email().optional(),
|
|
username: Joi.string().alphanum().min(3).max(30).optional(),
|
|
fullName: Joi.string().max(100).optional(),
|
|
isActive: Joi.boolean().optional()
|
|
})
|
|
};
|
|
|
|
// src/middleware/auth.ts
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
import { config } from '../config';
|
|
import { AuthTokenPayload } from '../types';
|
|
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
user?: AuthTokenPayload;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const authenticateToken = (req: Request, res: Response, next: NextFunction) => {
|
|
const authHeader = req.headers.authorization;
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
if (!token) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Access token required'
|
|
});
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, config.jwtSecret) as AuthTokenPayload;
|
|
req.user = decoded;
|
|
next();
|
|
} catch (error) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Invalid or expired token'
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/services/userService.ts
|
|
import { PrismaClient } from '@prisma/client';
|
|
import bcrypt from 'bcryptjs';
|
|
import jwt from 'jsonwebtoken';
|
|
import { config } from '../config';
|
|
import { CreateUserRequest, User } from '../types';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
export class UserService {
|
|
async createUser(userData: CreateUserRequest): Promise<User> {
|
|
// Check if user exists
|
|
const existingUser = await prisma.user.findFirst({
|
|
where: {
|
|
OR: [
|
|
{ email: userData.email },
|
|
{ username: userData.username }
|
|
]
|
|
}
|
|
});
|
|
|
|
if (existingUser) {
|
|
throw new Error('User with this email or username already exists');
|
|
}
|
|
|
|
// Hash password
|
|
const hashedPassword = await bcrypt.hash(userData.password, 12);
|
|
|
|
// Create user
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
email: userData.email,
|
|
username: userData.username,
|
|
fullName: userData.fullName,
|
|
hashedPassword,
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
// Remove password from response
|
|
const { hashedPassword: _, ...userWithoutPassword } = user;
|
|
return userWithoutPassword as User;
|
|
}
|
|
|
|
async getUserById(id: string): Promise<User | null> {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id },
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
username: true,
|
|
fullName: true,
|
|
isActive: true,
|
|
createdAt: true,
|
|
updatedAt: true
|
|
}
|
|
});
|
|
|
|
return user;
|
|
}
|
|
|
|
async authenticateUser(email: string, password: string): Promise<string | null> {
|
|
const user = await prisma.user.findUnique({
|
|
where: { email }
|
|
});
|
|
|
|
if (!user || !await bcrypt.compare(password, user.hashedPassword)) {
|
|
return null;
|
|
}
|
|
|
|
const token = jwt.sign(
|
|
{ userId: user.id, email: user.email },
|
|
config.jwtSecret,
|
|
{ expiresIn: config.jwtExpiresIn }
|
|
);
|
|
|
|
return token;
|
|
}
|
|
|
|
async getUsers(skip = 0, take = 10): Promise<User[]> {
|
|
return await prisma.user.findMany({
|
|
skip,
|
|
take,
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
username: true,
|
|
fullName: true,
|
|
isActive: true,
|
|
createdAt: true,
|
|
updatedAt: true
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// src/controllers/userController.ts
|
|
import { Request, Response } from 'express';
|
|
import { UserService } from '../services/userService';
|
|
import { logger } from '../utils/logger';
|
|
|
|
const userService = new UserService();
|
|
|
|
export class UserController {
|
|
async createUser(req: Request, res: Response) {
|
|
try {
|
|
const user = await userService.createUser(req.body);
|
|
|
|
logger.info('User created successfully', { userId: user.id });
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'User created successfully',
|
|
data: user
|
|
});
|
|
} catch (error) {
|
|
logger.error('Error creating user', { error: error.message });
|
|
|
|
res.status(400).json({
|
|
success: false,
|
|
message: error.message || 'Failed to create user'
|
|
});
|
|
}
|
|
}
|
|
|
|
async getUser(req: Request, res: Response) {
|
|
try {
|
|
const { id } = req.params;
|
|
const user = await userService.getUserById(id);
|
|
|
|
if (!user) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'User not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: user
|
|
});
|
|
} catch (error) {
|
|
logger.error('Error fetching user', { error: error.message });
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
}
|
|
|
|
async getCurrentUser(req: Request, res: Response) {
|
|
try {
|
|
const user = await userService.getUserById(req.user!.userId);
|
|
|
|
if (!user) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'User not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: user
|
|
});
|
|
} catch (error) {
|
|
logger.error('Error fetching current user', { error: error.message });
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
}
|
|
|
|
async loginUser(req: Request, res: Response) {
|
|
try {
|
|
const { email, password } = req.body;
|
|
const token = await userService.authenticateUser(email, password);
|
|
|
|
if (!token) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid email or password'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Login successful',
|
|
data: { token }
|
|
});
|
|
} catch (error) {
|
|
logger.error('Error during login', { error: error.message });
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Testing Implementation
|
|
|
|
Comprehensive testing setup:
|
|
|
|
**FastAPI Tests**
|
|
```python
|
|
# tests/conftest.py
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
from app.main import app
|
|
from app.core.database import Base, get_db
|
|
from app.core.config import settings
|
|
|
|
# Test database
|
|
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
|
|
engine = create_engine(
|
|
SQLALCHEMY_DATABASE_URL,
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
)
|
|
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
|
|
def override_get_db():
|
|
try:
|
|
db = TestingSessionLocal()
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
app.dependency_overrides[get_db] = override_get_db
|
|
|
|
@pytest.fixture(scope="session")
|
|
def db_engine():
|
|
Base.metadata.create_all(bind=engine)
|
|
yield engine
|
|
Base.metadata.drop_all(bind=engine)
|
|
|
|
@pytest.fixture(scope="function")
|
|
def db_session(db_engine):
|
|
connection = db_engine.connect()
|
|
transaction = connection.begin()
|
|
session = TestingSessionLocal(bind=connection)
|
|
|
|
yield session
|
|
|
|
session.close()
|
|
transaction.rollback()
|
|
connection.close()
|
|
|
|
@pytest.fixture(scope="module")
|
|
def client():
|
|
with TestClient(app) as test_client:
|
|
yield test_client
|
|
|
|
# tests/test_users.py
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
def test_create_user(client: TestClient):
|
|
"""Test user creation"""
|
|
user_data = {
|
|
"email": "test@example.com",
|
|
"username": "testuser",
|
|
"password": "testpassword123",
|
|
"full_name": "Test User"
|
|
}
|
|
|
|
response = client.post("/api/v1/users/", json=user_data)
|
|
assert response.status_code == 201
|
|
|
|
data = response.json()
|
|
assert data["email"] == user_data["email"]
|
|
assert data["username"] == user_data["username"]
|
|
assert "id" in data
|
|
assert "hashed_password" not in data
|
|
|
|
def test_create_user_duplicate_email(client: TestClient):
|
|
"""Test creating user with duplicate email"""
|
|
user_data = {
|
|
"email": "duplicate@example.com",
|
|
"username": "user1",
|
|
"password": "password123"
|
|
}
|
|
|
|
# Create first user
|
|
response1 = client.post("/api/v1/users/", json=user_data)
|
|
assert response1.status_code == 201
|
|
|
|
# Try to create second user with same email
|
|
user_data["username"] = "user2"
|
|
response2 = client.post("/api/v1/users/", json=user_data)
|
|
assert response2.status_code == 400
|
|
assert "already registered" in response2.json()["detail"]
|
|
|
|
def test_get_user_unauthorized(client: TestClient):
|
|
"""Test accessing protected endpoint without token"""
|
|
response = client.get("/api/v1/users/me")
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_user_authentication_flow(client: TestClient):
|
|
"""Test complete authentication flow"""
|
|
# Create user
|
|
user_data = {
|
|
"email": "auth@example.com",
|
|
"username": "authuser",
|
|
"password": "authpassword123"
|
|
}
|
|
|
|
create_response = client.post("/api/v1/users/", json=user_data)
|
|
assert create_response.status_code == 201
|
|
|
|
# Login
|
|
login_data = {
|
|
"email": user_data["email"],
|
|
"password": user_data["password"]
|
|
}
|
|
login_response = client.post("/api/v1/auth/login", json=login_data)
|
|
assert login_response.status_code == 200
|
|
|
|
token = login_response.json()["access_token"]
|
|
|
|
# Access protected endpoint
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
profile_response = client.get("/api/v1/users/me", headers=headers)
|
|
assert profile_response.status_code == 200
|
|
|
|
profile_data = profile_response.json()
|
|
assert profile_data["email"] == user_data["email"]
|
|
```
|
|
|
|
### 5. Deployment Configuration
|
|
|
|
**Docker Configuration**
|
|
```dockerfile
|
|
# Dockerfile (FastAPI)
|
|
FROM python:3.11-slim
|
|
|
|
WORKDIR /app
|
|
|
|
# Install system dependencies
|
|
RUN apt-get update && apt-get install -y \
|
|
gcc \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Install Python dependencies
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
# Copy application
|
|
COPY . .
|
|
|
|
# Create non-root user
|
|
RUN adduser --disabled-password --gecos '' appuser
|
|
RUN chown -R appuser:appuser /app
|
|
USER appuser
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
|
CMD curl -f http://localhost:8000/health || exit 1
|
|
|
|
EXPOSE 8000
|
|
|
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
```
|
|
|
|
**Docker Compose**
|
|
```yaml
|
|
# docker-compose.yml
|
|
version: '3.8'
|
|
|
|
services:
|
|
api:
|
|
build: .
|
|
ports:
|
|
- "8000:8000"
|
|
environment:
|
|
- DATABASE_URL=postgresql://postgres:password@db:5432/appdb
|
|
- REDIS_URL=redis://redis:6379
|
|
depends_on:
|
|
- db
|
|
- redis
|
|
volumes:
|
|
- ./app:/app/app
|
|
restart: unless-stopped
|
|
|
|
db:
|
|
image: postgres:15
|
|
environment:
|
|
- POSTGRES_DB=appdb
|
|
- POSTGRES_USER=postgres
|
|
- POSTGRES_PASSWORD=password
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
ports:
|
|
- "5432:5432"
|
|
restart: unless-stopped
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- "6379:6379"
|
|
volumes:
|
|
- redis_data:/data
|
|
restart: unless-stopped
|
|
|
|
nginx:
|
|
image: nginx:alpine
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
|
- ./ssl:/etc/nginx/ssl
|
|
depends_on:
|
|
- api
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
```
|
|
|
|
### 6. CI/CD Pipeline
|
|
|
|
**GitHub Actions Workflow**
|
|
```yaml
|
|
# .github/workflows/api.yml
|
|
name: API CI/CD
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: ${{ github.repository }}
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15
|
|
env:
|
|
POSTGRES_PASSWORD: postgres
|
|
POSTGRES_DB: test_db
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
ports:
|
|
- 5432:5432
|
|
|
|
redis:
|
|
image: redis:7
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
ports:
|
|
- 6379:6379
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install -r requirements-dev.txt
|
|
|
|
- name: Run linting
|
|
run: |
|
|
flake8 app tests
|
|
black --check app tests
|
|
isort --check-only app tests
|
|
|
|
- name: Run type checking
|
|
run: mypy app
|
|
|
|
- name: Run security scan
|
|
run: bandit -r app
|
|
|
|
- name: Run tests
|
|
env:
|
|
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
|
|
REDIS_URL: redis://localhost:6379
|
|
run: |
|
|
pytest tests/ -v --cov=app --cov-report=xml
|
|
|
|
- name: Upload coverage
|
|
uses: codecov/codecov-action@v3
|
|
with:
|
|
file: ./coverage.xml
|
|
|
|
build:
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'push'
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Log in to Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
tags: |
|
|
type=ref,event=branch
|
|
type=ref,event=pr
|
|
type=sha
|
|
|
|
- name: Build and push Docker image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
deploy:
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/main'
|
|
|
|
steps:
|
|
- name: Deploy to staging
|
|
run: |
|
|
echo "Deploying to staging environment"
|
|
# Add deployment commands here
|
|
```
|
|
|
|
### 7. Monitoring and Observability
|
|
|
|
**Prometheus Metrics**
|
|
```python
|
|
# app/core/metrics.py
|
|
from prometheus_client import Counter, Histogram, Gauge, generate_latest
|
|
from fastapi import Request
|
|
import time
|
|
|
|
# Metrics
|
|
REQUEST_COUNT = Counter(
|
|
'http_requests_total',
|
|
'Total HTTP requests',
|
|
['method', 'endpoint', 'status']
|
|
)
|
|
|
|
REQUEST_DURATION = Histogram(
|
|
'http_request_duration_seconds',
|
|
'HTTP request duration',
|
|
['method', 'endpoint']
|
|
)
|
|
|
|
ACTIVE_CONNECTIONS = Gauge(
|
|
'active_connections',
|
|
'Active connections'
|
|
)
|
|
|
|
async def record_metrics(request: Request, call_next):
|
|
"""Record metrics for each request"""
|
|
start_time = time.time()
|
|
|
|
ACTIVE_CONNECTIONS.inc()
|
|
|
|
try:
|
|
response = await call_next(request)
|
|
|
|
# Record metrics
|
|
REQUEST_COUNT.labels(
|
|
method=request.method,
|
|
endpoint=request.url.path,
|
|
status=response.status_code
|
|
).inc()
|
|
|
|
REQUEST_DURATION.labels(
|
|
method=request.method,
|
|
endpoint=request.url.path
|
|
).observe(time.time() - start_time)
|
|
|
|
return response
|
|
|
|
finally:
|
|
ACTIVE_CONNECTIONS.dec()
|
|
|
|
@app.get("/metrics")
|
|
async def metrics():
|
|
"""Prometheus metrics endpoint"""
|
|
return Response(
|
|
generate_latest(),
|
|
media_type="text/plain"
|
|
)
|
|
```
|
|
|
|
## Cross-Command Integration
|
|
|
|
This command integrates seamlessly with other Claude Code commands to create complete development workflows:
|
|
|
|
### 1. Complete API Development Workflow
|
|
|
|
**Standard Development Pipeline:**
|
|
```bash
|
|
# 1. Start with API scaffold
|
|
/api-scaffold "User management API with FastAPI, PostgreSQL, and JWT auth"
|
|
|
|
# 2. Set up comprehensive testing
|
|
/test-harness "FastAPI API with unit, integration, and load testing using pytest and locust"
|
|
|
|
# 3. Security validation
|
|
/security-scan "FastAPI application with authentication endpoints"
|
|
|
|
# 4. Container optimization
|
|
/docker-optimize "FastAPI application with PostgreSQL and Redis dependencies"
|
|
|
|
# 5. Kubernetes deployment
|
|
/k8s-manifest "FastAPI microservice with PostgreSQL, Redis, and ingress"
|
|
|
|
# 6. Frontend integration (if needed)
|
|
/frontend-optimize "React application connecting to FastAPI backend"
|
|
```
|
|
|
|
### 2. Database-First Development
|
|
|
|
**When starting with existing data:**
|
|
```bash
|
|
# 1. Handle database migrations first
|
|
/db-migrate "PostgreSQL schema migration from legacy system to modern structure"
|
|
|
|
# 2. Generate API based on migrated schema
|
|
/api-scaffold "REST API for migrated PostgreSQL schema with auto-generated models"
|
|
|
|
# 3. Continue with standard pipeline...
|
|
```
|
|
|
|
### 3. Microservices Architecture
|
|
|
|
**For distributed systems:**
|
|
```bash
|
|
# Generate multiple related APIs
|
|
/api-scaffold "User service with authentication and profile management"
|
|
/api-scaffold "Order service with payment processing and inventory"
|
|
/api-scaffold "Notification service with email and push notifications"
|
|
|
|
# Containerize all services
|
|
/docker-optimize "Microservices architecture with service discovery"
|
|
|
|
# Deploy as distributed system
|
|
/k8s-manifest "Microservices deployment with service mesh and monitoring"
|
|
```
|
|
|
|
### 4. Integration with Generated Code
|
|
|
|
**Test Integration Setup:**
|
|
```yaml
|
|
# After running /api-scaffold, use this with /test-harness
|
|
test_config:
|
|
api_base_url: "http://localhost:8000"
|
|
test_database: "postgresql://test:test@localhost:5432/test_db"
|
|
authentication:
|
|
test_user: "test@example.com"
|
|
test_password: "testpassword123"
|
|
|
|
endpoints_to_test:
|
|
- POST /api/v1/users/
|
|
- POST /api/v1/auth/login
|
|
- GET /api/v1/users/me
|
|
- GET /api/v1/users/{id}
|
|
```
|
|
|
|
**Security Scan Configuration:**
|
|
```yaml
|
|
# Configuration for /security-scan after API scaffold
|
|
security_scan:
|
|
target: "localhost:8000"
|
|
authentication_endpoints:
|
|
- "/api/v1/auth/login"
|
|
- "/api/v1/auth/refresh"
|
|
protected_endpoints:
|
|
- "/api/v1/users/me"
|
|
- "/api/v1/users/{id}"
|
|
vulnerability_tests:
|
|
- jwt_token_validation
|
|
- sql_injection
|
|
- xss_prevention
|
|
- rate_limiting
|
|
```
|
|
|
|
**Docker Integration:**
|
|
```dockerfile
|
|
# Generated Dockerfile can be optimized with /docker-optimize
|
|
# Multi-stage build for FastAPI application
|
|
FROM python:3.11-slim as builder
|
|
WORKDIR /app
|
|
COPY requirements.txt .
|
|
RUN pip install --user -r requirements.txt
|
|
|
|
FROM python:3.11-slim as runtime
|
|
WORKDIR /app
|
|
COPY --from=builder /root/.local /root/.local
|
|
COPY . .
|
|
ENV PATH=/root/.local/bin:$PATH
|
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
```
|
|
|
|
**Kubernetes Deployment:**
|
|
```yaml
|
|
# Use this configuration with /k8s-manifest
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: api-deployment
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: api
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: api
|
|
spec:
|
|
containers:
|
|
- name: api
|
|
image: api:latest
|
|
ports:
|
|
- containerPort: 8000
|
|
env:
|
|
- name: DATABASE_URL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-secrets
|
|
key: database-url
|
|
- name: JWT_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-secrets
|
|
key: jwt-secret
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 8000
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /ready
|
|
port: 8000
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
```
|
|
|
|
### 5. CI/CD Pipeline Integration
|
|
|
|
**Complete pipeline using multiple commands:**
|
|
```yaml
|
|
name: Full Stack CI/CD
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
api-test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Test API (generated by /api-scaffold)
|
|
- name: Run API tests
|
|
run: |
|
|
# Use test configuration from /test-harness
|
|
pytest tests/ -v --cov=app
|
|
|
|
# Security scan (from /security-scan)
|
|
- name: Security scan
|
|
run: |
|
|
bandit -r app/
|
|
safety check
|
|
|
|
# Build optimized container (from /docker-optimize)
|
|
- name: Build container
|
|
run: |
|
|
docker build -f Dockerfile.optimized -t api:${{ github.sha }} .
|
|
|
|
# Deploy to Kubernetes (from /k8s-manifest)
|
|
- name: Deploy to staging
|
|
run: |
|
|
kubectl apply -f k8s/staging/
|
|
kubectl set image deployment/api-deployment api=api:${{ github.sha }}
|
|
```
|
|
|
|
### 6. Frontend-Backend Integration
|
|
|
|
**When building full-stack applications:**
|
|
```bash
|
|
# 1. Backend API
|
|
/api-scaffold "REST API with user management and data operations"
|
|
|
|
# 2. Frontend application
|
|
/frontend-optimize "React SPA with API integration, authentication, and state management"
|
|
|
|
# 3. Integration testing
|
|
/test-harness "End-to-end testing for React frontend and FastAPI backend"
|
|
|
|
# 4. Unified deployment
|
|
/k8s-manifest "Full-stack deployment with API, frontend, and database"
|
|
```
|
|
|
|
**Frontend API Integration Code:**
|
|
```typescript
|
|
// Generated API client for frontend
|
|
// Use this pattern with /frontend-optimize
|
|
export class APIClient {
|
|
private baseURL: string;
|
|
private token: string | null = null;
|
|
|
|
constructor(baseURL: string) {
|
|
this.baseURL = baseURL;
|
|
}
|
|
|
|
setAuthToken(token: string) {
|
|
this.token = token;
|
|
}
|
|
|
|
private async request<T>(
|
|
endpoint: string,
|
|
options: RequestInit = {}
|
|
): Promise<T> {
|
|
const url = `${this.baseURL}${endpoint}`;
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
...(this.token && { Authorization: `Bearer ${this.token}` }),
|
|
...options.headers,
|
|
};
|
|
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API Error: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
// User management methods (matching API scaffold)
|
|
async createUser(userData: CreateUserRequest): Promise<User> {
|
|
return this.request<User>('/api/v1/users/', {
|
|
method: 'POST',
|
|
body: JSON.stringify(userData),
|
|
});
|
|
}
|
|
|
|
async login(credentials: LoginRequest): Promise<AuthResponse> {
|
|
return this.request<AuthResponse>('/api/v1/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify(credentials),
|
|
});
|
|
}
|
|
|
|
async getCurrentUser(): Promise<User> {
|
|
return this.request<User>('/api/v1/users/me');
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7. Monitoring and Observability Integration
|
|
|
|
**Complete observability stack:**
|
|
```bash
|
|
# After API deployment, add monitoring
|
|
/api-scaffold "Monitoring endpoints with Prometheus metrics and health checks"
|
|
|
|
# Use with Kubernetes monitoring
|
|
/k8s-manifest "Kubernetes deployment with Prometheus, Grafana, and alerting"
|
|
```
|
|
|
|
This integrated approach ensures all components work together seamlessly, creating a production-ready system with proper testing, security, deployment, and monitoring.
|
|
|
|
## Validation Checklist
|
|
|
|
- [ ] Framework selected based on requirements
|
|
- [ ] Project structure follows best practices
|
|
- [ ] Authentication and authorization implemented
|
|
- [ ] Input validation and sanitization in place
|
|
- [ ] Rate limiting configured
|
|
- [ ] Error handling comprehensive
|
|
- [ ] Logging and monitoring setup
|
|
- [ ] Tests written and passing
|
|
- [ ] Security measures implemented
|
|
- [ ] API documentation generated
|
|
- [ ] Deployment configuration ready
|
|
- [ ] CI/CD pipeline configured
|
|
|
|
Focus on creating production-ready APIs with proper architecture, security, testing, and operational concerns addressed from the start.
|