759 lines
24 KiB
Go
759 lines
24 KiB
Go
package controllers
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
database "goGin/app/database/config"
|
|
"goGin/app/database/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Payload for creating/updating settings
|
|
type SettingPayload struct {
|
|
Title string `json:"title" binding:"required"`
|
|
MetaTitle string `json:"meta_title" binding:"required"`
|
|
MetaDescription string `json:"meta_description" binding:"required"`
|
|
Phone string `json:"phone" binding:"required"`
|
|
URL string `json:"url" binding:"required"`
|
|
Email string `json:"email" binding:"required"`
|
|
Facebook string `json:"facebook"`
|
|
X string `json:"x"`
|
|
Instagram string `json:"instagram"`
|
|
Whatsapp string `json:"whatsapp"`
|
|
Pinterest string `json:"pinterest"`
|
|
Linkedin string `json:"linkedin"`
|
|
Slogan string `json:"slogan"`
|
|
Address string `json:"address"`
|
|
Copyright string `json:"copyright"`
|
|
MapEmbed string `json:"map_embed"`
|
|
WLogo string `json:"w_logo"`
|
|
BLogo string `json:"b_logo"`
|
|
IsActive *bool `json:"is_active"`
|
|
// Optional image transformation / dimension settings
|
|
WWidth *int `json:"w_width"`
|
|
WHeight *int `json:"w_height"`
|
|
WQuality *int `json:"w_quality"`
|
|
WFormat string `json:"w_format"`
|
|
BWidth *int `json:"b_width"`
|
|
BHeight *int `json:"b_height"`
|
|
BQuality *int `json:"b_quality"`
|
|
BFormat string `json:"b_format"`
|
|
}
|
|
|
|
// AdminListSettings godoc
|
|
// @Summary Admin: List settings
|
|
// @Description Admin listing of settings. Use ?soft=only to list deleted, ?soft=with to include deleted.
|
|
// @Tags settings
|
|
// @Security BearerAuth
|
|
// @Produce json
|
|
// @Param page query int false "Page number"
|
|
// @Param per_page query int false "Items per page"
|
|
// @Param soft query string false "Soft delete filter: only|with"
|
|
// @Success 200 {object} controllers.SettingListResponse
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /api/v1/admin/settings [get]
|
|
func AdminListSettings(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
pageStr := c.DefaultQuery("page", "1")
|
|
perPageStr := c.DefaultQuery("per_page", "20")
|
|
page, _ := strconv.Atoi(pageStr)
|
|
perPage, _ := strconv.Atoi(perPageStr)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 {
|
|
perPage = 20
|
|
}
|
|
if perPage > 200 {
|
|
perPage = 200
|
|
}
|
|
offset := (page - 1) * perPage
|
|
|
|
soft := c.Query("soft")
|
|
var query *gorm.DB
|
|
if soft == "only" {
|
|
query = database.DB.Unscoped().Model(&models.Setting{}).Where("deleted_at IS NOT NULL")
|
|
} else if soft == "with" {
|
|
query = database.DB.Unscoped().Model(&models.Setting{})
|
|
} else {
|
|
query = database.DB.Model(&models.Setting{})
|
|
}
|
|
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var items []models.Setting
|
|
if err := query.Order("created_at desc").Limit(perPage).Offset(offset).Find(&items).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"items": items, "total": total, "page": page, "per_page": perPage})
|
|
}
|
|
|
|
// AdminGetSetting godoc
|
|
// @Summary Admin: Get a setting by id
|
|
// @Description Return a single setting by id
|
|
// @Tags settings
|
|
// @Security BearerAuth
|
|
// @Produce json
|
|
// @Param id path int true "Setting ID"
|
|
// @Success 200 {object} controllers.SettingResponse
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/settings/{id} [get]
|
|
func AdminGetSetting(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil || id < 1 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
var s models.Setting
|
|
if err := database.DB.Unscoped().First(&s, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "setting not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": s})
|
|
}
|
|
|
|
// AdminCreateSetting godoc
|
|
// @Summary Admin: Create a setting
|
|
// @Description Create a new setting
|
|
// @Tags settings
|
|
// @Security BearerAuth
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Param title formData string true "Title"
|
|
// @Param meta_title formData string true "Meta title"
|
|
// @Param meta_description formData string true "Meta description"
|
|
// @Param phone formData string true "Phone"
|
|
// @Param url formData string true "URL"
|
|
// @Param email formData string true "Email"
|
|
// @Param facebook formData string false "Facebook"
|
|
// @Param x formData string false "X"
|
|
// @Param instagram formData string false "Instagram"
|
|
// @Param whatsapp formData string false "Whatsapp"
|
|
// @Param pinterest formData string false "Pinterest"
|
|
// @Param linkedin formData string false "Linkedin"
|
|
// @Param slogan formData string false "Slogan"
|
|
// @Param address formData string false "Address"
|
|
// @Param copyright formData string false "Copyright"
|
|
// @Param map_embed formData string false "Map embed"
|
|
// @Param w_logo formData file false "White logo file upload (or provide w_logo path as string)"
|
|
// @Param b_logo formData file false "Black logo file upload (or provide b_logo path as string)"
|
|
// @Param is_active formData boolean false "Is active"
|
|
// @Param w_width formData int false "W logo width"
|
|
// @Param w_height formData int false "W logo height"
|
|
// @Param w_quality formData int false "W logo quality"
|
|
// @Param w_format formData string false "W logo format"
|
|
// @Param b_width formData int false "B logo width"
|
|
// @Param b_height formData int false "B logo height"
|
|
// @Param b_quality formData int false "B logo quality"
|
|
// @Param b_format formData string false "B logo format"
|
|
// @Success 201 {object} controllers.SettingResponse
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /api/v1/admin/settings [post]
|
|
func AdminCreateSetting(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
|
|
// Support both JSON and multipart/form-data
|
|
var payload SettingPayload
|
|
contentType := c.GetHeader("Content-Type")
|
|
if strings.HasPrefix(contentType, "multipart/form-data") {
|
|
// read form fields
|
|
payload.Title = c.PostForm("title")
|
|
payload.MetaTitle = c.PostForm("meta_title")
|
|
payload.MetaDescription = c.PostForm("meta_description")
|
|
payload.Phone = c.PostForm("phone")
|
|
payload.URL = c.PostForm("url")
|
|
payload.Email = c.PostForm("email")
|
|
payload.Facebook = c.PostForm("facebook")
|
|
payload.X = c.PostForm("x")
|
|
payload.Instagram = c.PostForm("instagram")
|
|
payload.Whatsapp = c.PostForm("whatsapp")
|
|
payload.Pinterest = c.PostForm("pinterest")
|
|
payload.Linkedin = c.PostForm("linkedin")
|
|
payload.Slogan = c.PostForm("slogan")
|
|
payload.Address = c.PostForm("address")
|
|
payload.Copyright = c.PostForm("copyright")
|
|
payload.MapEmbed = c.PostForm("map_embed")
|
|
// keep payload.WLogo/BLogo as string if client sends path
|
|
payload.WLogo = c.PostForm("w_logo")
|
|
payload.BLogo = c.PostForm("b_logo")
|
|
if v := c.PostForm("is_active"); v != "" {
|
|
if b, err := strconv.ParseBool(v); err == nil {
|
|
payload.IsActive = &b
|
|
}
|
|
}
|
|
// numeric metadata
|
|
if v := c.PostForm("w_width"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
payload.WWidth = &n
|
|
}
|
|
}
|
|
if v := c.PostForm("w_height"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
payload.WHeight = &n
|
|
}
|
|
}
|
|
if v := c.PostForm("w_quality"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
payload.WQuality = &n
|
|
}
|
|
}
|
|
payload.WFormat = c.PostForm("w_format")
|
|
if v := c.PostForm("b_width"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
payload.BWidth = &n
|
|
}
|
|
}
|
|
if v := c.PostForm("b_height"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
payload.BHeight = &n
|
|
}
|
|
}
|
|
if v := c.PostForm("b_quality"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
payload.BQuality = &n
|
|
}
|
|
}
|
|
payload.BFormat = c.PostForm("b_format")
|
|
} else {
|
|
// JSON
|
|
if err := c.ShouldBindJSON(&payload); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
// basic required validation
|
|
if payload.Title == "" || payload.MetaTitle == "" || payload.MetaDescription == "" || payload.Phone == "" || payload.URL == "" || payload.Email == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "missing required fields"})
|
|
return
|
|
}
|
|
|
|
isActive := false
|
|
if payload.IsActive != nil {
|
|
isActive = *payload.IsActive
|
|
}
|
|
setting := models.Setting{
|
|
Title: payload.Title,
|
|
MetaTitle: payload.MetaTitle,
|
|
MetaDescription: payload.MetaDescription,
|
|
Phone: payload.Phone,
|
|
URL: payload.URL,
|
|
Email: payload.Email,
|
|
Facebook: payload.Facebook,
|
|
X: payload.X,
|
|
Instagram: payload.Instagram,
|
|
Whatsapp: payload.Whatsapp,
|
|
Pinterest: payload.Pinterest,
|
|
Linkedin: payload.Linkedin,
|
|
Slogan: payload.Slogan,
|
|
Address: payload.Address,
|
|
Copyright: payload.Copyright,
|
|
MapEmbed: payload.MapEmbed,
|
|
WLogo: payload.WLogo,
|
|
BLogo: payload.BLogo,
|
|
IsActive: isActive,
|
|
}
|
|
// optional image transform params
|
|
if payload.WWidth != nil {
|
|
setting.WWidth = *payload.WWidth
|
|
}
|
|
if payload.WHeight != nil {
|
|
setting.WHeight = *payload.WHeight
|
|
}
|
|
if payload.WQuality != nil {
|
|
setting.WQuality = *payload.WQuality
|
|
}
|
|
setting.WFormat = payload.WFormat
|
|
if payload.BWidth != nil {
|
|
setting.BWidth = *payload.BWidth
|
|
}
|
|
if payload.BHeight != nil {
|
|
setting.BHeight = *payload.BHeight
|
|
}
|
|
if payload.BQuality != nil {
|
|
setting.BQuality = *payload.BQuality
|
|
}
|
|
setting.BFormat = payload.BFormat
|
|
|
|
// Handle optional logo file uploads when multipart/form-data
|
|
if strings.HasPrefix(contentType, "multipart/form-data") {
|
|
// Support file upload on field name 'w_logo' (preferred) or fallback to provided path
|
|
if file, err := c.FormFile("w_logo"); err == nil {
|
|
uploadDir := filepath.Join("uploads", "logos")
|
|
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
|
ext := filepath.Ext(file.Filename)
|
|
newName := "wlogo-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ext
|
|
destination := filepath.Join(uploadDir, newName)
|
|
if err := c.SaveUploadedFile(file, destination); err == nil {
|
|
setting.WLogo = "/uploads/logos/" + newName
|
|
if setting.WFormat == "" && ext != "" {
|
|
setting.WFormat = ext[1:]
|
|
}
|
|
}
|
|
}
|
|
// Support file upload on field name 'b_logo'
|
|
if file, err := c.FormFile("b_logo"); err == nil {
|
|
uploadDir := filepath.Join("uploads", "logos")
|
|
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
|
ext := filepath.Ext(file.Filename)
|
|
newName := "blogo-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ext
|
|
destination := filepath.Join(uploadDir, newName)
|
|
if err := c.SaveUploadedFile(file, destination); err == nil {
|
|
setting.BLogo = "/uploads/logos/" + newName
|
|
if setting.BFormat == "" && ext != "" {
|
|
setting.BFormat = ext[1:]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enforce single active setting rule
|
|
if setting.IsActive {
|
|
// Deactivate all other settings
|
|
if err := database.DB.Model(&models.Setting{}).Where("1 = 1").Update("is_active", false).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to deactivate other settings: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := database.DB.Create(&setting).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, gin.H{"data": setting})
|
|
}
|
|
|
|
// AdminUpdateSetting godoc
|
|
// @Summary Admin: Update a setting
|
|
// @Description Update an existing setting
|
|
// @Tags settings
|
|
// @Security BearerAuth
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Param id path int true "Setting ID"
|
|
// @Param title formData string false "Title"
|
|
// @Param meta_title formData string false "Meta title"
|
|
// @Param meta_description formData string false "Meta description"
|
|
// @Param phone formData string false "Phone"
|
|
// @Param url formData string false "URL"
|
|
// @Param email formData string false "Email"
|
|
// @Param facebook formData string false "Facebook"
|
|
// @Param x formData string false "X"
|
|
// @Param instagram formData string false "Instagram"
|
|
// @Param whatsapp formData string false "Whatsapp"
|
|
// @Param pinterest formData string false "Pinterest"
|
|
// @Param linkedin formData string false "Linkedin"
|
|
// @Param slogan formData string false "Slogan"
|
|
// @Param address formData string false "Address"
|
|
// @Param copyright formData string false "Copyright"
|
|
// @Param map_embed formData string false "Map embed"
|
|
// @Param w_logo formData file false "White logo file upload (or provide w_logo path as string)"
|
|
// @Param b_logo formData file false "Black logo file upload (or provide b_logo path as string)"
|
|
// @Param is_active formData boolean false "Is active"
|
|
// @Param w_width formData int false "W logo width"
|
|
// @Param w_height formData int false "W logo height"
|
|
// @Param w_quality formData int false "W logo quality"
|
|
// @Param w_format formData string false "W logo format"
|
|
// @Param b_width formData int false "B logo width"
|
|
// @Param b_height formData int false "B logo height"
|
|
// @Param b_quality formData int false "B logo quality"
|
|
// @Param b_format formData string false "B logo format"
|
|
// @Success 200 {object} controllers.SettingResponse
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /api/v1/admin/settings/{id} [put]
|
|
func AdminUpdateSetting(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil || id < 1 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
|
|
var s models.Setting
|
|
if err := database.DB.First(&s, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "setting not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
contentType := c.GetHeader("Content-Type")
|
|
if strings.HasPrefix(contentType, "multipart/form-data") {
|
|
// read form fields and update if present
|
|
if v := c.PostForm("title"); v != "" {
|
|
s.Title = v
|
|
}
|
|
if v := c.PostForm("meta_title"); v != "" {
|
|
s.MetaTitle = v
|
|
}
|
|
if v := c.PostForm("meta_description"); v != "" {
|
|
s.MetaDescription = v
|
|
}
|
|
if v := c.PostForm("phone"); v != "" {
|
|
s.Phone = v
|
|
}
|
|
if v := c.PostForm("url"); v != "" {
|
|
s.URL = v
|
|
}
|
|
if v := c.PostForm("email"); v != "" {
|
|
s.Email = v
|
|
}
|
|
if v := c.PostForm("facebook"); v != "" {
|
|
s.Facebook = v
|
|
}
|
|
if v := c.PostForm("x"); v != "" {
|
|
s.X = v
|
|
}
|
|
if v := c.PostForm("instagram"); v != "" {
|
|
s.Instagram = v
|
|
}
|
|
if v := c.PostForm("whatsapp"); v != "" {
|
|
s.Whatsapp = v
|
|
}
|
|
if v := c.PostForm("pinterest"); v != "" {
|
|
s.Pinterest = v
|
|
}
|
|
if v := c.PostForm("linkedin"); v != "" {
|
|
s.Linkedin = v
|
|
}
|
|
if v := c.PostForm("slogan"); v != "" {
|
|
s.Slogan = v
|
|
}
|
|
if v := c.PostForm("address"); v != "" {
|
|
s.Address = v
|
|
}
|
|
if v := c.PostForm("copyright"); v != "" {
|
|
s.Copyright = v
|
|
}
|
|
if v := c.PostForm("map_embed"); v != "" {
|
|
s.MapEmbed = v
|
|
}
|
|
if v := c.PostForm("w_logo"); v != "" {
|
|
s.WLogo = v
|
|
}
|
|
if v := c.PostForm("b_logo"); v != "" {
|
|
s.BLogo = v
|
|
}
|
|
if v := c.PostForm("is_active"); v != "" {
|
|
if b, err := strconv.ParseBool(v); err == nil {
|
|
s.IsActive = b
|
|
}
|
|
}
|
|
if v := c.PostForm("w_width"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
s.WWidth = n
|
|
}
|
|
}
|
|
if v := c.PostForm("w_height"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
s.WHeight = n
|
|
}
|
|
}
|
|
if v := c.PostForm("w_quality"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
s.WQuality = n
|
|
}
|
|
}
|
|
if v := c.PostForm("w_format"); v != "" {
|
|
s.WFormat = v
|
|
}
|
|
if v := c.PostForm("b_width"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
s.BWidth = n
|
|
}
|
|
}
|
|
if v := c.PostForm("b_height"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
s.BHeight = n
|
|
}
|
|
}
|
|
if v := c.PostForm("b_quality"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
s.BQuality = n
|
|
}
|
|
}
|
|
if v := c.PostForm("b_format"); v != "" {
|
|
s.BFormat = v
|
|
}
|
|
|
|
// Handle optional file uploads
|
|
if file, err := c.FormFile("w_logo"); err == nil {
|
|
uploadDir := filepath.Join("uploads", "logos")
|
|
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
|
ext := filepath.Ext(file.Filename)
|
|
newName := "wlogo-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ext
|
|
destination := filepath.Join(uploadDir, newName)
|
|
if err := c.SaveUploadedFile(file, destination); err == nil {
|
|
s.WLogo = "/uploads/logos/" + newName
|
|
if s.WFormat == "" && ext != "" {
|
|
s.WFormat = ext[1:]
|
|
}
|
|
}
|
|
}
|
|
if file, err := c.FormFile("b_logo"); err == nil {
|
|
uploadDir := filepath.Join("uploads", "logos")
|
|
_ = os.MkdirAll(uploadDir, os.ModePerm)
|
|
ext := filepath.Ext(file.Filename)
|
|
newName := "blogo-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ext
|
|
destination := filepath.Join(uploadDir, newName)
|
|
if err := c.SaveUploadedFile(file, destination); err == nil {
|
|
s.BLogo = "/uploads/logos/" + newName
|
|
if s.BFormat == "" && ext != "" {
|
|
s.BFormat = ext[1:]
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// JSON payload
|
|
var payload SettingPayload
|
|
if err := c.ShouldBindJSON(&payload); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
// update fields from payload
|
|
s.Title = payload.Title
|
|
s.MetaTitle = payload.MetaTitle
|
|
s.MetaDescription = payload.MetaDescription
|
|
s.Phone = payload.Phone
|
|
s.URL = payload.URL
|
|
s.Email = payload.Email
|
|
s.Facebook = payload.Facebook
|
|
s.X = payload.X
|
|
s.Instagram = payload.Instagram
|
|
s.Whatsapp = payload.Whatsapp
|
|
s.Pinterest = payload.Pinterest
|
|
s.Linkedin = payload.Linkedin
|
|
s.Slogan = payload.Slogan
|
|
s.Address = payload.Address
|
|
s.Copyright = payload.Copyright
|
|
s.MapEmbed = payload.MapEmbed
|
|
s.WLogo = payload.WLogo
|
|
s.BLogo = payload.BLogo
|
|
if payload.IsActive != nil {
|
|
s.IsActive = *payload.IsActive
|
|
}
|
|
if payload.WWidth != nil {
|
|
s.WWidth = *payload.WWidth
|
|
}
|
|
if payload.WHeight != nil {
|
|
s.WHeight = *payload.WHeight
|
|
}
|
|
if payload.WQuality != nil {
|
|
s.WQuality = *payload.WQuality
|
|
}
|
|
s.WFormat = payload.WFormat
|
|
if payload.BWidth != nil {
|
|
s.BWidth = *payload.BWidth
|
|
}
|
|
if payload.BHeight != nil {
|
|
s.BHeight = *payload.BHeight
|
|
}
|
|
if payload.BQuality != nil {
|
|
s.BQuality = *payload.BQuality
|
|
}
|
|
s.BFormat = payload.BFormat
|
|
}
|
|
|
|
// Enforce single active setting rule
|
|
if s.IsActive {
|
|
// Deactivate all other settings except this one
|
|
if err := database.DB.Model(&models.Setting{}).Where("id != ?", s.ID).Update("is_active", false).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to deactivate other settings: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := database.DB.Save(&s).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": s})
|
|
}
|
|
|
|
// AdminDeleteSetting godoc
|
|
// @Summary Admin: Delete a setting
|
|
// @Description Soft-delete a setting by ID
|
|
// @Tags settings
|
|
// @Security BearerAuth
|
|
// @Produce json
|
|
// @Param id path int true "Setting ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /api/v1/admin/settings/{id} [delete]
|
|
func AdminDeleteSetting(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil || id < 1 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
var s models.Setting
|
|
if err := database.DB.First(&s, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "setting not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := database.DB.Delete(&s).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
// attempt to remove logo files if present (safe: only under uploads/)
|
|
for _, p := range []string{s.WLogo, s.BLogo} {
|
|
if p == "" {
|
|
continue
|
|
}
|
|
imgPath := strings.TrimPrefix(p, "/")
|
|
clean := filepath.Clean(imgPath)
|
|
if strings.HasPrefix(clean, "uploads"+string(os.PathSeparator)) {
|
|
_ = os.Remove(clean)
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "setting deleted successfully", "id": s.ID})
|
|
}
|
|
|
|
// AdminRestoreSetting godoc
|
|
// @Summary Admin: Restore a soft-deleted setting
|
|
// @Description Restore a soft-deleted setting by ID
|
|
// @Tags settings
|
|
// @Security BearerAuth
|
|
// @Produce json
|
|
// @Param id path int true "Setting ID"
|
|
// @Success 200 {object} controllers.SettingResponse
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /api/v1/admin/settings/{id}/restore [post]
|
|
func AdminRestoreSetting(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil || id < 1 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
var s models.Setting
|
|
// Find soft-deleted record using Unscoped
|
|
if err := database.DB.Unscoped().Where("id = ?", id).First(&s).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "setting not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
// If DeletedAt is zero, record is not soft-deleted
|
|
if s.DeletedAt.Time.IsZero() {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "setting is not deleted"})
|
|
return
|
|
}
|
|
// Clear deleted_at (restore) using Unscoped Model to allow update on soft-deleted rows
|
|
res := database.DB.Unscoped().Model(&models.Setting{}).Where("id = ?", id).UpdateColumn("deleted_at", nil)
|
|
if res.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": res.Error.Error()})
|
|
return
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "restore failed (no rows affected)"})
|
|
return
|
|
}
|
|
// Reload the record in normal scope to ensure DeletedAt is nil in struct
|
|
if err := database.DB.First(&s, id).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Enforce single active setting rule if restored setting is active
|
|
if s.IsActive {
|
|
if err := database.DB.Model(&models.Setting{}).Where("id != ?", s.ID).Update("is_active", false).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to deactivate other settings: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": s})
|
|
}
|
|
|
|
// GetSettings godoc
|
|
// @Summary Public: Get site settings
|
|
// @Description Return the active site setting (latest active). If none active, return latest setting.
|
|
// @Tags settings
|
|
// @Produce json
|
|
// @Success 200 {object} controllers.SettingResponse
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /api/v1/settings [get]
|
|
func GetSettings(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
|
return
|
|
}
|
|
var s models.Setting
|
|
// Try to find active setting
|
|
if err := database.DB.Where("is_active = ?", true).Order("updated_at desc").First(&s).Error; err != nil {
|
|
// if not found, fallback to latest
|
|
if err == gorm.ErrRecordNotFound {
|
|
if err2 := database.DB.Order("updated_at desc").First(&s).Error; err2 != nil {
|
|
if err2 == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusOK, gin.H{"data": nil})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err2.Error()})
|
|
return
|
|
}
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": s})
|
|
}
|