Commit Graph

7 Commits

Author SHA1 Message Date
98dcafdf26 feat(ci): deploy automático via Gitea Actions + rename branch para main
Some checks failed
Deploy / deploy (push) Failing after 1s
- .gitea/workflows/deploy.yml: push na main (paths do app/infra) ou
  disparo manual -> SSH no host -> make deploy + health check /api/health
- Makefile: alvo `deploy` (git reset --hard + up-wedding com build), BRANCH=main
- CLAUDE.md: documenta o fluxo de deploy e o setup de secrets

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 06:19:31 -03:00
Claude
69ef304562
docs: add CLAUDE.md with full project conventions and decisions
Single source of truth pro Claude Code (e devs humanos) sobre:
- Arquitetura (3 stacks Docker compartilhando infra-net)
- Stack técnica e rationale por escolha
- Estrutura completa de diretórios
- Roteamento, hostnames, hairpin DNS
- Comandos make (root + wedding)
- Convenções (env vars, IDs, camelCase, timestamps bigint, SQL puro)
- Fluxos importantes (upload single/multipart, HEIC transcoding,
  login admin, bulk ops, backup, Gitea bootstrap, troca de senha)
- Gotchas conhecidos (Postgres init 1ª vez, docker exec -it com
  heredoc, Gitea reserved names, imagem regular vs rootless,
  TTL local em *.localhost, firewall em prod, ACME_EMAIL)
- Histórico de iterações (Cloudflare -> Docker Node -> Python)
- Onde olhar pra estender cada coisa
- Decisões deferidas com estimativa de esforço
2026-06-09 08:34:42 +00:00
Claude
862dc370f7
fix(gitea): use /data volume path for the non-rootless image
The 1.22 (non-rootless) image expects everything under /data
(/data/gitea/conf/app.ini, /data/git/repositories, etc.). I'd
configured the rootless paths (/var/lib/gitea + /etc/gitea), so
the app.ini that gitea writes on first boot landed in the
container's ephemeral fs instead of the host volume. Result:
'docker exec gitea gitea admin user create' could not find the
config and bailed with 'Unable to load config file for a installed
Gitea instance'.

Also set GITEA__security__INSTALL_LOCK=true so the first boot
bypasses the /install web wizard since every required field is
already provided via GITEA__* env vars.

Migration for an existing broken install:
  make down-gitea
  sudo rm -rf infra/gitea/gitea/data infra/gitea/gitea/config
  make up-gitea
2026-06-09 00:54:48 +00:00
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
Claude
f882f8a1c8
feat: HEIC->JPEG transcoding and Postgres + MinIO backup sidecars
HEIC handling
- pillow-heif joins the deps; app/lib/transcode.py registers the
  HEIF opener and exposes heic_to_jpeg(bytes, quality=88) with EXIF
  rotation applied so portrait iPhone photos do not show sideways.
- Storage gains get_bytes / put_bytes so the server can read the
  uploaded HEIC out of MinIO, decode it, and put a JPEG back.
- /api/uploads/{id}/confirm now runs the transcode after the HEAD
  check passes when mime_type is image/heic or image/heif: writes
  the JPEG under a sibling key, deletes the HEIC, and updates the
  upload row's storage_key / mime_type / size_bytes. Failures fall
  back to keeping the HEIC so an upload is never lost in transit;
  the admin can re-upload if a particular file is unrenderable.

Backups (two sidecars in docker-compose.yml)
- postgres-backup uses prodrigestivill/postgres-backup-local:16-alpine
  with BACKUP_SCHEDULE_DB (default @daily) and layered retention
  (days/weeks/months) writing to ./backups/postgres on the host.
- media-backup is an alpine container that pulls mc on boot, sets up
  an alias for the in-cluster MinIO, optionally adds a remote alias
  (R2/B2/S3 via BACKUP_REMOTE_*), and runs mc mirror on a configurable
  cron schedule. Local mirror always; remote mirror only when
  BACKUP_REMOTE_* are set.
- Both write to ./backups/ on the host (bind mount), so the operator
  can rsync the directory off-box without touching containers.
- .env.example documents every new variable, including a R2 example
  for the remote target, and TZ for cron alignment.

Local backups directory is .gitignore'd so accidental commits do not
ship someone else's wedding photos to GitHub.
2026-06-07 22:50:29 +00:00
Claude
2fa17e5feb
feat: rewrite backend in Python (FastAPI + SQLAlchemy + boto3)
Same routes, same Docker topology, same env vars. Frontend untouched.

Backend swap:
- Hono on Node -> FastAPI on Python 3.12, served by uvicorn behind Caddy.
- Drizzle on Node -> SQLAlchemy 2.0 async (asyncpg driver) with declarative
  models that mirror the previous schema 1:1. Migrations are still plain
  SQL files (apps/api/app/migrations/) run idempotently at startup by
  app/db/migrate.py via asyncpg, tracking each file's sha256 in a
  schema_migrations table.
- aws4fetch -> boto3 wrapped in asyncio.to_thread so the async routes
  do not block on MinIO/S3 calls. The presign_put / init_multipart /
  complete_multipart / head / delete contract is unchanged so the React
  client sees the same /api/uploads/* payloads.
- Cookie+JWT admin auth -> pyjwt + FastAPI Cookie() dependency.
  constant-time password compare. Same 7-day HttpOnly cookie shape.
- QR code: qrcode lib + reportlab. /api/qrcode keeps format=png|svg|pdf;
  the PDF is still A6 with the couple's names + 'Aponte a câmera' CTA.
- Pydantic-settings replaces the zod env loader and still fails fast
  on missing required vars.

Routes ported with identical paths and JSON shapes:
- public: /api/event, /api/gallery, /api/qrcode, /api/stats
- uploads: /api/uploads/init, /{id}/confirm, /{id}/abort
- admin: /login, /logout, /me, /event (GET+PATCH), /uploads (GET with
  ?status, ?kind=photo|video, ?q= for search), /uploads/{id} (PATCH
  for author/message edit), /uploads/{id}/{approve,reject,cover},
  /uploads/{id} (DELETE), /event/cover (DELETE), /uploads/bulk, /stats.

Build pipeline:
- Dockerfile: stage 1 builds the React/Vite SPA with pnpm; stage 2 is
  python:3.12-slim that installs deps via uv (fast, reproducible),
  copies the app/, and copies the SPA dist from stage 1 into /app/public
  so the FastAPI process serves both /api/* and the SPA assets.
- docker-compose.yml unchanged: postgres + minio + minio-init + app +
  caddy. Same env vars (DATABASE_URL, S3_*, ADMIN_PASSWORD, SESSION_SECRET,
  ALLOWED_ADMIN_EMAILS, AUTO_MIGRATE).
- Removed apps/api from the pnpm workspace; root package.json now only
  scripts the web. Makefile centralizes dev/build/docker commands so
  the Python side has the same DX as the Node side did.
2026-06-07 22:45:20 +00:00
Claude
00baf9bd89
feat: full Docker stack on a VPS with improved admin editing
Self-hosted rewrite of the wedding photo app: Node + Hono replaces
Cloudflare Workers, Postgres 16 replaces D1, MinIO replaces R2,
Caddy fronts the stack with automatic Let's Encrypt TLS. Same routes
and feature set as before; storage abstraction is the same S3 client
so MinIO drops in without code changes.

Architecture:
- docker-compose.yml: postgres, minio, minio-init (creates bucket +
  anonymous read + CORS), app, caddy (reverse proxy + media subdomain).
- Dockerfile: multi-stage pnpm build, single runtime image serving
  the API and the SPA dist as static assets from one process.
- Caddyfile: primary domain proxies to app; media subdomain proxies
  to MinIO so guests upload directly and signatures match Host.
- app: tsx runtime, runs SQL migrations idempotently at startup via
  a schema_migrations(name, sha256, applied_at) table.

Admin upgrades requested:
- PATCH /api/admin/uploads/:id to edit author/message.
- POST /api/admin/uploads/bulk for bulk approve/reject/delete.
- POST /api/admin/uploads/:id/cover and DELETE /api/admin/event/cover
  to set/clear a featured image (rendered on Home when set).
- GET /api/admin/uploads gains ?q= text search across author and
  message and ?kind=photo|video filter.
- Dashboard: bulk select checkboxes with a toolbar, edit modal that
  rewrites author and message, search input, kind filter, set-cover
  button per item, cover preview + clear in the event card.

Singletons replace the per-request bindings pattern: initDb() and
initStorage() run once in server.ts; routes call getDb()/getStorage()
directly rather than threading env.DB / env.MEDIA through.

env.ts uses zod to parse process.env and fails fast if anything
mandatory is missing. .env.example documents every variable and
flags the hairpin tradeoff for MinIO access from the app container.
2026-06-07 22:11:10 +00:00