package api import ( "database/sql" "encoding/json" "fmt" "net/http" "time" "github.com/go-chi/chi/v5" "fuego/internal/auth" "fuego/pkg/types" ) // handleGenerateRegistrationToken generates a new registration token func (s *Server) handleGenerateRegistrationToken(w http.ResponseWriter, r *http.Request) { userID, err := getUserID(r) if err != nil { s.respondError(w, http.StatusUnauthorized, err.Error()) return } // Default expiration: 24 hours expiresIn := 24 * time.Hour var req struct { ExpiresInHours int `json:"expires_in_hours,omitempty"` } if r.Body != nil && r.ContentLength > 0 { if err := json.NewDecoder(r.Body).Decode(&req); err == nil && req.ExpiresInHours > 0 { expiresIn = time.Duration(req.ExpiresInHours) * time.Hour } } token, err := s.secrets.GenerateRegistrationToken(userID, expiresIn) if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to generate token: %v", err)) return } s.respondJSON(w, http.StatusCreated, map[string]interface{}{ "token": token, "expires_in": expiresIn.String(), "expires_at": time.Now().Add(expiresIn), }) } // handleListRegistrationTokens lists all registration tokens func (s *Server) handleListRegistrationTokens(w http.ResponseWriter, r *http.Request) { tokens, err := s.secrets.ListRegistrationTokens() if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list tokens: %v", err)) return } s.respondJSON(w, http.StatusOK, tokens) } // handleRevokeRegistrationToken revokes a registration token func (s *Server) handleRevokeRegistrationToken(w http.ResponseWriter, r *http.Request) { tokenID, err := parseID(r, "id") if err != nil { s.respondError(w, http.StatusBadRequest, err.Error()) return } if err := s.secrets.RevokeRegistrationToken(tokenID); err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to revoke token: %v", err)) return } s.respondJSON(w, http.StatusOK, map[string]string{"message": "Token revoked"}) } // handleVerifyRunner manually verifies a runner func (s *Server) handleVerifyRunner(w http.ResponseWriter, r *http.Request) { runnerID, err := parseID(r, "id") if err != nil { s.respondError(w, http.StatusBadRequest, err.Error()) return } // Check if runner exists var exists bool err = s.db.QueryRow("SELECT EXISTS(SELECT 1 FROM runners WHERE id = ?)", runnerID).Scan(&exists) if err != nil || !exists { s.respondError(w, http.StatusNotFound, "Runner not found") return } // Mark runner as verified _, err = s.db.Exec("UPDATE runners SET verified = 1 WHERE id = ?", runnerID) if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to verify runner: %v", err)) return } s.respondJSON(w, http.StatusOK, map[string]string{"message": "Runner verified"}) } // handleDeleteRunner removes a runner func (s *Server) handleDeleteRunner(w http.ResponseWriter, r *http.Request) { runnerID, err := parseID(r, "id") if err != nil { s.respondError(w, http.StatusBadRequest, err.Error()) return } // Check if runner exists var exists bool err = s.db.QueryRow("SELECT EXISTS(SELECT 1 FROM runners WHERE id = ?)", runnerID).Scan(&exists) if err != nil || !exists { s.respondError(w, http.StatusNotFound, "Runner not found") return } // Delete runner _, err = s.db.Exec("DELETE FROM runners WHERE id = ?", runnerID) if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete runner: %v", err)) return } s.respondJSON(w, http.StatusOK, map[string]string{"message": "Runner deleted"}) } // handleListRunnersAdmin lists all runners with admin details func (s *Server) handleListRunnersAdmin(w http.ResponseWriter, r *http.Request) { rows, err := s.db.Query( `SELECT id, name, hostname, ip_address, status, last_heartbeat, capabilities, registration_token, verified, created_at FROM runners ORDER BY created_at DESC`, ) if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query runners: %v", err)) return } defer rows.Close() runners := []map[string]interface{}{} for rows.Next() { var runner types.Runner var registrationToken sql.NullString var verified bool err := rows.Scan( &runner.ID, &runner.Name, &runner.Hostname, &runner.IPAddress, &runner.Status, &runner.LastHeartbeat, &runner.Capabilities, ®istrationToken, &verified, &runner.CreatedAt, ) if err != nil { s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to scan runner: %v", err)) return } runners = append(runners, map[string]interface{}{ "id": runner.ID, "name": runner.Name, "hostname": runner.Hostname, "ip_address": runner.IPAddress, "status": runner.Status, "last_heartbeat": runner.LastHeartbeat, "capabilities": runner.Capabilities, "registration_token": registrationToken.String, "verified": verified, "created_at": runner.CreatedAt, }) } s.respondJSON(w, http.StatusOK, runners) }