초기 커밋

This commit is contained in:
2026-03-01 07:44:19 +09:00
commit 09359f30be
146 changed files with 6120 additions and 0 deletions

View File

16
app/middleware/cors.py Normal file
View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.core.config import settings
def add_cors_middleware(app: FastAPI) -> None:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

View File

@@ -0,0 +1,37 @@
from __future__ import annotations
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from app.db.redis import get_redis
class RateLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, max_requests: int = 100, window_seconds: int = 60): # type: ignore[no-untyped-def]
super().__init__(app)
self.max_requests = max_requests
self.window_seconds = window_seconds
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
if request.url.path.startswith("/docs") or request.url.path.startswith("/redoc"):
return await call_next(request)
client_ip = request.client.host if request.client else "unknown"
key = f"rate_limit:{client_ip}"
try:
redis = get_redis()
current = await redis.incr(key)
if current == 1:
await redis.expire(key, self.window_seconds)
if current > self.max_requests:
return JSONResponse(
status_code=429,
content={"detail": "Too many requests"},
)
except Exception:
pass
return await call_next(request)

View File

@@ -0,0 +1,17 @@
from __future__ import annotations
import uuid
import structlog
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
class RequestIDMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
structlog.contextvars.bind_contextvars(request_id=request_id)
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response

View File

@@ -0,0 +1,26 @@
from __future__ import annotations
import time
import structlog
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
logger = structlog.get_logger("request")
class RequestLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
start = time.perf_counter()
response = await call_next(request)
elapsed_ms = round((time.perf_counter() - start) * 1000, 2)
logger.info(
"request",
method=request.method,
path=request.url.path,
status=response.status_code,
elapsed_ms=elapsed_ms,
)
return response