diff --git a/.gitignore b/.gitignore index 635c498..f04d17b 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,4 @@ scripts/resetDanger.js LstWrapper/Program_vite_as_Static.txt LstWrapper/Program_proxy_backend.txt scripts/stopPool.go +backend_bad_practice \ No newline at end of file diff --git a/backend/cmd/services/logging/createLog.go b/backend/cmd/services/logging/createLog.go deleted file mode 100644 index e2f3d93..0000000 --- a/backend/cmd/services/logging/createLog.go +++ /dev/null @@ -1,111 +0,0 @@ -package loggingx - -import ( - "encoding/json" - "fmt" - "os" - "strings" - "time" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "lst.net/utils/db" -) - -type CustomLogger struct { - consoleLogger zerolog.Logger -} - -// New creates a configured CustomLogger. -func New() *CustomLogger { - // Colorized console output - consoleWriter := zerolog.ConsoleWriter{ - Out: os.Stderr, - TimeFormat: "2006-01-02 15:04:05", - } - - return &CustomLogger{ - consoleLogger: zerolog.New(consoleWriter). - With(). - Timestamp(). - Logger(), - } -} - -func PrettyFormat(level, message string, metadata map[string]interface{}) string { - timestamp := time.Now().Format("2006-01-02 15:04:05") - base := fmt.Sprintf("[%s] %s| Message: %s", strings.ToUpper(level), timestamp, message) - - if len(metadata) > 0 { - metaJSON, _ := json.Marshal(metadata) - return fmt.Sprintf("%s | Metadata: %s", base, string(metaJSON)) - } - return base -} - -func (l *CustomLogger) logToPostgres(level, message, service string, metadata map[string]interface{}) { - err := db.CreateLog(level, message, service, metadata) - if err != nil { - // Fallback to console if DB fails - log.Error().Err(err).Msg("Failed to write log to PostgreSQL") - } -} - -// --- Level-Specific Methods --- - -func (l *CustomLogger) Info(message, service string, fields map[string]interface{}) { - l.consoleLogger.Info().Fields(fields).Msg(message) - l.logToPostgres("info", message, service, fields) - - PostLog(PrettyFormat("info", message, fields)) // Broadcast pretty message -} - -func (l *CustomLogger) Warn(message, service string, fields map[string]interface{}) { - l.consoleLogger.Error().Fields(fields).Msg(message) - l.logToPostgres("warn", message, service, fields) - - PostLog(PrettyFormat("warn", message, fields)) // Broadcast pretty message - - // Custom logic for errors (e.g., alerting) - if len(fields) > 0 { - l.consoleLogger.Warn().Msg("Additional error context captured") - } -} - -func (l *CustomLogger) Error(message, service string, fields map[string]interface{}) { - l.consoleLogger.Error().Fields(fields).Msg(message) - l.logToPostgres("error", message, service, fields) - - PostLog(PrettyFormat("error", message, fields)) // Broadcast pretty message - - // Custom logic for errors (e.g., alerting) - if len(fields) > 0 { - l.consoleLogger.Warn().Msg("Additional error context captured") - } -} - -func (l *CustomLogger) Panic(message, service string, fields map[string]interface{}) { - // Log to console (colored, with fields) - l.consoleLogger.Error(). - Str("service", service). - Fields(fields). - Msg(message + " (PANIC)") // Explicitly mark as panic - - // Log to PostgreSQL (sync to ensure it's saved before crashing) - err := db.CreateLog("panic", message, service, fields) // isCritical=true - if err != nil { - l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL") - } - - // Additional context (optional) - if len(fields) > 0 { - l.consoleLogger.Warn().Msg("Additional panic context captured") - } - - panic(message) -} - -func (l *CustomLogger) Debug(message, service string, fields map[string]interface{}) { - l.consoleLogger.Debug().Fields(fields).Msg(message) - l.logToPostgres("debug", message, service, fields) -} diff --git a/backend/cmd/services/logging/logger.go b/backend/cmd/services/logging/logger.go deleted file mode 100644 index 2c835dd..0000000 --- a/backend/cmd/services/logging/logger.go +++ /dev/null @@ -1,12 +0,0 @@ -package loggingx - -import ( - "github.com/gin-gonic/gin" -) - -func RegisterLoggerRoutes(l *gin.Engine, baseUrl string) { - - configGroup := l.Group(baseUrl + "/api/logger") - configGroup.GET("/logs", GetLogs) - -} diff --git a/backend/cmd/services/logging/logs_get_route.go b/backend/cmd/services/logging/logs_get_route.go deleted file mode 100644 index 7393f23..0000000 --- a/backend/cmd/services/logging/logs_get_route.go +++ /dev/null @@ -1,148 +0,0 @@ -package loggingx - -import ( - "fmt" - "log" - "net/http" - "sync" - - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" -) - -var ( - logChannel = make(chan string, 1000) // Buffered channel for new logs - wsClients = make(map[*websocket.Conn]bool) - wsClientsMux sync.Mutex -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - //fmt.Println("Origin:", r.Header.Get("Origin")) - return true - }, -} - -// PostLog sends a new log to all connected SSE clients -func PostLog(message string) { - // Send to SSE channel - select { - case logChannel <- message: - log.Printf("Published to SSE: %s", message) - default: - log.Printf("DROPPED SSE message (channel full): %s", message) - } - - wsClientsMux.Lock() - defer wsClientsMux.Unlock() - for client := range wsClients { - err := client.WriteMessage(websocket.TextMessage, []byte(message)) - if err != nil { - client.Close() - delete(wsClients, client) - } - } -} - -func GetLogs(c *gin.Context) { - // Check if it's a WebSocket request - if websocket.IsWebSocketUpgrade(c.Request) { - handleWebSocket(c) - return - } - - // Otherwise, handle as SSE - handleSSE(c) -} - -func handleSSE(c *gin.Context) { - log := New() - log.Info("SSE connection established", "logger", map[string]interface{}{ - "endpoint": "/api/logger/logs", - "client_ip": c.ClientIP(), - "user_agent": c.Request.UserAgent(), - }) - - c.Header("Access-Control-Allow-Origin", "*") - c.Header("Access-Control-Allow-Credentials", "true") - c.Header("Access-Control-Allow-Headers", "Content-Type") - c.Header("Access-Control-Allow-Methods", "GET, OPTIONS") - - // Handle preflight requests - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(204) - return - } - - c.Header("Content-Type", "text/event-stream") - c.Header("Cache-Control", "no-cache") - c.Header("Connection", "keep-alive") - - flusher, ok := c.Writer.(http.Flusher) - if !ok { - log.Info("SSE not supported", "logger", nil) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - notify := c.Writer.CloseNotify() - - for { - select { - case <-notify: - log.Info("SSE client disconnected", "logger", nil) - return - case message := <-logChannel: - fmt.Fprintf(c.Writer, "data: %s\n\n", message) - flusher.Flush() - } - } -} - -func handleWebSocket(c *gin.Context) { - log := New() - log.Info("WebSocket connection established", "logger", map[string]interface{}{ - "endpoint": "/api/logger/logs", - "client_ip": c.ClientIP(), - "user_agent": c.Request.UserAgent(), - }) - conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - log.Error("WebSocket upgrade failed", "logger", map[string]interface{}{ - "error": err.Error(), - }) - return - } - - // Register client - wsClientsMux.Lock() - wsClients[conn] = true - wsClientsMux.Unlock() - - defer func() { - wsClientsMux.Lock() - delete(wsClients, conn) - wsClientsMux.Unlock() - conn.Close() - - log.Info("WebSocket client disconnected", "logger", map[string]interface{}{}) - }() - - // Keep connection alive (or optionally echo, or wait for pings) - for { - // Can just read to keep the connection alive - if _, _, err := conn.NextReader(); err != nil { - break - } - } -} - -// func sendRecentLogs(conn *websocket.Conn) { -// // Implement your logic to get recent logs from DB or buffer -// recentLogs := getLast20Logs() -// for _, log := range recentLogs { -// conn.WriteMessage(websocket.TextMessage, []byte(log)) -// } -// } diff --git a/backend/cmd/services/system/servers/servers.go b/backend/cmd/services/system/servers/servers.go deleted file mode 100644 index 84c4cc0..0000000 --- a/backend/cmd/services/system/servers/servers.go +++ /dev/null @@ -1 +0,0 @@ -package servers diff --git a/backend/cmd/services/websocket/label.go b/backend/cmd/services/websocket/label.go deleted file mode 100644 index 1cbed18..0000000 --- a/backend/cmd/services/websocket/label.go +++ /dev/null @@ -1,24 +0,0 @@ -package websocket - -import logging "lst.net/utils/logger" - -func LabelProcessor(broadcaster chan logging.Message) { - // Initialize any label-specific listeners - // This could listen to a different PG channel or process differently - - // for { - // select { - // // Implementation depends on your label data source - // // Example: - // case labelEvent := <-someLabelChannel: - // broadcaster <- logging.Message{ - // Channel: "labels", - // Data: labelEvent.Data, - // Meta: map[string]interface{}{ - // "label": labelEvent.Label, - // "type": labelEvent.Type, - // }, - // } - // } - // } -} diff --git a/backend/go.mod b/backend/go.mod index 6767470..7a9aa46 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -5,42 +5,35 @@ go 1.24.3 require ( github.com/gin-contrib/cors v1.7.6 github.com/gin-gonic/gin v1.10.1 - github.com/gorilla/websocket v1.5.3 + github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.34.0 - github.com/swaggo/swag v1.16.6 gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.30.0 + gorm.io/gorm v1.30.1 ) require ( - github.com/KyleBanks/depth v1.2.1 // indirect github.com/bytedance/sonic v1.13.3 // indirect - github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.5 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.10.9 - github.com/mailru/easyjson v0.9.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -48,14 +41,12 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect - golang.org/x/arch v0.19.0 // indirect + golang.org/x/arch v0.18.0 // indirect golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.41.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.35.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/backend/utils/db/db.go b/backend/internal/db/db.go similarity index 88% rename from backend/utils/db/db.go rename to backend/internal/db/db.go index 0a480aa..873c4b5 100644 --- a/backend/utils/db/db.go +++ b/backend/internal/db/db.go @@ -6,12 +6,11 @@ import ( "gorm.io/driver/postgres" "gorm.io/gorm" + "lst.net/internal/models" ) var DB *gorm.DB -type JSONB map[string]interface{} - type DBConfig struct { DB *gorm.DB DSN string @@ -37,7 +36,8 @@ func InitDB() (*DBConfig, error) { // ensures we have the uuid stuff setup properly DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`) - err = DB.AutoMigrate(&Log{}, &Settings{}, &ClientRecord{}) + err = DB.AutoMigrate(&models.Log{}, &models.Settings{}) // &ClientRecord{}, &Servers{} + if err != nil { return nil, fmt.Errorf("failed to auto-migrate models: %v", err) } diff --git a/backend/utils/db/settings.go b/backend/internal/db/settings_seed.go similarity index 69% rename from backend/utils/db/settings.go rename to backend/internal/db/settings_seed.go index 1042f70..b4bede2 100644 --- a/backend/utils/db/settings.go +++ b/backend/internal/db/settings_seed.go @@ -2,28 +2,12 @@ package db import ( "log" - "reflect" - "strings" - "time" - "github.com/google/uuid" "gorm.io/gorm" - "lst.net/utils/inputs" + "lst.net/internal/models" ) -type Settings struct { - 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"` - Enabled bool `gorm:"default:true"` - AppService string `gorm:"default:system"` - CreatedAt time.Time `gorm:"index"` - UpdatedAt time.Time `gorm:"index"` - DeletedAt gorm.DeletedAt `gorm:"index"` -} - -var seedConfigData = []Settings{ +var seedConfigData = []models.Settings{ {Name: "serverPort", Description: "The port the server will listen on if not running in docker", Value: "4000", Enabled: true, AppService: "server"}, {Name: "server", Description: "The server we will use when connecting to the alplaprod sql", Value: "usmcd1vms006", Enabled: true, AppService: "server"}, {Name: "timezone", Value: "America/Chicago", Description: "What time zone is the server in this is used for cronjobs and some other time stuff", AppService: "server", Enabled: true}, @@ -60,10 +44,10 @@ var seedConfigData = []Settings{ {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}, } -func SeedConfigs(db *gorm.DB) error { +func SeedSettings(db *gorm.DB) error { for _, cfg := range seedConfigData { - var existing Settings + var existing models.Settings // Try to find config by unique Name result := db.Where("Name =?", cfg.Name).First(&existing) @@ -91,77 +75,3 @@ func SeedConfigs(db *gorm.DB) error { return nil } - -func GetAllConfigs(db *gorm.DB) ([]map[string]interface{}, error) { - var settings []Settings - result := db.Find(&settings) - - 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/internal/models/logs.go b/backend/internal/models/logs.go new file mode 100644 index 0000000..e2ae75e --- /dev/null +++ b/backend/internal/models/logs.go @@ -0,0 +1,21 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "gorm.io/gorm" + "lst.net/pkg" +) + +type Log struct { + LogID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"` + Level string `gorm:"size:10;not null"` // "info", "error", etc. + Message string `gorm:"not null"` + Service string `gorm:"size:50"` + Metadata pkg.JSONB `gorm:"type:jsonb"` // fields (e.g., {"user_id": 123}) + CreatedAt time.Time `gorm:"index"` + Checked bool `gorm:"type:boolean;default:false"` + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} diff --git a/backend/internal/models/servers.go b/backend/internal/models/servers.go new file mode 100644 index 0000000..06c825a --- /dev/null +++ b/backend/internal/models/servers.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "lst.net/pkg" +) + +type Servers struct { + ServerID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"` + ServerName string `gorm:"size:50;not null"` + ServerDNS string `gorm:"size:25;not null"` + PlantToken string `gorm:"size:10;not null"` + IPAddress string `gorm:"size:16;not null"` + GreatPlainsPlantCode int `gorm:"size:10;not null"` + StreetAddress string `gorm:"size:255;not null"` + CityState string `gorm:"size:50;not null"` + Zipcode int `gorm:"size:13;not null"` + ContactEmail string `gorm:"size:255"` + ContactPhone string `gorm:"size:255"` + CustomerTiAcc string `gorm:"size:255"` + LstServerPort int `gorm:"size:255; not null"` + Active bool `gorm:"type:boolean;default:true"` + LerverLoc string `gorm:"size:255:not null"` + LastUpdated time.Time `gorm:"index"` + ShippingHours pkg.JSONB `gorm:"type:jsonb;default:'[{\"early\": \"06:30\", \"late\": \"23:00\"}]'"` + TiPostTime pkg.JSONB `gorm:"type:jsonb;default:'[{\"from\": \"24\", \"to\": \"24\"}]'"` + OtherSettings pkg.JSONB `gorm:"type:jsonb;default:'[{\"specialInstructions\": \"something for ti\", \"active\": false}]'"` + IsUpgrading bool `gorm:"type:boolean;default:true"` + AlplaProdApiKey string `gorm:"size:255"` +} diff --git a/backend/internal/models/settings.go b/backend/internal/models/settings.go new file mode 100644 index 0000000..7c1f4cb --- /dev/null +++ b/backend/internal/models/settings.go @@ -0,0 +1,20 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +type Settings struct { + 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"` + Enabled bool `gorm:"default:true"` + AppService string `gorm:"default:system"` + CreatedAt time.Time `gorm:"index"` + UpdatedAt time.Time `gorm:"index"` + DeletedAt gorm.DeletedAt `gorm:"index"` +} diff --git a/backend/utils/db/ws_clients.go b/backend/internal/models/ws_client.go similarity index 87% rename from backend/utils/db/ws_clients.go rename to backend/internal/models/ws_client.go index e42b3f8..317143a 100644 --- a/backend/utils/db/ws_clients.go +++ b/backend/internal/models/ws_client.go @@ -1,9 +1,10 @@ -package db +package models import ( "time" "github.com/google/uuid" + "lst.net/pkg" ) type ClientRecord struct { @@ -13,7 +14,7 @@ type ClientRecord struct { UserAgent string `gorm:"size:255"` ConnectedAt time.Time `gorm:"index"` LastHeartbeat time.Time `gorm:"column:last_heartbeat"` - Channels JSONB `gorm:"type:jsonb"` + Channels pkg.JSONB `gorm:"type:jsonb"` CreatedAt time.Time UpdatedAt time.Time DisconnectedAt *time.Time `gorm:"column:disconnected_at"` diff --git a/backend/cmd/services/websocket/channel_manager.go b/backend/internal/notifications/ws/ws_channel_manager.go similarity index 94% rename from backend/cmd/services/websocket/channel_manager.go rename to backend/internal/notifications/ws/ws_channel_manager.go index 6f81650..404a193 100644 --- a/backend/cmd/services/websocket/channel_manager.go +++ b/backend/internal/notifications/ws/ws_channel_manager.go @@ -1,4 +1,4 @@ -package websocket +package ws import ( "encoding/json" @@ -6,7 +6,7 @@ import ( "strings" "sync" - logging "lst.net/utils/logger" + "lst.net/pkg/logger" ) type Channel struct { @@ -82,8 +82,8 @@ func CleanupChannels() { channels = make(map[string]*Channel) } -func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Channel) { - logger := logging.New() +func StartBroadcasting(broadcaster chan logger.Message, channels map[string]*Channel) { + logger := logger.New() go func() { for msg := range broadcaster { switch msg.Channel { @@ -147,7 +147,7 @@ func (ch *Channel) RunChannel() { ch.lock.Unlock() case message := <-ch.Broadcast: - var msg logging.Message + var msg logger.Message if err := json.Unmarshal(message, &msg); err != nil { continue } diff --git a/backend/cmd/services/websocket/ws_client.go b/backend/internal/notifications/ws/ws_client.go similarity index 94% rename from backend/cmd/services/websocket/ws_client.go rename to backend/internal/notifications/ws/ws_client.go index 21e7f8c..fccdc56 100644 --- a/backend/cmd/services/websocket/ws_client.go +++ b/backend/internal/notifications/ws/ws_client.go @@ -1,4 +1,4 @@ -package websocket +package ws import ( "fmt" @@ -9,8 +9,10 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" - "lst.net/utils/db" - logging "lst.net/utils/logger" + "lst.net/internal/db" + "lst.net/internal/models" + "lst.net/pkg" + "lst.net/pkg/logger" ) var ( @@ -43,11 +45,11 @@ func (c *Client) SaveToDB() { channels[ch] = true } - clientRecord := &db.ClientRecord{ + clientRecord := &models.ClientRecord{ APIKey: c.APIKey, IPAddress: c.IPAddress, UserAgent: c.UserAgent, - Channels: db.JSONB(channels), + Channels: pkg.JSONB(channels), ConnectedAt: time.Now(), LastHeartbeat: time.Now(), } @@ -62,12 +64,12 @@ func (c *Client) SaveToDB() { } func (c *Client) MarkDisconnected() { - logger := logging.New() + logger := logger.New() clientData := fmt.Sprintf("Client %v just lefts us", c.ClientID) logger.Info(clientData, "websocket", map[string]interface{}{}) now := time.Now() - res := db.DB.Model(&db.ClientRecord{}). + res := db.DB.Model(&models.ClientRecord{}). Where("client_id = ?", c.ClientID). Updates(map[string]interface{}{ "disconnected_at": &now, @@ -136,7 +138,7 @@ const ( ) func (c *Client) StartHeartbeat() { - logger := logging.New() + logger := logger.New() log.Println("Started hearbeat") ticker := time.NewTicker(pingPeriod) defer ticker.Stop() @@ -156,7 +158,7 @@ func (c *Client) StartHeartbeat() { } now := time.Now() - res := db.DB.Model(&db.ClientRecord{}). + res := db.DB.Model(&models.ClientRecord{}). Where("client_id = ?", c.ClientID). Updates(map[string]interface{}{ "last_heartbeat": &now, @@ -220,12 +222,12 @@ func (c *Client) IsActive() bool { func (c *Client) updateHeartbeat() { //fmt.Println("Updating heatbeat") now := time.Now() - logger := logging.New() + logger := logger.New() //fmt.Printf("Updating heartbeat for client: %s at %v\n", c.ClientID, now) //db.DB = db.DB.Debug() - res := db.DB.Model(&db.ClientRecord{}). + res := db.DB.Model(&models.ClientRecord{}). Where("client_id = ?", c.ClientID). Updates(map[string]interface{}{ "last_heartbeat": &now, // Explicit format diff --git a/backend/cmd/services/websocket/ws_handler.go b/backend/internal/notifications/ws/ws_handler.go similarity index 99% rename from backend/cmd/services/websocket/ws_handler.go rename to backend/internal/notifications/ws/ws_handler.go index b8cb51c..c233455 100644 --- a/backend/cmd/services/websocket/ws_handler.go +++ b/backend/internal/notifications/ws/ws_handler.go @@ -1,4 +1,4 @@ -package websocket +package ws import ( "encoding/json" diff --git a/backend/cmd/services/websocket/log_services.go b/backend/internal/notifications/ws/ws_log_service.go similarity index 76% rename from backend/cmd/services/websocket/log_services.go rename to backend/internal/notifications/ws/ws_log_service.go index e1f6881..d453aa9 100644 --- a/backend/cmd/services/websocket/log_services.go +++ b/backend/internal/notifications/ws/ws_log_service.go @@ -1,4 +1,4 @@ -package websocket +package ws // setup the notifiyer @@ -23,13 +23,13 @@ import ( "time" "github.com/lib/pq" - logging "lst.net/utils/logger" + "lst.net/pkg/logger" ) -func LogServices(broadcaster chan logging.Message) { - logger := logging.New() +func LogServices(broadcaster chan logger.Message) { + log := logger.New() - logger.Info("[LogServices] started - single channel for all logs", "websocket", map[string]interface{}{}) + log.Info("[LogServices] started - single channel for all logs", "websocket", map[string]interface{}{}) dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DB_HOST"), @@ -42,7 +42,7 @@ func LogServices(broadcaster chan logging.Message) { listener := pq.NewListener(dsn, 10*time.Second, time.Minute, nil) err := listener.Listen("new_log") if err != nil { - logger.Panic("Failed to LISTEN on new_log", "logger", map[string]interface{}{ + log.Panic("Failed to LISTEN on new_log", "logger", map[string]interface{}{ "error": err.Error(), }) } @@ -54,14 +54,14 @@ func LogServices(broadcaster chan logging.Message) { if notify != nil { var logData map[string]interface{} if err := json.Unmarshal([]byte(notify.Extra), &logData); err != nil { - logger.Error("Failed to unmarshal notification payload", "logger", map[string]interface{}{ + log.Error("Failed to unmarshal notification payload", "logger", map[string]interface{}{ "error": err.Error(), }) continue } // Always send to logServices channel - broadcaster <- logging.Message{ + broadcaster <- logger.Message{ Channel: "logServices", Data: logData, Meta: map[string]interface{}{ diff --git a/backend/cmd/services/websocket/routes.go b/backend/internal/notifications/ws/ws_routes.go similarity index 91% rename from backend/cmd/services/websocket/routes.go rename to backend/internal/notifications/ws/ws_routes.go index dbb5c41..ca4b9bd 100644 --- a/backend/cmd/services/websocket/routes.go +++ b/backend/internal/notifications/ws/ws_routes.go @@ -1,14 +1,14 @@ -package websocket +package ws import ( "net/http" "github.com/gin-gonic/gin" - logging "lst.net/utils/logger" + "lst.net/pkg/logger" ) var ( - broadcaster = make(chan logging.Message) + broadcaster = make(chan logger.Message) ) func RegisterSocketRoutes(r *gin.Engine, base_url string) { diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go new file mode 100644 index 0000000..5fa3b56 --- /dev/null +++ b/backend/internal/router/router.go @@ -0,0 +1,65 @@ +package router + +import ( + "net/http" + "os" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "lst.net/internal/notifications/ws" + "lst.net/internal/system/servers" + "lst.net/internal/system/settings" + "lst.net/pkg/logger" +) + +func Setup(db *gorm.DB, basePath string) *gin.Engine { + r := gin.Default() + + if os.Getenv("APP_ENV") == "production" { + gin.SetMode(gin.ReleaseMode) + } + + // Enable CORS (adjust origins as needed) + r.Use(cors.New(cors.Config{ + AllowOrigins: []string{"*"}, // Allow all origins (change in production) + AllowMethods: []string{"GET", "OPTIONS", "POST", "DELETE", "PATCH", "CONNECT"}, + AllowHeaders: []string{"Origin", "Cache-Control", "Content-Type"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + AllowWebSockets: true, + })) + + // Serve Docusaurus static files + r.StaticFS(basePath+"/docs", http.Dir("docs")) + r.StaticFS(basePath+"/app", http.Dir("frontend")) + + r.GET(basePath+"/api/ping", func(c *gin.Context) { + log := logger.New() + log.Info("Checking if the server is up", "system", map[string]interface{}{ + "endpoint": "/api/ping", + "client_ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + }) + c.JSON(200, gin.H{"message": "pong"}) + }) + + // all routes to there respective systems. + ws.RegisterSocketRoutes(r, basePath) + settings.RegisterSettingsRoutes(r, basePath) + servers.RegisterServersRoutes(r, basePath) + + r.Any(basePath+"/api", errorApiLoc) + + return r +} + +func errorApiLoc(c *gin.Context) { + log := logger.New() + log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{ + "endpoint": "/api", + "client_ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + }) + c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered an api route that dose not exist"}) +} diff --git a/backend/internal/system/servers/get_servers.go b/backend/internal/system/servers/get_servers.go new file mode 100644 index 0000000..8f55720 --- /dev/null +++ b/backend/internal/system/servers/get_servers.go @@ -0,0 +1,65 @@ +package servers + +import ( + "reflect" + "strings" + + "github.com/gin-gonic/gin" + "lst.net/internal/db" + "lst.net/internal/models" + "lst.net/pkg/logger" +) + +func getServers(c *gin.Context) { + log := logger.New() + servers, err := GetServers() + log.Info("Current Settings", "system", map[string]interface{}{ + "endpoint": "/api/v1/settings", + "client_ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + }) + + if err != nil { + log := logger.New() + log.Error("Current Settings", "system", map[string]interface{}{ + "endpoint": "/api/v1/settings", + "client_ip": c.ClientIP(), + "user_agent": c.Request.UserAgent(), + "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": servers}) +} + +func GetServers() ([]map[string]interface{}, error) { + var servers []models.Servers + res := db.DB.Find(&servers) + + if res.Error != nil { + return nil, res.Error + } + + toLowercase := func(s models.Servers) 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 + } + + var lowercaseServers []map[string]interface{} + for _, server := range servers { + lowercaseServers = append(lowercaseServers, toLowercase(server)) + } + + return lowercaseServers, nil +} diff --git a/backend/internal/system/servers/new_server.go b/backend/internal/system/servers/new_server.go new file mode 100644 index 0000000..ea62f1f --- /dev/null +++ b/backend/internal/system/servers/new_server.go @@ -0,0 +1,20 @@ +package servers + +import ( + "log" + + "lst.net/internal/db" + "lst.net/internal/models" +) + +func NewServer(serverData models.Servers) (string, error) { + + err := db.DB.Create(&serverData).Error + + if err != nil { + log.Println("There was an error adding the new server") + return "There was an error adding the new server", err + } + + return "New server was just created", nil +} diff --git a/backend/internal/system/servers/servers.go b/backend/internal/system/servers/servers.go new file mode 100644 index 0000000..cebcf12 --- /dev/null +++ b/backend/internal/system/servers/servers.go @@ -0,0 +1,11 @@ +package servers + +import ( + "github.com/gin-gonic/gin" +) + +func RegisterServersRoutes(l *gin.Engine, baseUrl string) { + + s := l.Group(baseUrl + "/api/v1") + s.GET("/servers", getServers) +} diff --git a/backend/internal/system/servers/update_server.go b/backend/internal/system/servers/update_server.go new file mode 100644 index 0000000..5b88812 --- /dev/null +++ b/backend/internal/system/servers/update_server.go @@ -0,0 +1,59 @@ +package servers + +// import ( +// "encoding/json" + +// "github.com/gin-gonic/gin" +// "lst.net/internal/db" +// "lst.net/pkg/logger" +// ) + +// func updateSettingById(c *gin.Context) { +// log := logger.New() +// settingID := c.Param("id") + +// if settingID == "" { +// c.JSON(500, gin.H{"message": "Invalid data"}) +// log.Error("Invalid data", "system", map[string]interface{}{ +// "endpoint": "/api/v1/settings", +// "client_ip": c.ClientIP(), +// "user_agent": c.Request.UserAgent(), +// }) +// return +// } +// var setting SettingUpdateInput + +// //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()}) +// log.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 := UpdateServer(db.DB, settingID, setting); err != nil { +// c.JSON(500, gin.H{"message": "Failed to update setting", "error": err.Error()}) +// log.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}) + +// } + +// func UpdateServer() (string, error) { +// return "Server was just updated", nil +// } diff --git a/backend/internal/system/settings/getSettings.go b/backend/internal/system/settings/getSettings.go new file mode 100644 index 0000000..1e3a6d5 --- /dev/null +++ b/backend/internal/system/settings/getSettings.go @@ -0,0 +1,41 @@ +package settings + +import ( + "reflect" + "strings" + + "gorm.io/gorm" + "lst.net/internal/models" +) + +func GetAllSettings(db *gorm.DB) ([]map[string]interface{}, error) { + var settings []models.Settings + result := db.Find(&settings) + + if result.Error != nil { + return nil, result.Error + } + + // Function to convert struct to map with lowercase keys + 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, nil +} diff --git a/backend/utils/inputs/settingsInput.go b/backend/internal/system/settings/inputs.go similarity index 91% rename from backend/utils/inputs/settingsInput.go rename to backend/internal/system/settings/inputs.go index 3174c3f..f02e1b8 100644 --- a/backend/utils/inputs/settingsInput.go +++ b/backend/internal/system/settings/inputs.go @@ -1,4 +1,4 @@ -package inputs +package settings type SettingUpdateInput struct { Description *string `json:"description"` diff --git a/backend/cmd/services/system/settings/settings.go b/backend/internal/system/settings/settings.go similarity index 73% rename from backend/cmd/services/system/settings/settings.go rename to backend/internal/system/settings/settings.go index 5329103..07d181e 100644 --- a/backend/cmd/services/system/settings/settings.go +++ b/backend/internal/system/settings/settings.go @@ -4,14 +4,13 @@ import ( "encoding/json" "github.com/gin-gonic/gin" - "lst.net/utils/db" - "lst.net/utils/inputs" - logging "lst.net/utils/logger" + "lst.net/internal/db" + "lst.net/pkg/logger" ) func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) { // seed the db on start up - db.SeedConfigs(db.DB) + db.SeedSettings(db.DB) s := l.Group(baseUrl + "/api/v1") s.GET("/settings", getSettings) @@ -19,16 +18,17 @@ func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) { } func getSettings(c *gin.Context) { - logger := logging.New() - configs, err := db.GetAllConfigs(db.DB) - logger.Info("Current Settings", "system", map[string]interface{}{ + log := logger.New() + configs, err := GetAllSettings(db.DB) + log.Info("Current Settings", "system", map[string]interface{}{ "endpoint": "/api/v1/settings", "client_ip": c.ClientIP(), "user_agent": c.Request.UserAgent(), }) if err != nil { - logger.Error("Current Settings", "system", map[string]interface{}{ + log := logger.New() + log.Error("Current Settings", "system", map[string]interface{}{ "endpoint": "/api/v1/settings", "client_ip": c.ClientIP(), "user_agent": c.Request.UserAgent(), @@ -42,19 +42,19 @@ func getSettings(c *gin.Context) { } func updateSettingById(c *gin.Context) { - logger := logging.New() + log := logger.New() settingID := c.Param("id") if settingID == "" { c.JSON(500, gin.H{"message": "Invalid data"}) - logger.Error("Invalid data", "system", map[string]interface{}{ + log.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 + var setting SettingUpdateInput //err := c.ShouldBindBodyWithJSON(&setting) @@ -63,7 +63,7 @@ func updateSettingById(c *gin.Context) { 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{}{ + log.Error("Invalid request body", "system", map[string]interface{}{ "endpoint": "/api/v1/settings", "client_ip": c.ClientIP(), "user_agent": c.Request.UserAgent(), @@ -72,9 +72,9 @@ func updateSettingById(c *gin.Context) { return } - if err := db.UpdateConfig(db.DB, settingID, setting); err != nil { + if err := UpdateSetting(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{}{ + log.Error("Failed to update setting", "system", map[string]interface{}{ "endpoint": "/api/v1/settings", "client_ip": c.ClientIP(), "user_agent": c.Request.UserAgent(), diff --git a/backend/internal/system/settings/update_setting.go b/backend/internal/system/settings/update_setting.go new file mode 100644 index 0000000..f46f97f --- /dev/null +++ b/backend/internal/system/settings/update_setting.go @@ -0,0 +1,34 @@ +package settings + +import ( + "gorm.io/gorm" + "lst.net/internal/models" +) + +func UpdateSetting(db *gorm.DB, id string, input SettingUpdateInput) error { + var cfg models.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 +} diff --git a/backend/main.go b/backend/main.go index dbc501c..5105171 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,50 +1,29 @@ -// @title My Awesome API -// @version 1.0 -// @description This is a sample server for a pet store. -// @termsOfService http://swagger.io/terms/ - -// @contact.name API Support -// @contact.url http://www.swagger.io/support -// @contact.email support@swagger.io - -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html - -// @host localhost:8080 -// @BasePath /api/v1 package main import ( "errors" "fmt" - - "net/http" "os" - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" "github.com/joho/godotenv" - "lst.net/cmd/services/system/settings" - "lst.net/cmd/services/websocket" - // _ "lst.net/docs" - - "lst.net/utils/db" - logging "lst.net/utils/logger" + "lst.net/internal/db" + "lst.net/internal/router" + "lst.net/pkg/logger" ) func main() { - log := logging.New() - // Load .env only in dev (not Docker/production) if os.Getenv("RUNNING_IN_DOCKER") != "true" { err := godotenv.Load("../.env") if err != nil { + log := logger.New() log.Info("Warning: .env file not found (ok in Docker/production)", "system", map[string]interface{}{}) } } // Initialize DB if _, err := db.InitDB(); err != nil { + log := logger.New() log.Panic("Database intialize failed", "db", map[string]interface{}{ "error": err.Error(), "casue": errors.Unwrap(err), @@ -54,6 +33,7 @@ func main() { } defer func() { if r := recover(); r != nil { + log := logger.New() sqlDB, _ := db.DB.DB() sqlDB.Close() log.Error("Recovered from panic during DB shutdown", "db", map[string]interface{}{ @@ -62,6 +42,10 @@ func main() { } }() + // long lived process like ocp running all the time should go here and base the db struct over. + // go ocp.MonitorPrinters + // go notifcations.Processor + // Set basePath dynamically basePath := "/" @@ -69,79 +53,21 @@ func main() { basePath = "/lst" // Dev only } - // fmt.Println(name) fmt.Println("Welcome to lst backend where all the fun happens.") - r := gin.Default() - if os.Getenv("APP_ENV") == "production" { - gin.SetMode(gin.ReleaseMode) - } - - // Enable CORS (adjust origins as needed) - r.Use(cors.New(cors.Config{ - AllowOrigins: []string{"*"}, // Allow all origins (change in production) - AllowMethods: []string{"GET", "OPTIONS", "POST", "DELETE", "PATCH", "CONNECT"}, - AllowHeaders: []string{"Origin", "Cache-Control", "Content-Type"}, - ExposeHeaders: []string{"Content-Length"}, - AllowCredentials: true, - AllowWebSockets: true, - })) - - // // --- Add Redirects Here --- - // // Redirect root ("/") to "/app" or "/lst/app" - // r.GET("/", func(c *gin.Context) { - // c.Redirect(http.StatusMovedPermanently, basePath+"/app") - // }) - - // // Redirect "/lst" (if applicable) to "/lst/app" - // if basePath == "/lst" { - // r.GET("/lst", func(c *gin.Context) { - // c.Redirect(http.StatusMovedPermanently, basePath+"/app") - // }) - // } - - // Serve Docusaurus static files - r.StaticFS(basePath+"/docs", http.Dir("docs")) - r.StaticFS(basePath+"/app", http.Dir("frontend")) - - r.GET(basePath+"/api/ping", func(c *gin.Context) { - log.Info("Checking if the server is up", "system", map[string]interface{}{ - "endpoint": "/api/ping", - "client_ip": c.ClientIP(), - "user_agent": c.Request.UserAgent(), - }) - c.JSON(200, gin.H{"message": "pong"}) - }) - - //logging.RegisterLoggerRoutes(r, basePath) - websocket.RegisterSocketRoutes(r, basePath) - settings.RegisterSettingsRoutes(r, basePath) - - r.Any(basePath+"/api", errorApiLoc) + // Init Gin router and pass DB to services + r := router.Setup(db.DB, basePath) // get the server port port := "8080" if os.Getenv("VITE_SERVER_PORT") != "" { port = os.Getenv("VITE_SERVER_PORT") } - r.Run(":" + port) -} -// func serveViteApp(c *gin.Context) { -// // Set proper Content-Type for HTML -// c.Header("Content-Type", "text/html") -// c.File("./dist/index.html") -// } - -// func errorLoc(c *gin.Context) { -// c.JSON(http.StatusBadRequest, gin.H{"message": "welcome to lst system you might have just encountered an incorrect area of the app"}) -// } -func errorApiLoc(c *gin.Context) { - log := logging.New() - log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{ - "endpoint": "/api", - "client_ip": c.ClientIP(), - "user_agent": c.Request.UserAgent(), - }) - c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered an api route that dose not exist"}) + if err := r.Run(":" + port); err != nil { + log := logger.New() + log.Panic("Server failed to start", "system", map[string]interface{}{ + "error": err, + }) + } } diff --git a/backend/pkg/json.go b/backend/pkg/json.go new file mode 100644 index 0000000..4b4e903 --- /dev/null +++ b/backend/pkg/json.go @@ -0,0 +1,3 @@ +package pkg + +type JSONB map[string]interface{} diff --git a/backend/pkg/logger/create_log.go b/backend/pkg/logger/create_log.go new file mode 100644 index 0000000..6f7a2c1 --- /dev/null +++ b/backend/pkg/logger/create_log.go @@ -0,0 +1,18 @@ +package logger + +import ( + "lst.net/internal/db" + "lst.net/internal/models" + "lst.net/pkg" +) + +// CreateLog inserts a new log entry. +func CreateLog(level, message, service string, metadata pkg.JSONB) error { + log := models.Log{ + Level: level, + Message: message, + Service: service, + Metadata: metadata, + } + return db.DB.Create(&log).Error +} diff --git a/backend/utils/logger/logger.go b/backend/pkg/logger/logger.go similarity index 94% rename from backend/utils/logger/logger.go rename to backend/pkg/logger/logger.go index 2e3b5e8..7a661d7 100644 --- a/backend/utils/logger/logger.go +++ b/backend/pkg/logger/logger.go @@ -1,4 +1,4 @@ -package logging +package logger import ( "encoding/json" @@ -9,7 +9,6 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "lst.net/utils/db" ) type CustomLogger struct { @@ -50,7 +49,7 @@ func PrettyFormat(level, message string, metadata map[string]interface{}) string } func (l *CustomLogger) logToPostgres(level, message, service string, metadata map[string]interface{}) { - err := db.CreateLog(level, message, service, metadata) + err := CreateLog(level, message, service, metadata) if err != nil { // Fallback to console if DB fails log.Error().Err(err).Msg("Failed to write log to PostgreSQL") @@ -98,7 +97,7 @@ func (l *CustomLogger) Panic(message, service string, fields map[string]interfac Msg(message + " (PANIC)") // Explicitly mark as panic // Log to PostgreSQL (sync to ensure it's saved before crashing) - err := db.CreateLog("panic", message, service, fields) // isCritical=true + err := CreateLog("panic", message, service, fields) // isCritical=true if err != nil { l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL") } diff --git a/backend/utils/db/logs.go b/backend/utils/db/logs.go deleted file mode 100644 index d9139c2..0000000 --- a/backend/utils/db/logs.go +++ /dev/null @@ -1,48 +0,0 @@ -package db - -import ( - "time" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -type Log struct { - LogID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"` - Level string `gorm:"size:10;not null"` // "info", "error", etc. - Message string `gorm:"not null"` - Service string `gorm:"size:50"` - Metadata JSONB `gorm:"type:jsonb"` // fields (e.g., {"user_id": 123}) - CreatedAt time.Time `gorm:"index"` - Checked bool `gorm:"type:boolean;default:false"` - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` -} - -// JSONB is a helper type for PostgreSQL JSONB fields. -//type JSONB map[string]interface{} - -// --- CRUD Operations --- - -// CreateLog inserts a new log entry. -func CreateLog(level, message, service string, metadata JSONB) error { - log := Log{ - Level: level, - Message: message, - Service: service, - Metadata: metadata, - } - return DB.Create(&log).Error -} - -// GetLogsByLevel fetches logs filtered by severity. -func GetLogs(level string, limit int, service string) ([]Log, error) { - var logs []Log - err := DB.Where("level = ? and service = ?", level, service).Limit(limit).Find(&logs).Error - return logs, err -} - -// DeleteOldLogs removes logs older than `days` and by level. -func DeleteOldLogs(days int, level string) error { - return DB.Where("created_at < ? and level = ?", time.Now().AddDate(0, 0, -days), level).Delete(&Log{}).Error -}