Files
marcandClaude Opus 5 471679ff64 refactor(repo): deploy/ heißt jetzt build/ – der Name sagte das Gegenteil
"deploy" klang nach Ausrollen, drin steht das Gegenteil: das Bau-Rezept
und das Compose, das aus dem Quelltext baut. Ausgerollt wird mit
docker-compose.example.yml im Wurzelverzeichnis. Marc ist genau darüber
gestolpert.

Zwei Dinge mussten dafür vorher fest werden, sonst hätte das Umbenennen
die Entwicklungsdatenbank gekostet: Compose leitet den Projektnamen aus
dem Ordnernamen ab und hätte unter "build" ein neues, leeres Projekt mit
neuem, leerem Volume angelegt. Der Projektname steht deshalb jetzt im
Compose (name: aether), und das Volume trägt seinen alten Namen
ausdrücklich weiter (deploy_postgres_data) – unschön, aber die Daten
bleiben.

Ebenfalls angepasst: .gitignore. Dort stand deploy/.env; nach dem
Umbenennen wäre build/.env nur noch von der allgemeinen .env-Zeile
gedeckt gewesen.

Nachgeprüft nach dem Umzug: Abbild baut, Stapel startet, 113 Dateien in
der Datenbank wie vorher, Volume unverändert deploy_postgres_data,
GET / → 200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 09:24:09 +02:00

7.9 KiB
Raw Permalink Blame History

Aether – Projektkonventionen für Claude Code

Projekt-Überblick

Self-hosted Cloud-Alternative zu Nextcloud. Go-Backend + React-Frontend, modularer Monolith. Projektplan: cloud-projekt-plan.md

Monorepo-Struktur

aether/
├── backend/          # Go – modularer Monolith
│   ├── cmd/aether/   # main.go – Einstiegspunkt
│   ├── internal/     # Domänen-Pakete (nie von außen importieren)
│   │   ├── auth/         # Login, Sessions, CSRF, Argon2id, OIDC-User-Anlage
│   │   ├── storage/      # Storage-Interface + LocalFS (atomar, SHA-256)
│   │   ├── files/        # Datei-/Ordner-CRUD, Trash, Versionen, Favoriten
│   │   ├── sharing/      # Öffentliche Share-Links
│   │   ├── admin/        # Admin-Panel: User, Gruppen(-mitgliedschaft), Settings/Theming
│   │   ├── oidc/         # OIDC-Login (optional, via .env)
│   │   ├── avatar/       # Profilbild: zuschneiden, ablegen, ausliefern
│   │   ├── webdav/       # WebDAV-Endpoint /dav/ (Basic Auth – nur App-Passwörter)
│   │   ├── acl/          # Interne Freigaben an User/Gruppen (Rechteauflösung)
│   │   ├── audit/        # Audit-Log (Schreibpfad; Endpunkte liegen in admin/)
│   │   ├── gc/           # Blob-GC: Platte gegen DB abgleichen
│   │   ├── thumbnails/   # Vorschaubild-Generierung
│   │   ├── groups/       # LEERER STUB – Gruppenordner liegen in files/groupfolder.go
│   │   ├── documents/    # Volltext: OCR-Warteschlange + Arbeiter (Phase 6, Teil 1)
│   │   ├── tags/         # Ordnungsmerkmale einer Datei: Etiketten, Belegdatum, Absender
│   │   └── office/       # WOPI-Host: Collabora/OnlyOffice, ein Adapter fuer beide
│   ├── db/migrations/    # SQL-Migrations – Spiegelkopie (siehe Migrations-Regeln!)
│   └── pkg/              # Hilfspakete: config, middleware, response, db (embed migrations)
├── frontend/         # React + TS + Vite + Tailwind
│   └── src/
│       ├── components/
│       ├── pages/
│       ├── api/          # Typed API-Client
│       └── hooks/
├── build/            # Dockerfile + Compose zum Bauen aus dem Quelltext
└── CLAUDE.md

Backend-Konventionen (Go)

  • Module-Pfad: aether
  • Go-Version: 1.25 (siehe go.mod)
  • Router: github.com/go-chi/chi/v5
  • DB-Treiber: github.com/jackc/pgx/v5 (pgxpool für Connection-Pool)
  • Migrations: github.com/golang-migrate/migrate/v4
  • Logging: github.com/rs/zerolog – strukturiert, immer mit Kontext (log.Ctx(ctx))
  • Hashing: golang.org/x/crypto/argon2 (Argon2id) – App-Passwörter dagegen SHA-256 (zufälliges Token, jeder WebDAV-Request authentifiziert einzeln)
  • Sessions: github.com/gorilla/sessions mit securecookie

Paket-Regeln

  • Alle Domänen-Pakete unter internal/ – kein Import von außen
  • Keine zirkulären Abhängigkeiten; Dependency-Richtung: cmd → internal/* → pkg/*
  • Handler in internal/<domain>/handler.go, Business-Logic in internal/<domain>/service.go, DB in internal/<domain>/store.go
  • Interfaces für alle externen Abhängigkeiten (Storage, DB) → leicht mockbar

Code-Stil

  • gofmt + goimports – immer formatiert
  • Fehler nie ignorieren (_), immer wrappen mit fmt.Errorf("context: %w", err)
  • Keine Magic-Numbers – Konstanten mit sprechenden Namen
  • HTTP-Handler signieren: func (h *Handler) MethodName(w http.ResponseWriter, r *http.Request)
  • Middleware-Chain: Auth → CSRF → RateLimit → Handler

Tests

  • Testdatei neben der Quelldatei: auth_test.go neben auth.go
  • Integrationstests mit echtem Postgres via testcontainers-go
  • Pflicht-Tests: Path-Traversal, Upload-Abbruch, Checksum-Verifikation, Soft-Delete-Recovery
  • go test ./... muss grün sein vor jedem Commit

Frontend-Konventionen (React + TS)

  • Bundler: Vite
  • Styling: Tailwind CSS + CSS-Variablen für Design-Tokens
  • Testing: Vitest + Testing Library; E2E: Playwright
  • API-Client: typed fetch-Wrapper unter src/api/

Design-Tokens (CSS-Variablen)

Alle Farben laufen über CSS-Custom-Properties:

:root {
  --color-primary: ...;
  --color-accent: ...;
  --color-background: ...;
  --color-surface: ...;
  --color-text: ...;
}

Admin setzt globale Tokens; User-Override nur für eigene Palette (Scope: [data-user-theme]).

Komponenten-Regeln

  • Komponenten in PascalCase.tsx
  • Kein dangerouslySetInnerHTML mit User-Input (XSS)
  • API-Calls nur über src/api/-Funktionen, nie direkt fetch in Komponenten

Sicherheits-Pflichten

  • Path-Traversal: jeden Dateipfad kanonisieren und gegen Storage-Root prüfen
  • CSRF: Double-Submit-Cookie oder synchronizer-Token
  • Rate-Limiting: Login-Endpunkt + Admin-Login strenger
  • Auth-Check: jede Route serverseitig prüfen – nie Frontend vertrauen
  • SQL: ausschließlich parametrisierte Queries (pgx unterstützt das nativ)
  • Uploads: außerhalb Webroot, keine ausführbaren Rechte, MIME-Prüfung
  • WebDAV: nur App-Passwörter, nie das Kontopasswort – es ist zugleich der Admin-Zugang
  • Kein Zugriff auf fremde IDs → 404, nicht 403: sonst verrät die Antwort die Existenz
  • Listen und Suche schneiden den erreichbaren Bereich in der Abfrage zu, nie nachträglich im Handler
  • Hochgeladene Dateien nur für eine Positivliste harmloser Typen inline ausliefern (sonst läuft fremdes HTML im eigenen Ursprung)

Datenintegrität (nicht verhandelbar)

  1. Atomares Schreiben: Temp-Datei → fsync → rename. Niemals in-place.
  2. SHA-256: beim Upload berechnen + in DB; beim Download verifizieren.
  3. Soft-Delete only: kein hartes Löschen. Bytes erst per GC entfernen.
  4. DB-Commit nach Blob-Write: Metadaten erst committen, wenn Blob erfolgreich.

Migrations-Regeln

  • Dateien: 000001_init.up.sql / 000001_init.down.sql
  • Jede Migration in BEIDE Verzeichnisse legen: backend/pkg/db/migrations/ (wird ins Binary embedded und beim Start ausgeführt) UND backend/db/migrations/ (Spiegelkopie). Beide müssen identisch sein.
  • Keine destruktiven Änderungen ohne explizites Down-Migration
  • Migration beim App-Start automatisch ausführen (via migrate.Up())
  • Aktueller Stand: bis 000019_user_avatar (Profilbild eines Kontos)

Commit-Regeln

Format: <typ>(<scope>): <kurze Beschreibung>

Typen: feat, fix, test, refactor, docs, chore, security

Beispiele:

  • feat(auth): add argon2id password hashing
  • test(storage): add checksum verification test
  • security(admin): enforce server-side admin check on all routes

Kein Commit ohne grüne Tests.

Reverse Proxy & Proxy-Awareness

  • Kein Proxy-Container in docker-compose.yml – TLS und Domain gehören zum Wirt, nicht ins Projekt. Nichts hier soll einen mitgelieferten Proxy voraussetzen.
  • App lauscht auf reinem HTTP (APP_LISTEN_ADDR). TLS = externer Proxy.
  • X-Forwarded-For / X-Forwarded-Proto werden nur von TRUSTED_PROXIES (IPs/CIDRs) akzeptiert.
  • Echte Client-IP immer über middleware.RealIP(r) abrufen – niemals r.RemoteAddr direkt.
  • Secure-Cookie-Flag: über middleware.IsHTTPS(r) setzen (berücksichtigt Forwarded-Proto).
  • PUBLIC_URL für OIDC-Redirects, WOPI-Callbacks und Share-Links verwenden.
  • HSTS gehört auf den Edge-Proxy, nicht in die App-Header.

Docker & Secrets

  • Backend immer via Docker starten/laufen lassen (docker compose -f build/docker-compose.yml ...), nie go run. Der Host hat kein Go-Toolchain installiert; Builds/Compile-Checks laufen über das golang:1.25-alpine-Image. Migrations + DB-Verbindung sind auf die Compose-Umgebung ausgelegt.
  • Secrets nur über .env / Compose-Env – nie im Image oder Repo
  • Multi-Stage-Build: Builder-Stage (Go-Compiler) → Alpine final
  • .env ist in .gitignore; .env.example zeigt alle Keys ohne Werte

Phasen-Stopp

Nach Phase 1: STOPP – Zusammenfassung + Phase-2-Vorschlag, erst nach OK weitermachen.