feat(aether): initial commit – self-hosted Cloud, Phasen 1–7

Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA.
Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb,
Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming,
Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office
(Collabora/OnlyOffice).

Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort)
sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 5 committed 2026-08-24 09:25:36 +02:00
commit 31a027c93a
225 files changed
+38042

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
package storage
import (
"context"
"io"
"os"
"time"
)
// TempFiler kann Uploads unbekannter Größe auf der Platte zwischenlagern,
// statt sie im Arbeitsspeicher zu halten. Bewusst eine eigene, optionale
// Schnittstelle: *os.File gehört nicht in den allgemeinen Storage-Vertrag, und
// eine Implementierung ohne lokales Dateisystem kann sie schlicht weglassen.
type TempFiler interface {
CreateTemp(pattern string) (*os.File, error)
}
// LocalPather gibt den echten Pfad eines Blobs heraus – nur lesend gedacht.
//
// Gebraucht von der Texterkennung: ocrmypdf und tesseract sind externe
// Programme und wollen einen Pfad, keinen Datenstrom. Ohne diese Schnittstelle
// müsste jede Datei erst in eine vollständige Kopie geschrieben werden, bevor
// sie erkannt werden kann – bei einem 300-MB-Scan also 300 MB zusätzlich auf
// einer Platte, die ohnehin knapp sein kann.
//
// Wie TempFiler bewusst optional: Eine Ablage ohne lokales Dateisystem (S3)
// lässt sie weg, und der Aufrufer fällt dann auf die Kopie zurück.
type LocalPather interface {
LocalPath(key string) (string, error)
}
// FileInfo holds metadata about a stored blob.
type FileInfo struct {
Size int64
Checksum string // SHA-256 hex
ModTime time.Time
}
// Storage is the blob storage abstraction.
// All implementations must guarantee atomic writes and never corrupt existing data.
type Storage interface {
// Put streams r to the given key atomically (temp → fsync → rename).
// Returns the SHA-256 checksum and byte count written.
Put(ctx context.Context, key string, r io.Reader) (checksum string, size int64, err error)
// Get returns a reader for the blob at key. Caller must close it.
Get(ctx context.Context, key string) (io.ReadCloser, error)
// Delete removes a blob. In Phase 1 this is a hard delete used only by
// cleanup/GC paths; user-facing deletes are soft-deletes in the DB.
Delete(ctx context.Context, key string) error
// Stat returns metadata for a blob.
Stat(ctx context.Context, key string) (FileInfo, error)
// Move atomically moves a blob from oldKey to newKey within the same storage root.
Move(ctx context.Context, oldKey, newKey string) error
// Walk ruft fn für jeden Blob unterhalb von prefix auf. Existiert der
// Präfix nicht, ist das kein Fehler – nur nichts zu tun. Gedacht für die
// Bereinigung verwaister Blobs, die den Bestand auf der Platte gegen die
// Datenbank abgleichen muss.
Walk(ctx context.Context, prefix string, fn func(key string, info FileInfo) error) error
// DeletePrefix entfernt alles unterhalb eines Präfixes, etwa den kompletten
// Bereich eines Nutzers ("users/alice/"). Gedacht für das endgültige Löschen
// eines Kontos: danach existiert keine Datenbankzeile mehr, die noch auf die
// einzelnen Blobs zeigen könnte.
DeletePrefix(ctx context.Context, prefix string) error
}