feat: complete MVP epics E7-E10 (provisioning, console proxy, audit, frontend)
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
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
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user