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:
commit
31a027c93a
225 files changed
+38042
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user