From a0aa75c5a0b4a6e3a10b88bbcccf43d096e532b4 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Wed, 30 Jul 2025 19:35:13 -0500 Subject: [PATCH] refactor(settings): changed config to settings and added in the update method for this as well strict fields on the updates so we can only change what we want in here --- .../cmd/services/system/settings/settings.go | 49 ++++++++---- backend/go.mod | 2 +- backend/main.go | 2 +- backend/utils/db/settings.go | 76 ++++++++++++++++++- backend/utils/inputs/settingsInput.go | 8 ++ 5 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 backend/utils/inputs/settingsInput.go diff --git a/backend/cmd/services/system/settings/settings.go b/backend/cmd/services/system/settings/settings.go index 8936fad..5329103 100644 --- a/backend/cmd/services/system/settings/settings.go +++ b/backend/cmd/services/system/settings/settings.go @@ -1,25 +1,21 @@ package settings import ( + "encoding/json" + "github.com/gin-gonic/gin" "lst.net/utils/db" + "lst.net/utils/inputs" logging "lst.net/utils/logger" ) -type SettingUpdateInput struct { - Description *string `json:"description"` - Value *string `json:"value"` - Enabled *bool `json:"enabled"` - AppService *string `json:"app_service"` -} - func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) { // seed the db on start up db.SeedConfigs(db.DB) s := l.Group(baseUrl + "/api/v1") s.GET("/settings", getSettings) - s.PATCH("/settings", updateSettingById) + s.PATCH("/settings/:id", updateSettingById) } func getSettings(c *gin.Context) { @@ -39,6 +35,7 @@ func getSettings(c *gin.Context) { "error": err, }) c.JSON(500, gin.H{"message": "There was an error getting the settings", "error": err}) + return } c.JSON(200, gin.H{"message": "Current settings", "data": configs}) @@ -46,18 +43,44 @@ func getSettings(c *gin.Context) { func updateSettingById(c *gin.Context) { logger := logging.New() - var setting SettingUpdateInput + settingID := c.Param("id") - err := c.ShouldBindBodyWithJSON(&setting) + if settingID == "" { + c.JSON(500, gin.H{"message": "Invalid data"}) + logger.Error("Invalid data", "system", map[string]interface{}{ + "endpoint": "/api/v1/settings", + "client_ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + }) + return + } + var setting inputs.SettingUpdateInput - if err != nil { - c.JSON(500, gin.H{"message": "Internal Server Error"}) - logger.Error("Current Settings", "system", map[string]interface{}{ + //err := c.ShouldBindBodyWithJSON(&setting) + + decoder := json.NewDecoder(c.Request.Body) // more strict and will force us to have correct data + decoder.DisallowUnknownFields() + + if err := decoder.Decode(&setting); err != nil { + c.JSON(400, gin.H{"message": "Invalid request body", "error": err.Error()}) + logger.Error("Invalid request body", "system", map[string]interface{}{ "endpoint": "/api/v1/settings", "client_ip": c.ClientIP(), "user_agent": c.Request.UserAgent(), "error": err, }) + return + } + + if err := db.UpdateConfig(db.DB, settingID, setting); err != nil { + c.JSON(500, gin.H{"message": "Failed to update setting", "error": err.Error()}) + logger.Error("Failed to update setting", "system", map[string]interface{}{ + "endpoint": "/api/v1/settings", + "client_ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + "error": err, + }) + return } c.JSON(200, gin.H{"message": "Setting was just updated", "data": setting}) diff --git a/backend/go.mod b/backend/go.mod index d32324a..6767470 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,7 +8,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.34.0 - github.com/swaggo/swag v1.16.5 + github.com/swaggo/swag v1.16.6 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.30.0 ) diff --git a/backend/main.go b/backend/main.go index b8c7075..dbc501c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -27,7 +27,7 @@ import ( "lst.net/cmd/services/system/settings" "lst.net/cmd/services/websocket" - _ "lst.net/docs" + // _ "lst.net/docs" "lst.net/utils/db" logging "lst.net/utils/logger" diff --git a/backend/utils/db/settings.go b/backend/utils/db/settings.go index 86932d6..1042f70 100644 --- a/backend/utils/db/settings.go +++ b/backend/utils/db/settings.go @@ -2,14 +2,17 @@ package db import ( "log" + "reflect" + "strings" "time" "github.com/google/uuid" "gorm.io/gorm" + "lst.net/utils/inputs" ) type Settings struct { - ConfigID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"` + SettingID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"` Name string `gorm:"uniqueIndex;not null"` Description string `gorm:"type:text"` Value string `gorm:"not null"` @@ -89,11 +92,76 @@ func SeedConfigs(db *gorm.DB) error { return nil } -func GetAllConfigs(db *gorm.DB) ([]Settings, error) { +func GetAllConfigs(db *gorm.DB) ([]map[string]interface{}, error) { var settings []Settings - result := db.Find(&settings) - return settings, result.Error + if result.Error != nil { + return nil, result.Error + } + // Function to convert struct to map with lowercase keys + toLowercase := func(s Settings) map[string]interface{} { + t := reflect.TypeOf(s) + v := reflect.ValueOf(s) + + data := make(map[string]interface{}) + + for i := 0; i < t.NumField(); i++ { + field := strings.ToLower(t.Field(i).Name) + data[field] = v.Field(i).Interface() + } + + return data + } + + // Convert each struct in settings slice to a map with lowercase keys + var lowercaseSettings []map[string]interface{} + for _, setting := range settings { + lowercaseSettings = append(lowercaseSettings, toLowercase(setting)) + } + + return lowercaseSettings, nil +} + +func UpdateConfig(db *gorm.DB, id string, input inputs.SettingUpdateInput) error { + var cfg Settings + if err := db.Where("setting_id =?", id).First(&cfg).Error; err != nil { + return err + } + + updates := map[string]interface{}{} + + if input.Description != nil { + updates["description"] = *input.Description + } + if input.Value != nil { + updates["value"] = *input.Value + } + if input.Enabled != nil { + updates["enabled"] = *input.Enabled + } + if input.AppService != nil { + updates["app_service"] = *input.AppService + } + + if len(updates) == 0 { + return nil // nothing to update + } + + return db.Model(&cfg).Updates(updates).Error +} + +func DeleteConfig(db *gorm.DB, id uint) error { + // Soft delete by ID + return db.Delete(&Settings{}, id).Error +} + +func RestoreConfig(db *gorm.DB, id uint) error { + var cfg Settings + if err := db.Unscoped().First(&cfg, id).Error; err != nil { + return err + } + cfg.DeletedAt = gorm.DeletedAt{} + return db.Unscoped().Save(&cfg).Error } diff --git a/backend/utils/inputs/settingsInput.go b/backend/utils/inputs/settingsInput.go new file mode 100644 index 0000000..3174c3f --- /dev/null +++ b/backend/utils/inputs/settingsInput.go @@ -0,0 +1,8 @@ +package inputs + +type SettingUpdateInput struct { + Description *string `json:"description"` + Value *string `json:"value"` + Enabled *bool `json:"enabled"` + AppService *string `json:"app_service"` +}