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

@@ -0,0 +1,51 @@
-- Interne Freigaben: Ordner oder Datei an einen anderen Nutzer oder eine Gruppe
-- geben, ohne dass die Bytes umziehen. Der Empfänger sieht sie virtuell unter
-- „Geteilt mit mir"; auf der Platte bleibt alles beim Eigentümer.
--
-- Abweichung vom Projektplan: dort war eine polymorphe Tabelle skizziert
-- (subject_type + subject_id als Text/UUID ohne Fremdschlüssel). Hier stattdessen
-- vier nullbare Fremdschlüssel mit CHECK-Bedingung. Grund: polymorph kann die
-- Datenbank nichts erzwingen – ein gelöschter Nutzer oder Ordner hinterließe
-- Einträge, die auf nichts mehr zeigen, und aus so einer Leiche später eine
-- Freigabe für einen neu vergebenen Namen zu machen wäre ein Sicherheitsloch.
-- Mit echten Fremdschlüsseln räumt ON DELETE CASCADE das von selbst auf.
CREATE TABLE acl_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Empfänger: genau eines von beiden.
subject_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
subject_group_id UUID REFERENCES groups(id) ON DELETE CASCADE,
-- Freigegebene Ressource: genau eines von beiden.
resource_folder_id UUID REFERENCES folders(id) ON DELETE CASCADE,
resource_file_id UUID REFERENCES files(id) ON DELETE CASCADE,
permission TEXT NOT NULL CHECK (permission IN ('read', 'write')),
-- Wer die Freigabe erteilt hat. SET NULL statt CASCADE: die Freigabe soll
-- nicht verschwinden, nur weil das Konto des Erteilers gelöscht wurde.
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT acl_one_subject CHECK (num_nonnulls(subject_user_id, subject_group_id) = 1),
CONSTRAINT acl_one_resource CHECK (num_nonnulls(resource_folder_id, resource_file_id) = 1)
);
-- Zwei Freigaben derselben Ressource an denselben Empfänger wären mehrdeutig:
-- welches Recht gilt dann? Partielle Unique-Indizes, weil NULL in einem
-- gewöhnlichen UNIQUE nicht mit sich selbst kollidiert.
CREATE UNIQUE INDEX idx_acl_user_folder ON acl_entries (subject_user_id, resource_folder_id)
WHERE subject_user_id IS NOT NULL AND resource_folder_id IS NOT NULL;
CREATE UNIQUE INDEX idx_acl_user_file ON acl_entries (subject_user_id, resource_file_id)
WHERE subject_user_id IS NOT NULL AND resource_file_id IS NOT NULL;
CREATE UNIQUE INDEX idx_acl_group_folder ON acl_entries (subject_group_id, resource_folder_id)
WHERE subject_group_id IS NOT NULL AND resource_folder_id IS NOT NULL;
CREATE UNIQUE INDEX idx_acl_group_file ON acl_entries (subject_group_id, resource_file_id)
WHERE subject_group_id IS NOT NULL AND resource_file_id IS NOT NULL;
-- Die Zugriffsprüfung läuft bei jedem Request; sie fragt nach Ressource.
CREATE INDEX idx_acl_folder ON acl_entries (resource_folder_id) WHERE resource_folder_id IS NOT NULL;
CREATE INDEX idx_acl_file ON acl_entries (resource_file_id) WHERE resource_file_id IS NOT NULL;
-- „Geteilt mit mir" fragt nach Empfänger.
CREATE INDEX idx_acl_subject_user ON acl_entries (subject_user_id) WHERE subject_user_id IS NOT NULL;
CREATE INDEX idx_acl_subject_group ON acl_entries (subject_group_id) WHERE subject_group_id IS NOT NULL;