package web import ( "encoding/json" "errors" "net/http" "time" "ymhut-box/server/unified-management/internal/config" "ymhut-box/server/unified-management/internal/db" "ymhut-box/server/unified-management/internal/health" ) func (r *router) handleAdminDatabase(w http.ResponseWriter, req *http.Request) { path := cleanPath(req.URL.Path) switch { case req.Method == http.MethodGet && path == "/api/admin/database/status": writeJSON(w, http.StatusOK, map[string]any{"ok": true, "database": r.store.Status()}) case req.Method == http.MethodPost && path == "/api/admin/database/test": var body config.DatabaseConfig if err := json.NewDecoder(req.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err) return } if body.Provider == "" { body.Provider = r.cfg.Database.Provider } if body.SQLitePath == "" { body.SQLitePath = r.cfg.Database.SQLitePath } if body.MySQLDSN == "" { body.MySQLDSN = r.cfg.Database.MySQLDSN } if err := db.TestDatabase(body); err != nil { writeError(w, http.StatusBadGateway, "DATABASE_TEST_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true}) case req.Method == http.MethodPost && path == "/api/admin/database/import-sqlite": result, err := r.store.ImportSQLiteToRemote() if err != nil { writeError(w, http.StatusBadGateway, "DATABASE_IMPORT_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result}) case req.Method == http.MethodPost && path == "/api/admin/database/sync": result, err := r.store.SyncNow() if err != nil { writeError(w, http.StatusBadGateway, "DATABASE_SYNC_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result}) default: http.NotFound(w, req) } } func (r *router) handleAdminDashboard(w http.ResponseWriter, req *http.Request) { path := cleanPath(req.URL.Path) if req.Method != http.MethodGet || path != "/api/admin/dashboard/overview" { http.NotFound(w, req) return } overview, err := r.store.DashboardOverview(80) if err != nil { writeError(w, http.StatusInternalServerError, "DASHBOARD_FAILED", err) return } overview["health"] = health.Snapshot(r.cfg, r.store) writeJSON(w, http.StatusOK, overview) } func (r *router) handleAdminSync(w http.ResponseWriter, req *http.Request) { if r.syncer == nil { writeError(w, http.StatusNotFound, "SYNC_DISABLED", errors.New("legacy sync service is not configured")) return } path := cleanPath(req.URL.Path) switch { case req.Method == http.MethodGet && path == "/api/admin/sync/legacy/preview": writeJSON(w, http.StatusOK, r.syncer.Preview(req.Context())) case req.Method == http.MethodPost && path == "/api/admin/sync/legacy/run": writeJSON(w, http.StatusOK, r.syncer.Run(req.Context())) default: http.NotFound(w, req) } } func (r *router) handleAdminEndpoints(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet { http.NotFound(w, req) return } items, err := r.sources.Endpoints(true) if err != nil { writeError(w, http.StatusInternalServerError, "ENDPOINTS_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": items}) } func (r *router) handleAdminEvents(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet { writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET required")) return } flusher, ok := w.(http.Flusher) if !ok { writeError(w, http.StatusInternalServerError, "SSE_UNSUPPORTED", errors.New("streaming is not supported")) return } w.Header().Set("Content-Type", "text/event-stream; charset=utf-8") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") events, unsubscribe := r.sources.SubscribeEvents() defer unsubscribe() ticker := time.NewTicker(15 * time.Second) defer ticker.Stop() writeSSE(w, "ready", map[string]any{"ok": true, "time": time.Now().UTC().Format(time.RFC3339)}) flusher.Flush() for { select { case event, ok := <-events: if !ok { return } writeSSE(w, event.Type, event.Data) flusher.Flush() case <-ticker.C: writeSSE(w, "heartbeat", map[string]any{"time": time.Now().UTC().Format(time.RFC3339)}) flusher.Flush() case <-req.Context().Done(): return } } } func (r *router) handleAdminSystem(w http.ResponseWriter, req *http.Request) { path := cleanPath(req.URL.Path) switch path { case "/api/admin/system/health": writeJSON(w, http.StatusOK, health.Snapshot(r.cfg, r.store)) case "/api/admin/system/audit": items, err := r.store.ListAuditLogs(100) if err != nil { writeError(w, http.StatusInternalServerError, "AUDIT_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": items}) case "/api/admin/system/database/sync": if req.Method != http.MethodPost { writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required")) return } result, err := r.store.ImportSQLiteToRemote() if err != nil { writeError(w, http.StatusBadRequest, "SYNC_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result, "finishedAt": result.FinishedAt}) default: http.NotFound(w, req) } }