first
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package site
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var slugPattern = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
type settingsResponse struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
func toResponse(s *Settings) settingsResponse {
|
||||
return settingsResponse{Name: s.Name, Description: s.Description, Slug: s.Slug}
|
||||
}
|
||||
|
||||
func (h *Handler) Get(c *gin.Context) {
|
||||
settings, err := h.service.Get(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load site settings"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, toResponse(settings))
|
||||
}
|
||||
|
||||
type updateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Slug string `json:"slug" binding:"required"`
|
||||
}
|
||||
|
||||
func (h *Handler) Update(c *gin.Context) {
|
||||
var req updateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "validation error", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
if !slugPattern.MatchString(req.Slug) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "slug must be lowercase letters, digits and hyphens only"})
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := h.service.Update(c.Request.Context(), req.Name, req.Description, req.Slug)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update site settings"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, toResponse(settings))
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Package site owns the minimal, config-driven site identity (name,
|
||||
// description, slug). It is the proof of the "code defines capabilities,
|
||||
// admin defines content" pattern: future modules (appearance, menu, pages,
|
||||
// ...) will follow this same shape (single-row or keyed settings table +
|
||||
// admin-only read/write endpoints).
|
||||
package site
|
||||
|
||||
import "time"
|
||||
|
||||
type Settings struct {
|
||||
ID int16 `gorm:"primaryKey"`
|
||||
Name string
|
||||
Description string
|
||||
Slug string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (Settings) TableName() string { return "site_settings" }
|
||||
@@ -0,0 +1,37 @@
|
||||
package site
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Get(ctx context.Context) (*Settings, error)
|
||||
Update(ctx context.Context, s *Settings) error
|
||||
}
|
||||
|
||||
type gormRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) Repository {
|
||||
return &gormRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *gormRepository) Get(ctx context.Context) (*Settings, error) {
|
||||
var s Settings
|
||||
if err := r.db.WithContext(ctx).First(&s, "id = 1").Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (r *gormRepository) Update(ctx context.Context, s *Settings) error {
|
||||
s.ID = 1
|
||||
return r.db.WithContext(ctx).Model(&Settings{}).Where("id = 1").Updates(map[string]any{
|
||||
"name": s.Name,
|
||||
"description": s.Description,
|
||||
"slug": s.Slug,
|
||||
}).Error
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package site
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func RegisterRoutes(rg *gin.RouterGroup, h *Handler, requireAdmin gin.HandlerFunc) {
|
||||
group := rg.Group("/admin/site-settings")
|
||||
group.GET("", h.Get)
|
||||
group.PUT("", requireAdmin, h.Update)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package site
|
||||
|
||||
import "context"
|
||||
|
||||
type Service struct {
|
||||
repo Repository
|
||||
}
|
||||
|
||||
func NewService(repo Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context) (*Settings, error) {
|
||||
return s.repo.Get(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, name, description, slug string) (*Settings, error) {
|
||||
settings := &Settings{Name: name, Description: description, Slug: slug}
|
||||
if err := s.repo.Update(ctx, settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.repo.Get(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user