feat: production docker + documentation

- Backend Dockerfile: multi-stage build with venv, gunicorn+uvicorn workers, entrypoint runs alembic then gunicorn
- Frontend Dockerfile: multi-stage with npm ci, nginx:1.27-alpine runtime
- nginx.conf: gzip compression, security headers (X-Frame-Options, X-Content-Type-Options, etc.), static asset caching, correct API proxy preserving /api/ prefix
- docker-compose.yml: production config — db healthcheck, backend healthcheck, frontend depends_on backend healthy, no exposed backend port
- docker-compose.override.yml: dev hot-reload — uvicorn --reload with source mount, npm run dev on port 5173
- Rate limiting: slowapi middleware on /auth/login (10/min) and /auth/register (5/min)
- README.md: full documentation with architecture, quick start, API table, dev/prod instructions, tests
- .env.example: all variables documented with comments
- .gitignore: extended with *.pyc, *.cover, .ruff_cache, frontend/.vite, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nox (OpenClaw)
2026-03-17 17:07:38 +00:00
parent e3fac99045
commit 434de9aa3e
13 changed files with 315 additions and 60 deletions
+6 -1
View File
@@ -1,13 +1,14 @@
import uuid
from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_user
from app.limiter import limiter
from app.auth.security import (
create_access_token,
create_refresh_token,
@@ -28,7 +29,9 @@ router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
@limiter.limit("5/minute")
async def register(
request: Request,
user_data: UserCreate,
session: AsyncSession = Depends(get_session),
) -> UserResponse:
@@ -54,7 +57,9 @@ async def register(
@router.post("/login", response_model=Token)
@limiter.limit("10/minute")
async def login(
request: Request,
form_data: OAuth2PasswordRequestForm = Depends(),
session: AsyncSession = Depends(get_session),
) -> Token:
+4
View File
@@ -0,0 +1,4 @@
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
+8
View File
@@ -3,10 +3,14 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from app.auth.router import router as auth_router
from app.config import settings
from app.database import engine
from app.limiter import limiter
from app.routers.budgets import router as budgets_router
from app.routers.categories import router as categories_router
from app.routers.dashboard import router as dashboard_router
@@ -28,6 +32,10 @@ def create_app() -> FastAPI:
lifespan=lifespan,
)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,