package controllers import ( "errors" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" "goaresv3/app/settings/models" "goaresv3/config" ) type UpsertSettingRequest struct { Title string `json:"title" binding:"required,max=254"` MetaTitle string `json:"meta_title" binding:"required,max=254"` MetaDescription string `json:"meta_description" binding:"required,max=254"` Phone string `json:"phone" binding:"required,max=254"` URL string `json:"url" binding:"required,max=254"` Email string `json:"email" binding:"required,email,max=254"` Facebook string `json:"facebook" binding:"omitempty,max=254"` X string `json:"x" binding:"omitempty,max=254"` Instagram string `json:"instagram" binding:"omitempty,max=254"` Whatsapp string `json:"whatsapp" binding:"omitempty,max=254"` Pinterest string `json:"pinterest" binding:"omitempty,max=254"` Linkedin string `json:"linkedin" binding:"omitempty,max=254"` Slogan string `json:"slogan" binding:"omitempty,max=254"` Address string `json:"address"` Copyright string `json:"copyright" binding:"omitempty,max=254"` MapEmbed string `json:"map_embed"` WLogo string `json:"w_logo"` BLogo string `json:"b_logo"` IsActive bool `json:"is_active"` WWidth int `json:"w_width"` WHeight int `json:"w_height"` WQuality int `json:"w_quality"` WFormat string `json:"w_format" binding:"omitempty,max=10"` BWidth int `json:"b_width"` BHeight int `json:"b_height"` BQuality int `json:"b_quality"` BFormat string `json:"b_format" binding:"omitempty,max=10"` } type UpsertHeroRequest struct { Color string `json:"color" binding:"required,max=32"` Title string `json:"title" binding:"omitempty,max=254"` Text1 string `json:"text1" binding:"omitempty,max=254"` Text2 string `json:"text2" binding:"omitempty,max=254"` Text4 string `json:"text4" binding:"omitempty,max=254"` Text5 string `json:"text5" binding:"omitempty,max=254"` Image string `json:"image" binding:"omitempty,max=254"` IsActive bool `json:"is_active"` Width int `json:"width"` Height int `json:"height"` Quality int `json:"quality"` Format string `json:"format" binding:"omitempty,max=10"` } type UpsertCorsWhitelistRequest struct { Origin string `json:"origin" binding:"required,max=255"` Description string `json:"description" binding:"omitempty,max=255"` IsActive bool `json:"is_active"` CreatedBy string `json:"created_by" binding:"omitempty,max=255"` } type UpsertCorsBlacklistRequest struct { Origin string `json:"origin" binding:"required,max=255"` Reason string `json:"reason" binding:"omitempty,max=255"` IsActive bool `json:"is_active"` CreatedBy string `json:"created_by" binding:"omitempty,max=255"` } type UpsertRateLimitRequest struct { Name string `json:"name" binding:"required,max=100"` Description string `json:"description" binding:"omitempty,max=255"` MaxRequests int64 `json:"max_requests" binding:"required,min=1"` WindowSeconds int `json:"window_seconds" binding:"required,min=1"` IsActive bool `json:"is_active"` UpdatedBy string `json:"updated_by" binding:"omitempty,max=255"` } func parseID(c *gin.Context) (uint64, bool) { id, err := strconv.ParseUint(strings.TrimSpace(c.Param("id")), 10, 64) if err != nil || id == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return 0, false } return id, true } // GetSetting godoc // @Summary Get global setting // @Description Returns the latest settings record. // @Tags Settings // @Produce json // @Success 200 {object} map[string]interface{} // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings [get] func GetSetting(c *gin.Context) { var setting models.Setting err := config.DB.Order("id DESC").First(&setting).Error if errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "setting not found"}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch setting"}) return } c.JSON(http.StatusOK, setting) } // UpsertSetting godoc // @Summary Create or update global setting // @Description Creates the first setting record if none exists, otherwise updates the latest one. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param request body UpsertSettingRequest true "setting payload" // @Success 200 {object} map[string]interface{} // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings [put] func UpsertSetting(c *gin.Context) { var req UpsertSettingRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var setting models.Setting err := config.DB.Order("id DESC").First(&setting).Error if errors.Is(err, gorm.ErrRecordNotFound) { setting = models.Setting{ Title: req.Title, MetaTitle: req.MetaTitle, MetaDescription: req.MetaDescription, Phone: req.Phone, URL: req.URL, Email: req.Email, Facebook: req.Facebook, X: req.X, Instagram: req.Instagram, Whatsapp: req.Whatsapp, Pinterest: req.Pinterest, Linkedin: req.Linkedin, Slogan: req.Slogan, Address: req.Address, Copyright: req.Copyright, MapEmbed: req.MapEmbed, WLogo: req.WLogo, BLogo: req.BLogo, IsActive: req.IsActive, WWidth: req.WWidth, WHeight: req.WHeight, WQuality: req.WQuality, WFormat: req.WFormat, BWidth: req.BWidth, BHeight: req.BHeight, BQuality: req.BQuality, BFormat: req.BFormat, } if createErr := config.DB.Create(&setting).Error; createErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create setting"}) return } c.JSON(http.StatusCreated, setting) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch setting"}) return } updates := map[string]any{ "title": req.Title, "meta_title": req.MetaTitle, "meta_description": req.MetaDescription, "phone": req.Phone, "url": req.URL, "email": req.Email, "facebook": req.Facebook, "x": req.X, "instagram": req.Instagram, "whatsapp": req.Whatsapp, "pinterest": req.Pinterest, "linkedin": req.Linkedin, "slogan": req.Slogan, "address": req.Address, "copyright": req.Copyright, "map_embed": req.MapEmbed, "w_logo": req.WLogo, "b_logo": req.BLogo, "is_active": req.IsActive, "w_width": req.WWidth, "w_height": req.WHeight, "w_quality": req.WQuality, "w_format": req.WFormat, "b_width": req.BWidth, "b_height": req.BHeight, "b_quality": req.BQuality, "b_format": req.BFormat, } if err := config.DB.Model(&setting).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update setting"}) return } if err := config.DB.First(&setting, setting.ID).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch updated setting"}) return } c.JSON(http.StatusOK, setting) } // ListHeroes godoc // @Summary List heroes // @Description Returns all hero records ordered by id desc. // @Tags Settings // @Produce json // @Success 200 {array} map[string]interface{} // @Failure 500 {object} map[string]string // @Router /api/v1/settings/heroes [get] func ListHeroes(c *gin.Context) { var heroes []models.Hero if err := config.DB.Order("id DESC").Find(&heroes).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch heroes"}) return } c.JSON(http.StatusOK, heroes) } // CreateHero godoc // @Summary Create hero // @Description Creates a new hero record. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param request body UpsertHeroRequest true "hero payload" // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings/heroes [post] func CreateHero(c *gin.Context) { var req UpsertHeroRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } hero := models.Hero{ Color: req.Color, Title: req.Title, Text1: req.Text1, Text2: req.Text2, Text4: req.Text4, Text5: req.Text5, Image: req.Image, IsActive: req.IsActive, Width: req.Width, Height: req.Height, Quality: req.Quality, Format: req.Format, } if err := config.DB.Create(&hero).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create hero"}) return } c.JSON(http.StatusCreated, hero) } // UpdateHero godoc // @Summary Update hero // @Description Updates a hero by id. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param id path int true "hero id" // @Param request body UpsertHeroRequest true "hero payload" // @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/settings/heroes/{id} [put] func UpdateHero(c *gin.Context) { id, ok := parseID(c) if !ok { return } var req UpsertHeroRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var hero models.Hero if err := config.DB.First(&hero, id).Error; errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "hero not found"}) return } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch hero"}) return } if err := config.DB.Model(&hero).Updates(map[string]any{ "color": req.Color, "title": req.Title, "text1": req.Text1, "text2": req.Text2, "text4": req.Text4, "text5": req.Text5, "image": req.Image, "is_active": req.IsActive, "width": req.Width, "height": req.Height, "quality": req.Quality, "format": req.Format, }).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update hero"}) return } if err := config.DB.First(&hero, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch updated hero"}) return } c.JSON(http.StatusOK, hero) } // DeleteHero godoc // @Summary Delete hero // @Description Deletes a hero by id. // @Tags Settings // @Security BearerAuth // @Produce json // @Param id path int true "hero id" // @Success 200 {object} map[string]string // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings/heroes/{id} [delete] func DeleteHero(c *gin.Context) { id, ok := parseID(c) if !ok { return } res := config.DB.Delete(&models.Hero{}, id) if res.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete hero"}) return } if res.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "hero not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "hero deleted"}) } // ListCorsWhitelists godoc // @Summary List CORS whitelist items // @Description Returns all CORS whitelist records. // @Tags Settings // @Security BearerAuth // @Produce json // @Success 200 {array} map[string]interface{} // @Failure 500 {object} map[string]string // @Router /api/v1/settings/cors/whitelist [get] func ListCorsWhitelists(c *gin.Context) { var items []models.CorsWhitelist if err := config.DB.Order("id DESC").Find(&items).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cors whitelists"}) return } c.JSON(http.StatusOK, items) } // CreateCorsWhitelist godoc // @Summary Create CORS whitelist item // @Description Creates a new whitelist origin. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param request body UpsertCorsWhitelistRequest true "cors whitelist payload" // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 409 {object} map[string]string // @Router /api/v1/settings/cors/whitelist [post] func CreateCorsWhitelist(c *gin.Context) { var req UpsertCorsWhitelistRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } item := models.CorsWhitelist{ Origin: req.Origin, Description: req.Description, IsActive: req.IsActive, CreatedBy: req.CreatedBy, } if err := config.DB.Create(&item).Error; err != nil { c.JSON(http.StatusConflict, gin.H{"error": "failed to create cors whitelist"}) return } c.JSON(http.StatusCreated, item) } // UpdateCorsWhitelist godoc // @Summary Update CORS whitelist item // @Description Updates a whitelist origin by id. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param id path int true "whitelist id" // @Param request body UpsertCorsWhitelistRequest true "cors whitelist payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 409 {object} map[string]string // @Router /api/v1/settings/cors/whitelist/{id} [put] func UpdateCorsWhitelist(c *gin.Context) { id, ok := parseID(c) if !ok { return } var req UpsertCorsWhitelistRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var item models.CorsWhitelist if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "cors whitelist not found"}) return } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cors whitelist"}) return } if err := config.DB.Model(&item).Updates(map[string]any{ "origin": req.Origin, "description": req.Description, "is_active": req.IsActive, "created_by": req.CreatedBy, }).Error; err != nil { c.JSON(http.StatusConflict, gin.H{"error": "failed to update cors whitelist"}) return } if err := config.DB.First(&item, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch updated cors whitelist"}) return } c.JSON(http.StatusOK, item) } // DeleteCorsWhitelist godoc // @Summary Delete CORS whitelist item // @Description Deletes a whitelist origin by id. // @Tags Settings // @Security BearerAuth // @Produce json // @Param id path int true "whitelist id" // @Success 200 {object} map[string]string // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings/cors/whitelist/{id} [delete] func DeleteCorsWhitelist(c *gin.Context) { id, ok := parseID(c) if !ok { return } res := config.DB.Delete(&models.CorsWhitelist{}, id) if res.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete cors whitelist"}) return } if res.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "cors whitelist not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "cors whitelist deleted"}) } // ListCorsBlacklists godoc // @Summary List CORS blacklist items // @Description Returns all CORS blacklist records. // @Tags Settings // @Security BearerAuth // @Produce json // @Success 200 {array} map[string]interface{} // @Failure 500 {object} map[string]string // @Router /api/v1/settings/cors/blacklist [get] func ListCorsBlacklists(c *gin.Context) { var items []models.CorsBlacklist if err := config.DB.Order("id DESC").Find(&items).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cors blacklists"}) return } c.JSON(http.StatusOK, items) } // CreateCorsBlacklist godoc // @Summary Create CORS blacklist item // @Description Creates a new blacklist origin. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param request body UpsertCorsBlacklistRequest true "cors blacklist payload" // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 409 {object} map[string]string // @Router /api/v1/settings/cors/blacklist [post] func CreateCorsBlacklist(c *gin.Context) { var req UpsertCorsBlacklistRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } item := models.CorsBlacklist{ Origin: req.Origin, Reason: req.Reason, IsActive: req.IsActive, CreatedBy: req.CreatedBy, } if err := config.DB.Create(&item).Error; err != nil { c.JSON(http.StatusConflict, gin.H{"error": "failed to create cors blacklist"}) return } c.JSON(http.StatusCreated, item) } // UpdateCorsBlacklist godoc // @Summary Update CORS blacklist item // @Description Updates a blacklist origin by id. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param id path int true "blacklist id" // @Param request body UpsertCorsBlacklistRequest true "cors blacklist payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 409 {object} map[string]string // @Router /api/v1/settings/cors/blacklist/{id} [put] func UpdateCorsBlacklist(c *gin.Context) { id, ok := parseID(c) if !ok { return } var req UpsertCorsBlacklistRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var item models.CorsBlacklist if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "cors blacklist not found"}) return } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch cors blacklist"}) return } if err := config.DB.Model(&item).Updates(map[string]any{ "origin": req.Origin, "reason": req.Reason, "is_active": req.IsActive, "created_by": req.CreatedBy, }).Error; err != nil { c.JSON(http.StatusConflict, gin.H{"error": "failed to update cors blacklist"}) return } if err := config.DB.First(&item, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch updated cors blacklist"}) return } c.JSON(http.StatusOK, item) } // DeleteCorsBlacklist godoc // @Summary Delete CORS blacklist item // @Description Deletes a blacklist origin by id. // @Tags Settings // @Security BearerAuth // @Produce json // @Param id path int true "blacklist id" // @Success 200 {object} map[string]string // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings/cors/blacklist/{id} [delete] func DeleteCorsBlacklist(c *gin.Context) { id, ok := parseID(c) if !ok { return } res := config.DB.Delete(&models.CorsBlacklist{}, id) if res.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete cors blacklist"}) return } if res.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "cors blacklist not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "cors blacklist deleted"}) } // ListRateLimits godoc // @Summary List rate limits // @Description Returns all rate-limit settings. // @Tags Settings // @Security BearerAuth // @Produce json // @Success 200 {array} map[string]interface{} // @Failure 500 {object} map[string]string // @Router /api/v1/settings/rate-limits [get] func ListRateLimits(c *gin.Context) { var items []models.RateLimitSetting if err := config.DB.Order("id DESC").Find(&items).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch rate limits"}) return } c.JSON(http.StatusOK, items) } // CreateRateLimit godoc // @Summary Create rate limit // @Description Creates a new rate-limit setting. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param request body UpsertRateLimitRequest true "rate limit payload" // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 409 {object} map[string]string // @Router /api/v1/settings/rate-limits [post] func CreateRateLimit(c *gin.Context) { var req UpsertRateLimitRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } item := models.RateLimitSetting{ Name: req.Name, Description: req.Description, MaxRequests: req.MaxRequests, WindowSeconds: req.WindowSeconds, IsActive: req.IsActive, UpdatedBy: req.UpdatedBy, } if err := config.DB.Create(&item).Error; err != nil { c.JSON(http.StatusConflict, gin.H{"error": "failed to create rate limit"}) return } c.JSON(http.StatusCreated, item) } // UpdateRateLimit godoc // @Summary Update rate limit // @Description Updates a rate-limit setting by id. // @Tags Settings // @Security BearerAuth // @Accept json // @Produce json // @Param id path int true "rate limit id" // @Param request body UpsertRateLimitRequest true "rate limit payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 409 {object} map[string]string // @Router /api/v1/settings/rate-limits/{id} [put] func UpdateRateLimit(c *gin.Context) { id, ok := parseID(c) if !ok { return } var req UpsertRateLimitRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var item models.RateLimitSetting if err := config.DB.First(&item, id).Error; errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "rate limit not found"}) return } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch rate limit"}) return } if err := config.DB.Model(&item).Updates(map[string]any{ "name": req.Name, "description": req.Description, "max_requests": req.MaxRequests, "window_seconds": req.WindowSeconds, "is_active": req.IsActive, "updated_by": req.UpdatedBy, }).Error; err != nil { c.JSON(http.StatusConflict, gin.H{"error": "failed to update rate limit"}) return } if err := config.DB.First(&item, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch updated rate limit"}) return } c.JSON(http.StatusOK, item) } // DeleteRateLimit godoc // @Summary Delete rate limit // @Description Deletes a rate-limit setting by id. // @Tags Settings // @Security BearerAuth // @Produce json // @Param id path int true "rate limit id" // @Success 200 {object} map[string]string // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/settings/rate-limits/{id} [delete] func DeleteRateLimit(c *gin.Context) { id, ok := parseID(c) if !ok { return } res := config.DB.Delete(&models.RateLimitSetting{}, id) if res.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete rate limit"}) return } if res.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "rate limit not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "rate limit deleted"}) }