package api import ( "errors" "net/http" "github.com/go-chi/chi/v5" "github.com/alexei/docker-watcher/internal/store" ) // projectRequest is the expected JSON body for creating/updating a project. type projectRequest struct { Name string `json:"name"` Registry string `json:"registry"` Image string `json:"image"` Port int `json:"port"` Healthcheck string `json:"healthcheck"` Env string `json:"env"` Volumes string `json:"volumes"` } // listProjects handles GET /api/projects. func (s *Server) listProjects(w http.ResponseWriter, r *http.Request) { projects, err := s.store.GetAllProjects() if err != nil { respondError(w, http.StatusInternalServerError, "failed to list projects: "+err.Error()) return } respondJSON(w, http.StatusOK, projects) } // createProject handles POST /api/projects. func (s *Server) createProject(w http.ResponseWriter, r *http.Request) { var req projectRequest if !decodeJSON(w, r, &req) { return } if req.Name == "" { respondError(w, http.StatusBadRequest, "name is required") return } if req.Image == "" { respondError(w, http.StatusBadRequest, "image is required") return } if req.Env == "" { req.Env = "{}" } if req.Volumes == "" { req.Volumes = "{}" } project, err := s.store.CreateProject(store.Project{ Name: req.Name, Registry: req.Registry, Image: req.Image, Port: req.Port, Healthcheck: req.Healthcheck, Env: req.Env, Volumes: req.Volumes, }) if err != nil { respondError(w, http.StatusInternalServerError, "failed to create project: "+err.Error()) return } respondJSON(w, http.StatusCreated, project) } // getProject handles GET /api/projects/{id}. func (s *Server) getProject(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") project, err := s.store.GetProjectByID(id) if err != nil { if errors.Is(err, store.ErrNotFound) { respondNotFound(w, "project") return } respondError(w, http.StatusInternalServerError, "failed to get project: "+err.Error()) return } // Also fetch stages for this project. stages, err := s.store.GetStagesByProjectID(id) if err != nil { respondError(w, http.StatusInternalServerError, "failed to get stages: "+err.Error()) return } respondJSON(w, http.StatusOK, map[string]any{ "project": project, "stages": stages, }) } // updateProject handles PUT /api/projects/{id}. func (s *Server) updateProject(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") existing, err := s.store.GetProjectByID(id) if err != nil { if errors.Is(err, store.ErrNotFound) { respondNotFound(w, "project") return } respondError(w, http.StatusInternalServerError, "failed to get project: "+err.Error()) return } var req projectRequest if !decodeJSON(w, r, &req) { return } // Apply updates to existing project, preserving fields not provided. updated := existing if req.Name != "" { updated.Name = req.Name } if req.Image != "" { updated.Image = req.Image } updated.Registry = req.Registry updated.Port = req.Port updated.Healthcheck = req.Healthcheck if req.Env != "" { updated.Env = req.Env } if req.Volumes != "" { updated.Volumes = req.Volumes } if err := s.store.UpdateProject(updated); err != nil { respondError(w, http.StatusInternalServerError, "failed to update project: "+err.Error()) return } respondJSON(w, http.StatusOK, updated) } // deleteProject handles DELETE /api/projects/{id}. func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if err := s.store.DeleteProject(id); err != nil { if errors.Is(err, store.ErrNotFound) { respondNotFound(w, "project") return } respondError(w, http.StatusInternalServerError, "failed to delete project: "+err.Error()) return } respondJSON(w, http.StatusOK, map[string]string{"deleted": id}) }