package api import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "github.com/go-chi/chi/v5" "github.com/google/uuid" "proto-gen/internal/config" "proto-gen/internal/models" ) func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, msg string) { writeJSON(w, status, map[string]string{"error": msg}) } type CreateTaskRequest struct { Language string `json:"language"` ProtoRepo string `json:"proto_repo"` ProtoBranch string `json:"proto_branch"` TargetRepo string `json:"target_repo"` } func (s *Server) handleCreateTask(w http.ResponseWriter, r *http.Request) { var req CreateTaskRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Language == "" || req.ProtoRepo == "" || req.ProtoBranch == "" { writeError(w, http.StatusBadRequest, "language, proto_repo, and proto_branch are required") return } plugin := s.cfg.GetPluginByLanguage(req.Language) if plugin == nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("unsupported language: %s", req.Language)) return } if req.TargetRepo == "" { req.TargetRepo = strings.ReplaceAll( strings.ReplaceAll(s.cfg.TargetRepoTemplate, "{proto_repo}", req.ProtoRepo), "{lang}", req.Language, ) } branch, err := s.git.GetBranch(req.ProtoRepo, req.ProtoRepo, req.ProtoBranch) if err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("branch not found: %v", err)) return } hasRunning, err := s.db.HasRunningTask(req.TargetRepo) if err != nil { writeError(w, http.StatusInternalServerError, "internal error") return } if hasRunning { writeError(w, http.StatusConflict, "target repo already has a running task") return } task := &models.Task{ ID: uuid.New().String(), Status: models.StatusPending, Language: req.Language, ProtoRepo: req.ProtoRepo, ProtoBranch: req.ProtoBranch, ProtoCommit: branch.Commit, TargetRepo: req.TargetRepo, ToolchainImage: s.cfg.ToolchainRegistry + "/" + plugin.Image, } if err := s.db.CreateTask(task); err != nil { writeError(w, http.StatusInternalServerError, "failed to create task") return } writeJSON(w, http.StatusCreated, task) } func (s *Server) handleListTasks(w http.ResponseWriter, r *http.Request) { status := r.URL.Query().Get("status") limit := 50 if l := r.URL.Query().Get("limit"); l != "" { if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { limit = parsed } } offset := 0 if o := r.URL.Query().Get("offset"); o != "" { if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { offset = parsed } } tasks, err := s.db.ListTasks(status, limit, offset) if err != nil { writeError(w, http.StatusInternalServerError, "failed to list tasks") return } writeJSON(w, http.StatusOK, tasks) } func (s *Server) handleGetTask(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") task, err := s.db.GetTask(id) if err != nil { writeError(w, http.StatusNotFound, "task not found") return } writeJSON(w, http.StatusOK, task) } func (s *Server) handleRetryTask(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") task, err := s.db.GetTask(id) if err != nil { writeError(w, http.StatusNotFound, "task not found") return } if task.Status != models.StatusFailed { writeError(w, http.StatusBadRequest, "can only retry failed tasks") return } hasRunning, err := s.db.HasRunningTask(task.TargetRepo) if err != nil { writeError(w, http.StatusInternalServerError, "internal error") return } if hasRunning { writeError(w, http.StatusConflict, "target repo already has a running task") return } task.Status = models.StatusPending task.Error = "" task.Log = "" task.Retries++ if err := s.db.UpdateTask(task); err != nil { writeError(w, http.StatusInternalServerError, "failed to retry task") return } writeJSON(w, http.StatusOK, task) } func (s *Server) handleListPlugins(w http.ResponseWriter, r *http.Request) { var plugins []config.PluginConfig for _, p := range s.cfg.Plugins { plugins = append(plugins, p) } writeJSON(w, http.StatusOK, plugins) } func (s *Server) handleGetPluginVersion(w http.ResponseWriter, r *http.Request) { lang := chi.URLParam(r, "lang") plugin := s.cfg.GetPluginByLanguage(lang) if plugin == nil { writeError(w, http.StatusNotFound, "plugin not found") return } writeJSON(w, http.StatusOK, plugin) } func (s *Server) handleUpdatePluginVersion(w http.ResponseWriter, r *http.Request) { lang := chi.URLParam(r, "lang") plugin := s.cfg.GetPluginByLanguage(lang) if plugin == nil { writeError(w, http.StatusNotFound, "plugin not found") return } var req struct { Image string `json:"image"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Image == "" { writeError(w, http.StatusBadRequest, "image is required") return } plugin.Image = req.Image writeJSON(w, http.StatusOK, plugin) }