package template import ( "context" "encoding/json" "net/http" "strings" ) type Repository interface { List(ctx context.Context) ([]Template, error) Get(ctx context.Context, id string) (Template, bool, error) Upsert(ctx context.Context, t Template) (Template, error) Delete(ctx context.Context, id string) (bool, error) } type Handler struct { repository Repository } func NewHandler(repository Repository) Handler { return Handler{repository: repository} } func (h Handler) ListTemplates(w http.ResponseWriter, r *http.Request) { templates, err := h.repository.List(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "templates_list_failed") return } if templates == nil { templates = []Template{} } writeJSON(w, http.StatusOK, map[string][]Template{"data": templates}) } func (h Handler) CreateTemplate(w http.ResponseWriter, r *http.Request) { var request createTemplateRequest if !decodeJSON(w, r, &request) { return } request.Name = strings.TrimSpace(request.Name) if request.Name == "" { writeError(w, http.StatusBadRequest, "name_required") return } if request.ClusterID == "" { writeError(w, http.StatusBadRequest, "cluster_id_required") return } if request.ProxmoxTemplateVMID <= 0 { writeError(w, http.StatusBadRequest, "proxmox_template_vmid_required") return } saved, err := h.repository.Upsert(r.Context(), Template{ ClusterID: request.ClusterID, Name: request.Name, Description: request.Description, ProxmoxTemplateVMID: request.ProxmoxTemplateVMID, ProxmoxNode: request.ProxmoxNode, }) if err != nil { writeError(w, http.StatusBadRequest, "template_upsert_failed") return } writeJSON(w, http.StatusCreated, saved) } func (h Handler) UpdateTemplate(w http.ResponseWriter, r *http.Request) { templateID := r.PathValue("templateID") if templateID == "" { writeError(w, http.StatusBadRequest, "template_id_required") return } existing, found, err := h.repository.Get(r.Context(), templateID) if err != nil { writeError(w, http.StatusInternalServerError, "template_get_failed") return } if !found { writeError(w, http.StatusNotFound, "template_not_found") return } var request updateTemplateRequest if !decodeJSON(w, r, &request) { return } if request.ClusterID == nil { request.ClusterID = &existing.ClusterID } if request.Name == nil { request.Name = &existing.Name } else { trimmed := strings.TrimSpace(*request.Name) request.Name = &trimmed if *request.Name == "" { writeError(w, http.StatusBadRequest, "name_required") return } } if request.Description == nil { request.Description = &existing.Description } if request.ProxmoxTemplateVMID == nil { request.ProxmoxTemplateVMID = &existing.ProxmoxTemplateVMID } else if *request.ProxmoxTemplateVMID <= 0 { writeError(w, http.StatusBadRequest, "proxmox_template_vmid_required") return } if request.ProxmoxNode == nil { request.ProxmoxNode = &existing.ProxmoxNode } saved, err := h.repository.Upsert(r.Context(), Template{ ID: existing.ID, ClusterID: *request.ClusterID, Name: *request.Name, Description: *request.Description, ProxmoxTemplateVMID: *request.ProxmoxTemplateVMID, ProxmoxNode: *request.ProxmoxNode, }) if err != nil { writeError(w, http.StatusBadRequest, "template_upsert_failed") return } writeJSON(w, http.StatusOK, saved) } func (h Handler) DeleteTemplate(w http.ResponseWriter, r *http.Request) { templateID := r.PathValue("templateID") if templateID == "" { writeError(w, http.StatusBadRequest, "template_id_required") return } deleted, err := h.repository.Delete(r.Context(), templateID) if err != nil { writeError(w, http.StatusInternalServerError, "template_delete_failed") return } if !deleted { writeError(w, http.StatusNotFound, "template_not_found") return } w.WriteHeader(http.StatusNoContent) } type createTemplateRequest struct { ClusterID string `json:"cluster_id"` Name string `json:"name"` Description string `json:"description"` ProxmoxTemplateVMID int `json:"proxmox_template_vmid"` ProxmoxNode string `json:"proxmox_node"` } type updateTemplateRequest struct { ClusterID *string `json:"cluster_id"` Name *string `json:"name"` Description *string `json:"description"` ProxmoxTemplateVMID *int `json:"proxmox_template_vmid"` ProxmoxNode *string `json:"proxmox_node"` } func decodeJSON(w http.ResponseWriter, r *http.Request, target any) bool { defer r.Body.Close() decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { writeError(w, http.StatusBadRequest, "invalid_json") return false } return true } func writeJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(body) } func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]string{"error": message}) }