feat(app state): settings are now global and get updated on the fly
This commit is contained in:
41
backend/internal/router/middleware/settings_Check.go
Normal file
41
backend/internal/router/middleware/settings_Check.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"lst.net/internal/system/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SettingCheckMiddleware(settingName string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Debug: Log the setting name we're checking
|
||||||
|
//log.Printf("Checking setting '%s' for path: %s", settingName, c.Request.URL.Path)
|
||||||
|
|
||||||
|
// Get the current setting value
|
||||||
|
value, err := settings.GetString(settingName)
|
||||||
|
if err != nil {
|
||||||
|
//log.Printf("Error getting setting '%s': %v", settingName, err)
|
||||||
|
c.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"error": "endpoint not available",
|
||||||
|
"details": "setting error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log the actual value received
|
||||||
|
//log.Printf("Setting '%s' value: '%s'", settingName, value)
|
||||||
|
|
||||||
|
// Changed condition to check for "1" (enable) instead of "0" (disable)
|
||||||
|
if value != "1" {
|
||||||
|
//log.Printf("Setting '%s' not enabled (value: '%s')", settingName, value)
|
||||||
|
c.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"error": "endpoint not available",
|
||||||
|
"details": "required feature is disabled",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log successful check
|
||||||
|
//log.Printf("Setting check passed for '%s'", settingName)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +1,39 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllSettings(db *gorm.DB) ([]map[string]interface{}, error) {
|
func GetAllSettings(db *gorm.DB) ([]map[string]interface{}, error) {
|
||||||
var settings []models.Settings
|
// var settings []models.Settings
|
||||||
result := db.Find(&settings)
|
// result := db.Find(&settings)
|
||||||
|
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
return nil, result.Error
|
// return nil, result.Error
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Function to convert struct to map with lowercase keys
|
// // Function to convert struct to map with lowercase keys
|
||||||
toLowercase := func(s models.Settings) map[string]interface{} {
|
// toLowercase := func(s models.Settings) map[string]interface{} {
|
||||||
t := reflect.TypeOf(s)
|
// t := reflect.TypeOf(s)
|
||||||
v := reflect.ValueOf(s)
|
// v := reflect.ValueOf(s)
|
||||||
|
|
||||||
data := make(map[string]interface{})
|
// data := make(map[string]interface{})
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
// for i := 0; i < t.NumField(); i++ {
|
||||||
field := strings.ToLower(t.Field(i).Name)
|
// field := strings.ToLower(t.Field(i).Name)
|
||||||
data[field] = v.Field(i).Interface()
|
// data[field] = v.Field(i).Interface()
|
||||||
}
|
// }
|
||||||
|
|
||||||
return data
|
// return data
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Convert each struct in settings slice to a map with lowercase keys
|
// // Convert each struct in settings slice to a map with lowercase keys
|
||||||
var lowercaseSettings []map[string]interface{}
|
// var lowercaseSettings []map[string]interface{}
|
||||||
for _, setting := range settings {
|
// for _, setting := range settings {
|
||||||
lowercaseSettings = append(lowercaseSettings, toLowercase(setting))
|
// lowercaseSettings = append(lowercaseSettings, toLowercase(setting))
|
||||||
}
|
// }
|
||||||
|
|
||||||
return lowercaseSettings, nil
|
convertedSettings := GetMap()
|
||||||
|
|
||||||
|
return convertedSettings, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/models"
|
"lst.net/internal/models"
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
@@ -41,41 +44,80 @@ var seedConfigData = []models.Settings{
|
|||||||
{Name: "scannerID", Value: `500`, Description: "What scanner id will we be using for the app", AppService: "logistics", Enabled: true},
|
{Name: "scannerID", Value: `500`, Description: "What scanner id will we be using for the app", AppService: "logistics", Enabled: true},
|
||||||
{Name: "scannerPort", Value: `50002`, Description: "What port instance will we be using?", AppService: "logistics", Enabled: true},
|
{Name: "scannerPort", Value: `50002`, Description: "What port instance will we be using?", AppService: "logistics", Enabled: true},
|
||||||
{Name: "stagingReturnLocations", Value: `30125,31523`, Description: "What are the staging location IDs we will use to select from. seperated by commas", AppService: "logistics", Enabled: true},
|
{Name: "stagingReturnLocations", Value: `30125,31523`, Description: "What are the staging location IDs we will use to select from. seperated by commas", AppService: "logistics", Enabled: true},
|
||||||
|
{Name: "testingApiFunction", Value: `1`, Description: "This is a test to validate if we set to 0 it will actaully not allow the route", AppService: "logistics", Enabled: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
func SeedSettings(db *gorm.DB, log *logger.CustomLogger) error {
|
func SeedSettings(db *gorm.DB, log *logger.CustomLogger) error {
|
||||||
|
|
||||||
for _, cfg := range seedConfigData {
|
for _, cfg := range seedConfigData {
|
||||||
var existing models.Settings
|
var existing models.Settings
|
||||||
// Try to find config by unique Name
|
if err := db.Unscoped().Where("name = ?", cfg.Name).First(&existing).Error; err == nil {
|
||||||
result := db.Where("Name =?", cfg.Name).First(&existing)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
if existing.DeletedAt.Valid {
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
// Undelete by setting DeletedAt to NULL
|
||||||
// not here lets add it
|
if err := db.Unscoped().Model(&existing).Update("DeletedAt", gorm.DeletedAt{}).Error; err != nil {
|
||||||
|
log.Error("Failed to undelete settings", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
if err := db.Create(&cfg).Error; err != nil {
|
if err := db.Create(&cfg).Error; err != nil {
|
||||||
log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
||||||
"name": cfg.Name,
|
"name": cfg.Name,
|
||||||
"error": err,
|
"error": err,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
//log.Printf("Seeded new config: %s", cfg.Name)
|
|
||||||
} else {
|
|
||||||
// Some other error
|
|
||||||
return result.Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // Try to find config by unique Name
|
||||||
|
// result := db.Where("Name =?", cfg.Name).First(&existing)
|
||||||
|
|
||||||
|
// if result.Error != nil {
|
||||||
|
// if result.Error == gorm.ErrRecordNotFound && cfg.Enabled {
|
||||||
|
// // not here lets add it
|
||||||
|
|
||||||
|
// if err := db.Create(&cfg).Error; err != nil && !existing.DeletedAt.Valid {
|
||||||
|
// log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
||||||
|
// "name": cfg.Name,
|
||||||
|
// "error": err,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //log.Printf("Seeded new config: %s", cfg.Name)
|
||||||
|
// } else {
|
||||||
|
// // Some other error
|
||||||
|
// return result.Error
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
// only update the fields we want to update.
|
// remove the setting if we change to false this will help with future proofing our seeder in the event we need to add it back
|
||||||
existing.Description = cfg.Description
|
if cfg.Enabled {
|
||||||
existing.Name = cfg.Name
|
existing.Description = cfg.Description
|
||||||
existing.AppService = cfg.AppService
|
existing.Name = cfg.Name
|
||||||
if err := db.Save(&existing).Error; err != nil {
|
existing.AppService = cfg.AppService
|
||||||
log.Error("Failed to update ettings.", "settings", map[string]interface{}{
|
if err := db.Save(&existing).Error; err != nil {
|
||||||
"name": cfg.Name,
|
log.Error("Failed to update ettings.", "settings", map[string]interface{}{
|
||||||
"error": err,
|
"name": cfg.Name,
|
||||||
})
|
"error": err,
|
||||||
return err
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we delete the setting so its no longer there
|
||||||
|
if err := db.Delete(&existing).Error; err != nil {
|
||||||
|
log.Error("Failed to delete ettings.", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settingDelete := fmt.Sprintf("Updated existing config: %s", cfg.Name)
|
||||||
|
log.Info(settingDelete, "settings", map[string]interface{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("Updated existing config: %s", cfg.Name)
|
//log.Printf("Updated existing config: %s", cfg.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
backend/internal/system/settings/settings_states.go
Normal file
110
backend/internal/system/settings/settings_states.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Global state
|
||||||
|
appSettings []models.Settings
|
||||||
|
appSettingsLock sync.RWMutex
|
||||||
|
dbInstance *gorm.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize loads settings into memory at startup
|
||||||
|
func Initialize(db *gorm.DB) error {
|
||||||
|
dbInstance = db
|
||||||
|
return Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh reloads settings from DB (call after updates)
|
||||||
|
func Refresh() error {
|
||||||
|
appSettingsLock.Lock()
|
||||||
|
defer appSettingsLock.Unlock()
|
||||||
|
|
||||||
|
var settings []models.Settings
|
||||||
|
if err := dbInstance.Find(&settings).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appSettings = settings
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns a thread-safe copy of settings
|
||||||
|
func GetAll() []models.Settings {
|
||||||
|
appSettingsLock.RLock()
|
||||||
|
defer appSettingsLock.RUnlock()
|
||||||
|
|
||||||
|
// Return copy to prevent external modification
|
||||||
|
copied := make([]models.Settings, len(appSettings))
|
||||||
|
copy(copied, appSettings)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMap returns settings as []map[string]interface{}
|
||||||
|
func GetMap() []map[string]interface{} {
|
||||||
|
return convertToMap(GetAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToMap helper (move your existing conversion logic here)
|
||||||
|
func convertToMap(settings []models.Settings) []map[string]interface{} {
|
||||||
|
toLowercase := func(s models.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetString(name string) (string, error) {
|
||||||
|
appSettingsLock.RLock()
|
||||||
|
defer appSettingsLock.RUnlock()
|
||||||
|
|
||||||
|
for _, s := range appSettings {
|
||||||
|
if s.Name == name { // assuming your model has a "Name" field
|
||||||
|
fmt.Println(s.Value)
|
||||||
|
return s.Value, nil // assuming your model has a "Value" field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("setting not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetTemp(name, value string) {
|
||||||
|
appSettingsLock.Lock()
|
||||||
|
defer appSettingsLock.Unlock()
|
||||||
|
|
||||||
|
for i, s := range appSettings {
|
||||||
|
if s.Name == name {
|
||||||
|
appSettings[i].Value = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If not found, add new setting
|
||||||
|
appSettings = append(appSettings, models.Settings{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -40,8 +40,16 @@ func UpdateSetting(log *logger.CustomLogger, db *gorm.DB, id string, input Setti
|
|||||||
return settingUpdate.Error
|
return settingUpdate.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := Refresh(); err != nil {
|
||||||
|
log.Error("There was an error refreshing the settings after a setting update", "settings", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("The setting was just updated", "settings", map[string]interface{}{
|
log.Info("The setting was just updated", "settings", map[string]interface{}{
|
||||||
"name": settingUpdate.Name,
|
"id": id,
|
||||||
|
"name": cfg.Name,
|
||||||
|
"updated": updates,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user