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

+63
View File
@@ -0,0 +1,63 @@
import { useEffect } from 'react'
import { useAuth } from './hooks/useAuth'
import { AppSettingsProvider, useAppSettings } from './hooks/useAppSettings'
import { applyTheme } from './theme'
import LoginPage from './pages/LoginPage'
import Shell from './components/Shell'
import SharePage from './pages/SharePage'
// Simple path-based routing without react-router
function getShareToken(): string | null {
const match = window.location.pathname.match(/^\/s\/([^/]+)/)
return match ? match[1] : null
}
export default function App() {
const shareToken = getShareToken()
// Name und Farben umschliessen auch die Share-Seite: sie ist die einzige
// Ansicht, die Fremde zu Gesicht bekommen.
return (
<AppSettingsProvider>
{shareToken ? <SharePage token={shareToken} /> : <AuthedApp />}
</AppSettingsProvider>
)
}
function AuthedApp() {
const { user, loading, login, logout, refreshUser } = useAuth()
const { globalColors } = useAppSettings()
// Die persönliche Palette gilt nur, wenn der Nutzer sie eingeschaltet hat –
// sonst die des Administrators. Läuft bei jeder Änderung am Nutzer erneut,
// damit ein Umschalten im Profil sofort überall greift.
useEffect(() => {
if (!user) return
if (user.use_custom_theme) {
applyTheme(
user.primary_color || globalColors.primary,
user.accent_color || globalColors.accent,
)
} else {
applyTheme(globalColors.primary, globalColors.accent)
}
}, [user, globalColors])
if (loading) {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{
width: 32, height: 32,
border: '2px solid var(--color-primary)',
borderTopColor: 'transparent',
borderRadius: '50%',
animation: 'spin 0.7s linear infinite',
}} />
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
</div>
)
}
if (!user) return <LoginPage onLogin={login} />
return <Shell user={user} onLogout={logout} onUserChanged={refreshUser} />
}