This repository has been archived on 2026-06-09. You can view files and clone it, but cannot push or open issues or pull requests.
wedding-app/infra/wedding_photo/apps/api/app/main.py
Claude 5a19753013
feat: split into 3 docker-compose stacks (main / gitea / wedding_photo)
Repo passa a viver dentro de infra/, com três stacks isoladas que
compartilham uma network Docker externa ('infra-net'):

main/        infra base: postgres + redis + minio + minio-init + pgadmin
             + caddy. Postgres roda em modo multi-banco; o init script
             cria os DBs 'wedding' e 'gitea' com roles dedicadas. MinIO
             tem um bucket inicial criado pelo minio-init com anonymous
             download + CORS. Caddy é o único container expondo 80/443
             e roteia por hostname: gitea.{DOMAIN_BASE} / wedding.* /
             pgadmin.* / minio.* / media.* (rewrite de bucket).

gitea/       gitea + act_runner. Gitea liga no postgres compartilhado e
             usa redis pra cache+sessões. O runner ganha um Dockerfile
             pequeno que adiciona docker CLI por cima do
             gitea/act_runner pra workflows poderem chamar 'docker
             build'. Bootstrap do token de runner documentado no
             .env.example.

wedding_photo/  Só a aplicação: 'wedding_app' (FastAPI + SPA) +
             postgres-backup + media-backup. Os bancos e o MinIO vêm
             da stack main/. A app usa extra_hosts: host-gateway pra
             alcançar media.{DOMAIN_BASE} via Caddy mesmo em dev local
             — assim a assinatura S3 fecha com o host que o browser
             usa pra fazer PUT.

Orquestração:
- Makefile no root: 'make up' sobe tudo na ordem (main -> gitea ->
  wedding_photo). 'make up-{main,gitea,wedding}' pra controle
  granular. 'make logs-*', 'make down', 'make status', 'make pull-*'.
- network.sh cria a 'infra-net' antes de qualquer up; idempotente.
- Cada stack tem seu próprio .env.example. As creds compartilhadas
  (DOMAIN_BASE, MINIO_ROOT_*, WEDDING_DB_*) precisam casar entre
  main/.env e o consumidor (gitea/.env ou wedding_photo/.env).
- .gitignore ignora todas as pastas data/ dos volumes.
2026-06-08 23:34:14 +00:00

72 lines
2.0 KiB
Python

import time
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from .config import settings
from .db.base import engine
from .db.migrate import run_migrations
from .routes import admin, public, uploads
@asynccontextmanager
async def lifespan(app: FastAPI):
if settings.AUTO_MIGRATE:
print("[startup] running migrations")
await run_migrations(settings.DATABASE_URL)
print(f"[startup] static dir: {settings.STATIC_DIR}")
yield
await engine.dispose()
app = FastAPI(
lifespan=lifespan,
title="Wedding Photos API",
openapi_url="/api/openapi.json",
docs_url="/api/docs",
redoc_url=None,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/api/health")
async def health():
return {"ok": True, "ts": int(time.time() * 1000)}
app.include_router(public.router, prefix="/api", tags=["public"])
app.include_router(uploads.router, prefix="/api/uploads", tags=["uploads"])
app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
# ----- SPA static asset serving -----
static_dir = Path(settings.STATIC_DIR)
assets_dir = static_dir / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
@app.get("/{full_path:path}", include_in_schema=False)
async def spa_fallback(full_path: str):
if full_path.startswith("api/") or full_path == "api":
raise HTTPException(404, detail="not_found")
if full_path in {"favicon.ico", "robots.txt"}:
candidate = static_dir / full_path
if candidate.is_file():
return FileResponse(candidate)
index = static_dir / "index.html"
if index.is_file():
return FileResponse(index)
raise HTTPException(404, detail="static_not_found")