feat: Projektfundament mit Datenmodell, Migrationen und Seed

- Repo-Struktur für Backend, Frontend, Dokumentation und Gitea-Workflows
- FastAPI-Skeleton mit pydantic-settings und RFC-7807-artigem Fehlerformat
- Vollständiges Datenmodell (15 Tabellen) als SQLAlchemy-2.0-Modelle
- Alembic gegen die Async-Engine inklusive Erstmigration
- Idempotenter Seed für den deutschen Kategoriebaum und Standardregeln
- Endpunkte /api/health und /api/version
- pytest-Infrastruktur mit transaktionsisolierten Fixtures

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
This commit is contained in:
moneyfy
2026-09-09 13:12:08 +02:00
co-authored by Claude Opus 5
commit 0b06775be3
40 changed files with 2236 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
"""Deklarative Basisklasse und gemeinsame Spaltentypen."""
from datetime import UTC, datetime
from decimal import Decimal
from sqlalchemy import DateTime, MetaData, Numeric
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
# Feste Namenskonventionen, damit Alembic stabile Constraint-Namen erzeugt.
NAMING_CONVENTION = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
# Alle Geldbeträge einheitlich als NUMERIC(12,2) -> Decimal.
Money = Numeric(12, 2, asdecimal=True)
def utcnow() -> datetime:
"""Zeitstempel in UTC die Anzeige rechnet nach Europe/Berlin um."""
return datetime.now(UTC)
class Base(DeclarativeBase):
metadata = MetaData(naming_convention=NAMING_CONVENTION)
type_annotation_map = { # noqa: RUF012
Decimal: Money,
}
class TimestampMixin:
"""created_at/updated_at für Tabellen mit Änderungsverfolgung."""
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class CreatedAtMixin:
"""Nur created_at für rein additive Tabellen."""
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)