readysite / website / controllers / partials.go
5.0 KB
partials.go
package controllers

import (
	"fmt"
	"net/http"

	"github.com/readysite/readysite/pkg/application"
	"github.com/readysite/readysite/website/internal/access"
	"github.com/readysite/readysite/website/internal/helpers"
	"github.com/readysite/readysite/website/internal/content"
	"github.com/readysite/readysite/website/models"
)

// Partials returns the partials controller.
func Partials() (string, *PartialsController) {
	return "partials", &PartialsController{}
}

// PartialsController handles partial CRUD operations.
type PartialsController struct {
	application.BaseController
}

// Setup registers routes.
func (c *PartialsController) Setup(app *application.App) {
	c.BaseController.Setup(app)
}

// Handle implements Controller interface with value receiver for request isolation.
func (c PartialsController) Handle(r *http.Request) application.Controller {
	c.Request = r
	return &c
}

// Partial returns the current partial from path parameter.
func (c *PartialsController) Partial() *models.Partial {
	if c.Request == nil {
		return nil
	}
	id := c.PathValue("id")
	if id == "" {
		return nil
	}
	partial, err := models.Partials.Get(id)
	if err != nil {
		return nil
	}
	return partial
}

// Partials returns all partials.
func (c *PartialsController) Partials() []*models.Partial {
	partials, _ := models.Partials.Search("ORDER BY Name")
	return partials
}

// Create creates a new partial.
func (c *PartialsController) Create(w http.ResponseWriter, r *http.Request) {
	slug := r.FormValue("slug")
	name := r.FormValue("name")
	description := r.FormValue("description")
	html := r.FormValue("html")

	// Validate slug if provided
	if slug != "" {
		if err := content.ValidateSlug(slug); err != nil {
			c.RenderError(w, r, err)
			return
		}
		// Check if slug is already used
		if existing, _ := models.Partials.Get(slug); existing != nil {
			c.RenderError(w, r, fmt.Errorf("A partial with this ID already exists"))
			return
		}
	}

	// Validate partial input fields
	if err := content.ValidateName(name); err != nil {
		c.RenderError(w, r, err)
		return
	}
	if err := content.ValidateDescription(description); err != nil {
		c.RenderError(w, r, err)
		return
	}
	if err := content.ValidateHTML(html); err != nil {
		c.RenderError(w, r, err)
		return
	}

	partial := &models.Partial{
		Name:        name,
		Description: description,
		HTML:        html,
		Published:   r.FormValue("published") == "on" || r.FormValue("published") == "true",
	}

	// Use slug as ID if provided
	if slug != "" {
		partial.ID = slug
	}

	id, err := models.Partials.Insert(partial)
	if err != nil {
		c.RenderError(w, r, fmt.Errorf("Failed to create partial: %w", err))
		return
	}

	// Audit log
	userID := ""
	if user := access.GetUserFromJWT(r); user != nil {
		userID = user.ID
	}
	helpers.AuditCreate(r, userID, "partial", id, partial.Name)

	w.Header().Set("HX-Trigger", `{"showToast":"Partial created"}`)
	c.Redirect(w, r, "/admin/partial/"+id)
}

// Update updates an existing partial.
func (c *PartialsController) Update(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	name := r.FormValue("name")
	description := r.FormValue("description")
	html := r.FormValue("html")

	// Validate partial input fields
	if err := content.ValidateName(name); err != nil {
		c.RenderError(w, r, err)
		return
	}
	if err := content.ValidateDescription(description); err != nil {
		c.RenderError(w, r, err)
		return
	}
	if err := content.ValidateHTML(html); err != nil {
		c.RenderError(w, r, err)
		return
	}

	partial, err := models.Partials.Get(id)
	if err != nil {
		c.RenderError(w, r, fmt.Errorf("Partial not found"))
		return
	}

	// Check for concurrent edit conflict using UpdatedAt
	expectedVersion := r.FormValue("version")
	if expectedVersion != "" && partial.UpdatedAt.Format("2006-01-02T15:04:05") != expectedVersion {
		c.RenderError(w, r, fmt.Errorf("This partial was modified by another user. Please reload and try again."))
		return
	}

	partial.Name = name
	partial.Description = description
	partial.HTML = html
	partial.Published = r.FormValue("published") == "on" || r.FormValue("published") == "true"

	if err := models.Partials.Update(partial); err != nil {
		c.RenderError(w, r, fmt.Errorf("Failed to update partial: %w", err))
		return
	}

	// Audit log
	userID := ""
	if user := access.GetUserFromJWT(r); user != nil {
		userID = user.ID
	}
	helpers.AuditUpdate(r, userID, "partial", id, partial.Name, nil)

	w.Header().Set("HX-Trigger", `{"showToast":"Partial saved"}`)
	c.Refresh(w, r)
}

// Delete deletes a partial.
func (c *PartialsController) Delete(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	partial, err := models.Partials.Get(id)
	if err != nil {
		c.RenderError(w, r, fmt.Errorf("Partial not found"))
		return
	}

	if err := models.Partials.Delete(partial); err != nil {
		c.RenderError(w, r, fmt.Errorf("Failed to delete partial: %w", err))
		return
	}

	// Audit log
	userID := ""
	if user := access.GetUserFromJWT(r); user != nil {
		userID = user.ID
	}
	helpers.AuditDelete(r, userID, "partial", id, partial.Name)

	c.Redirect(w, r, "/admin/partials")
}
← Back