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.
79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
from sqlalchemy import BigInteger, Boolean, Index, Integer, Text, text
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
_NOW_MS_DEFAULT = text("(EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT")
|
|
|
|
|
|
class EventConfig(Base):
|
|
__tablename__ = "event_config"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, server_default=text("1"))
|
|
couple_names: Mapped[str] = mapped_column(Text, nullable=False)
|
|
event_date: Mapped[str | None] = mapped_column(Text)
|
|
cover_key: Mapped[str | None] = mapped_column(Text)
|
|
gallery_visibility: Mapped[str] = mapped_column(
|
|
Text, nullable=False, server_default=text("'public'")
|
|
)
|
|
moderation: Mapped[str] = mapped_column(
|
|
Text, nullable=False, server_default=text("'post'")
|
|
)
|
|
max_file_mb: Mapped[int] = mapped_column(
|
|
Integer, nullable=False, server_default=text("500")
|
|
)
|
|
allow_video: Mapped[bool] = mapped_column(
|
|
Boolean, nullable=False, server_default=text("TRUE")
|
|
)
|
|
max_video_seconds: Mapped[int] = mapped_column(
|
|
Integer, nullable=False, server_default=text("300")
|
|
)
|
|
welcome_message: Mapped[str | None] = mapped_column(Text)
|
|
updated_at: Mapped[int] = mapped_column(
|
|
BigInteger, nullable=False, server_default=_NOW_MS_DEFAULT
|
|
)
|
|
|
|
|
|
class Upload(Base):
|
|
__tablename__ = "uploads"
|
|
__table_args__ = (
|
|
Index("idx_uploads_status_created", "status", "created_at"),
|
|
Index("idx_uploads_source", "source"),
|
|
)
|
|
|
|
id: Mapped[str] = mapped_column(Text, primary_key=True)
|
|
storage_key: Mapped[str] = mapped_column(Text, nullable=False)
|
|
thumbnail_key: Mapped[str | None] = mapped_column(Text)
|
|
mime_type: Mapped[str] = mapped_column(Text, nullable=False)
|
|
size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
duration_seconds: Mapped[int | None] = mapped_column(Integer)
|
|
author_name: Mapped[str | None] = mapped_column(Text)
|
|
message: Mapped[str | None] = mapped_column(Text)
|
|
status: Mapped[str] = mapped_column(
|
|
Text, nullable=False, server_default=text("'approved'")
|
|
)
|
|
source: Mapped[str] = mapped_column(
|
|
Text, nullable=False, server_default=text("'guest'")
|
|
)
|
|
ip_hash: Mapped[str | None] = mapped_column(Text)
|
|
provider_upload_id: Mapped[str | None] = mapped_column(Text)
|
|
created_at: Mapped[int] = mapped_column(
|
|
BigInteger, nullable=False, server_default=_NOW_MS_DEFAULT
|
|
)
|
|
approved_at: Mapped[int | None] = mapped_column(BigInteger)
|
|
|
|
|
|
class AuditLog(Base):
|
|
__tablename__ = "audit_log"
|
|
|
|
id: Mapped[str] = mapped_column(Text, primary_key=True)
|
|
action: Mapped[str] = mapped_column(Text, nullable=False)
|
|
actor: Mapped[str | None] = mapped_column(Text)
|
|
payload: Mapped[str | None] = mapped_column(Text)
|
|
created_at: Mapped[int] = mapped_column(
|
|
BigInteger, nullable=False, server_default=_NOW_MS_DEFAULT
|
|
)
|