// backend\routes.go package main import ( "fmt" "net/http" "net/url" "strings" ) // routes.go (package main) func registerRoutes(mux *http.ServeMux, auth *AuthManager) *ModelStore { // -------------------------- // 1) Public Auth Endpoints // -------------------------- mux.HandleFunc("/api/auth/login", authLoginHandler(auth)) mux.HandleFunc("/api/auth/logout", authLogoutHandler(auth)) mux.HandleFunc("/api/auth/me", authMeHandler(auth)) // 2FA (Authenticator/TOTP) mux.HandleFunc("/api/auth/2fa/setup", auth2FASetupHandler(auth)) mux.HandleFunc("/api/auth/2fa/enable", auth2FAEnableHandler(auth)) // mux.HandleFunc("/api/auth/2fa/disable", auth2FADisableHandler(auth)) // -------------------------- // 2) Protected API Mux // -------------------------- api := http.NewServeMux() api.HandleFunc("/api/cookies", cookiesHandler) api.HandleFunc("/api/events/stream", eventsStream) api.HandleFunc("/api/perf/stream", perfStreamHandler) api.HandleFunc("/api/status/disk", diskStatusHandler) api.HandleFunc("/api/autostart/state", autostartStateHandler) api.HandleFunc("/api/autostart/state/stream", autostartStateStreamHandler) api.HandleFunc("/api/autostart/pause", autostartPauseQuickHandler) api.HandleFunc("/api/autostart/resume", autostartResumeHandler) api.HandleFunc("/api/settings", recordSettingsHandler) api.HandleFunc("/api/settings/browse", settingsBrowse) api.HandleFunc("/api/settings/cleanup", settingsCleanupHandler) api.HandleFunc("/api/record", startRecordingFromRequest) api.HandleFunc("/api/record/status", recordStatus) api.HandleFunc("/api/record/stop", recordStop) api.HandleFunc("/api/record/postwork/remove", recordRemoveQueuedPostwork) api.HandleFunc("/api/preview", recordPreview) api.HandleFunc("/api/preview/live", recordPreviewLive) api.HandleFunc("/api/preview-scrubber/", recordPreviewScrubberFrame) api.HandleFunc("/api/preview-sprite/", recordPreviewSprite) api.HandleFunc("/api/record/list", recordList) api.HandleFunc("/api/record/done/meta", recordDoneMeta) api.HandleFunc("/api/record/video", recordVideo) api.HandleFunc("/api/record/split", recordSplitVideo) api.HandleFunc("/api/record/analyze", recordAnalyzeVideo) api.HandleFunc("/api/record/done", recordDoneList) api.HandleFunc("/api/record/delete", recordDeleteVideo) api.HandleFunc("/api/record/toggle-hot", recordToggleHot) api.HandleFunc("/api/record/keep", recordKeepVideo) api.HandleFunc("/api/record/unkeep", recordUnkeepVideo) api.HandleFunc("/api/record/restore", recordRestoreVideo) api.HandleFunc("/api/record/prepare-split", recordPrepareSplit) api.HandleFunc("/api/chaturbate/online", chaturbateOnlineHandler) api.HandleFunc("/api/chaturbate/biocontext", chaturbateBioContextHandler) api.HandleFunc("/api/generated/teaser", generatedTeaser) api.HandleFunc("/api/generated/cover", generatedCover) api.HandleFunc("/api/generated/coverinfo/list", generatedCoverInfoList) // Tasks api.HandleFunc("/api/tasks/status", tasksStatusHandler) api.HandleFunc("/api/tasks/generate-assets", tasksGenerateAssets) // -------------------------- // 3) ModelStore (Postgres) // DSN kommt aus Settings: databaseUrl + gespeichertes Passwort // -------------------------- dsn, err := buildPostgresDSNFromSettings() if err != nil { fmt.Println("⚠️ models DSN:", err) } fmt.Println("📦 Models DB (Postgres):", sanitizeDSNForLog(dsn)) store := NewModelStore(dsn) if err := store.Load(); err != nil { fmt.Println("⚠️ models load:", err) } setCoverModelStore(store) RegisterModelAPI(api, store) setChaturbateOnlineModelStore(store) // -------------------------- // 4) Mount Protected API // -------------------------- // /api/auth/* ist schon public am root mux und gewinnt als längeres Pattern. mux.Handle("/api/", requireAuth(auth, api, false)) // -------------------------- // 5) Mount Protected SPA (/) // -------------------------- frontend, ok := makeFrontendHandler() if ok && frontend != nil { // allowPaths: login + assets müssen öffentlich sein, sonst Redirect-Loop mux.Handle("/", requireAuth(auth, frontend, true, "/login", "/assets/", "/favicon.ico", "/manifest.webmanifest", "/robots.txt", "/service-worker.js", )) } return store } // buildPostgresDSNFromSettings baut eine DSN aus den Recorder-Settings: // - databaseUrl kann bereits "postgres://user:pass@host/db" sein // - oder ohne Passwort, dann wird das verschlüsselt gespeicherte Passwort eingesetzt func buildPostgresDSNFromSettings() (string, error) { // Settings sind bereits durch loadSettings() in main() geladen. s := getSettings() dbURL := strings.TrimSpace(s.DatabaseURL) if dbURL == "" { return "", fmt.Errorf("databaseUrl ist leer") } // Wenn databaseUrl ein Passwort enthält: verwenden, // aber gleichzeitig safe sein, falls da Altbestand drin ist. u, err := url.Parse(dbURL) if err != nil { return "", fmt.Errorf("databaseUrl ungültig: %w", err) } // 1) Wenn URL bereits Passwort enthält -> nur verwenden, wenn es NICHT der Placeholder ist if u.User != nil { if pw, hasPw := u.User.Password(); hasPw { pw = strings.TrimSpace(pw) if pw != "" && pw != "****" { return u.String(), nil } // sonst: Placeholder -> ignorieren und unten aus EncryptedDBPassword einsetzen } } // 2) Passwort fehlt -> aus EncryptedDBPassword holen enc := strings.TrimSpace(s.EncryptedDBPassword) if enc == "" { // kein Passwort gespeichert -> URL ohne Passwort ist ok (z.B. trust/peer auth) return u.String(), nil } plainPw, err := decryptSettingString(enc) if err != nil { return "", fmt.Errorf("db password decrypt failed: %w", err) } plainPw = strings.TrimSpace(plainPw) if plainPw == "" { return u.String(), nil } // 3) Username muss in databaseUrl vorhanden sein, sonst kann man kein Passwort einsetzen user := "" if u.User != nil { user = u.User.Username() } if strings.TrimSpace(user) == "" { return "", fmt.Errorf("databaseUrl enthält keinen Username, kann Passwort nicht einsetzen") } u.User = url.UserPassword(user, plainPw) return u.String(), nil } // sanitizeDSNForLog entfernt Passwort aus DSN, damit du es gefahrlos loggen kannst. func sanitizeDSNForLog(dsn string) string { dsn = strings.TrimSpace(dsn) if dsn == "" { return "" } u, err := url.Parse(dsn) if err != nil { return "" } if u.User != nil { u.User = url.UserPassword(u.User.Username(), "****") } return u.String() }