// Package handlers provides HTTP handlers for the rdev API. package handlers import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/orchard9/rdev/pkg/api" ) // ProjectsHandler handles project-related endpoints. type ProjectsHandler struct{} // NewProjectsHandler creates a new projects handler. func NewProjectsHandler() *ProjectsHandler { return &ProjectsHandler{} } // Mount registers the projects routes. func (h *ProjectsHandler) Mount(r api.Router) { r.Route("/projects", func(r chi.Router) { r.Get("/", h.List) r.Get("/{id}", h.Get) r.Post("/{id}/claude", h.RunClaude) r.Post("/{id}/shell", h.RunShell) r.Post("/{id}/git", h.RunGit) r.Get("/{id}/events", h.Events) }) } // List returns all available projects. // GET /projects func (h *ProjectsHandler) List(w http.ResponseWriter, r *http.Request) { // TODO: Implement project discovery from K8s projects := []map[string]any{ { "id": "pantheon", "name": "Pantheon", "description": "Go API backend", "pod": "claudebox-pantheon-0", "status": "running", }, { "id": "aeries", "name": "Aeries", "description": "Note community platform", "pod": "claudebox-aeries-0", "status": "running", }, } api.WriteSuccess(w, r, projects) } // Get returns a specific project by ID. // GET /projects/{id} func (h *ProjectsHandler) Get(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") // TODO: Look up project from registry project := map[string]any{ "id": id, "name": id, "description": "Project description", "pod": "claudebox-" + id + "-0", "status": "running", "workspace": "/workspace", "config": map[string]any{ "claude_auth": true, "git_enabled": true, "last_command": nil, }, } api.WriteSuccess(w, r, project) } // RunClaude executes a Claude command in the project's claudebox. // POST /projects/{id}/claude func (h *ProjectsHandler) RunClaude(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") // TODO: Parse request body for prompt // TODO: Execute kubectl exec -n rdev claudebox-{id}-0 -- claude "{prompt}" result := map[string]any{ "id": "cmd-" + id + "-001", "project": id, "type": "claude", "status": "queued", "stream_url": "/projects/" + id + "/events?stream_id=cmd-" + id + "-001", } api.WriteCreated(w, r, result) } // RunShell executes a shell command in the project's claudebox. // POST /projects/{id}/shell func (h *ProjectsHandler) RunShell(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") // TODO: Parse request body for command // TODO: Execute kubectl exec -n rdev claudebox-{id}-0 -- bash -c "{command}" result := map[string]any{ "id": "cmd-" + id + "-002", "project": id, "type": "shell", "status": "queued", "stream_url": "/projects/" + id + "/events?stream_id=cmd-" + id + "-002", } api.WriteCreated(w, r, result) } // RunGit executes a git command in the project's claudebox. // POST /projects/{id}/git func (h *ProjectsHandler) RunGit(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") // TODO: Parse request body for git command // TODO: Execute kubectl exec -n rdev claudebox-{id}-0 -- git {args} result := map[string]any{ "id": "cmd-" + id + "-003", "project": id, "type": "git", "status": "queued", "stream_url": "/projects/" + id + "/events?stream_id=cmd-" + id + "-003", } api.WriteCreated(w, r, result) } // Events streams command output via Server-Sent Events. // GET /projects/{id}/events func (h *ProjectsHandler) Events(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") streamID := r.URL.Query().Get("stream_id") // Set SSE headers w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "SSE not supported", http.StatusInternalServerError) return } // TODO: Stream actual command output // For now, send mock events // Send initial connected event writeSSE(w, flusher, "connected", map[string]any{ "project": id, "stream_id": streamID, }) // Send mock output writeSSE(w, flusher, "output", map[string]any{ "line": "Starting command execution...", "stream": "stdout", }) writeSSE(w, flusher, "output", map[string]any{ "line": "Command completed successfully.", "stream": "stdout", }) // Send complete event writeSSE(w, flusher, "complete", map[string]any{ "exit_code": 0, "duration_ms": 1234, }) } // writeSSE writes a Server-Sent Event. func writeSSE(w http.ResponseWriter, flusher http.Flusher, event string, data map[string]any) { dataBytes, _ := jsonMarshal(data) w.Write([]byte("event: " + event + "\n")) w.Write([]byte("data: " + string(dataBytes) + "\n\n")) flusher.Flush() } // jsonMarshal is a simple JSON marshal helper. func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) }