refactor(correction to folder sturcture): before we got to deep resturctures to best pactice folder
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -194,3 +194,4 @@ scripts/resetDanger.js
|
|||||||
LstWrapper/Program_vite_as_Static.txt
|
LstWrapper/Program_vite_as_Static.txt
|
||||||
LstWrapper/Program_proxy_backend.txt
|
LstWrapper/Program_proxy_backend.txt
|
||||||
scripts/stopPool.go
|
scripts/stopPool.go
|
||||||
|
backend_bad_practice
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package servers
|
|
||||||
@@ -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,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -5,42 +5,35 @@ go 1.24.3
|
|||||||
require (
|
require (
|
||||||
github.com/gin-contrib/cors v1.7.6
|
github.com/gin-contrib/cors v1.7.6
|
||||||
github.com/gin-gonic/gin v1.10.1
|
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/joho/godotenv v1.5.1
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/swaggo/swag v1.16.6
|
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.30.0
|
gorm.io/gorm v1.30.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
|
||||||
github.com/bytedance/sonic v1.13.3 // 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/base64x v0.1.5 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // 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/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.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/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/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // 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/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/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // 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/crypto v0.40.0 // indirect
|
||||||
golang.org/x/mod v0.26.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
golang.org/x/text v0.27.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
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import (
|
|||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
|
|
||||||
type JSONB map[string]interface{}
|
|
||||||
|
|
||||||
type DBConfig struct {
|
type DBConfig struct {
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
DSN string
|
DSN string
|
||||||
@@ -37,7 +36,8 @@ func InitDB() (*DBConfig, error) {
|
|||||||
// ensures we have the uuid stuff setup properly
|
// ensures we have the uuid stuff setup properly
|
||||||
DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to auto-migrate models: %v", err)
|
return nil, fmt.Errorf("failed to auto-migrate models: %v", err)
|
||||||
}
|
}
|
||||||
@@ -2,28 +2,12 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"lst.net/utils/inputs"
|
"lst.net/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
var seedConfigData = []models.Settings{
|
||||||
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{
|
|
||||||
{Name: "serverPort", Description: "The port the server will listen on if not running in docker", Value: "4000", Enabled: true, AppService: "server"},
|
{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: "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},
|
{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},
|
{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 {
|
for _, cfg := range seedConfigData {
|
||||||
var existing Settings
|
var existing models.Settings
|
||||||
// Try to find config by unique Name
|
// Try to find config by unique Name
|
||||||
result := db.Where("Name =?", cfg.Name).First(&existing)
|
result := db.Where("Name =?", cfg.Name).First(&existing)
|
||||||
|
|
||||||
@@ -91,77 +75,3 @@ func SeedConfigs(db *gorm.DB) error {
|
|||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
21
backend/internal/models/logs.go
Normal file
21
backend/internal/models/logs.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
32
backend/internal/models/servers.go
Normal file
32
backend/internal/models/servers.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
20
backend/internal/models/settings.go
Normal file
20
backend/internal/models/settings.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package db
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"lst.net/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientRecord struct {
|
type ClientRecord struct {
|
||||||
@@ -13,7 +14,7 @@ type ClientRecord struct {
|
|||||||
UserAgent string `gorm:"size:255"`
|
UserAgent string `gorm:"size:255"`
|
||||||
ConnectedAt time.Time `gorm:"index"`
|
ConnectedAt time.Time `gorm:"index"`
|
||||||
LastHeartbeat time.Time `gorm:"column:last_heartbeat"`
|
LastHeartbeat time.Time `gorm:"column:last_heartbeat"`
|
||||||
Channels JSONB `gorm:"type:jsonb"`
|
Channels pkg.JSONB `gorm:"type:jsonb"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DisconnectedAt *time.Time `gorm:"column:disconnected_at"`
|
DisconnectedAt *time.Time `gorm:"column:disconnected_at"`
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
logging "lst.net/utils/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
@@ -82,8 +82,8 @@ func CleanupChannels() {
|
|||||||
channels = make(map[string]*Channel)
|
channels = make(map[string]*Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Channel) {
|
func StartBroadcasting(broadcaster chan logger.Message, channels map[string]*Channel) {
|
||||||
logger := logging.New()
|
logger := logger.New()
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range broadcaster {
|
for msg := range broadcaster {
|
||||||
switch msg.Channel {
|
switch msg.Channel {
|
||||||
@@ -147,7 +147,7 @@ func (ch *Channel) RunChannel() {
|
|||||||
ch.lock.Unlock()
|
ch.lock.Unlock()
|
||||||
|
|
||||||
case message := <-ch.Broadcast:
|
case message := <-ch.Broadcast:
|
||||||
var msg logging.Message
|
var msg logger.Message
|
||||||
if err := json.Unmarshal(message, &msg); err != nil {
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -9,8 +9,10 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"lst.net/utils/db"
|
"lst.net/internal/db"
|
||||||
logging "lst.net/utils/logger"
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -43,11 +45,11 @@ func (c *Client) SaveToDB() {
|
|||||||
channels[ch] = true
|
channels[ch] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRecord := &db.ClientRecord{
|
clientRecord := &models.ClientRecord{
|
||||||
APIKey: c.APIKey,
|
APIKey: c.APIKey,
|
||||||
IPAddress: c.IPAddress,
|
IPAddress: c.IPAddress,
|
||||||
UserAgent: c.UserAgent,
|
UserAgent: c.UserAgent,
|
||||||
Channels: db.JSONB(channels),
|
Channels: pkg.JSONB(channels),
|
||||||
ConnectedAt: time.Now(),
|
ConnectedAt: time.Now(),
|
||||||
LastHeartbeat: time.Now(),
|
LastHeartbeat: time.Now(),
|
||||||
}
|
}
|
||||||
@@ -62,12 +64,12 @@ func (c *Client) SaveToDB() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) MarkDisconnected() {
|
func (c *Client) MarkDisconnected() {
|
||||||
logger := logging.New()
|
logger := logger.New()
|
||||||
clientData := fmt.Sprintf("Client %v just lefts us", c.ClientID)
|
clientData := fmt.Sprintf("Client %v just lefts us", c.ClientID)
|
||||||
logger.Info(clientData, "websocket", map[string]interface{}{})
|
logger.Info(clientData, "websocket", map[string]interface{}{})
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
res := db.DB.Model(&db.ClientRecord{}).
|
res := db.DB.Model(&models.ClientRecord{}).
|
||||||
Where("client_id = ?", c.ClientID).
|
Where("client_id = ?", c.ClientID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"disconnected_at": &now,
|
"disconnected_at": &now,
|
||||||
@@ -136,7 +138,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) StartHeartbeat() {
|
func (c *Client) StartHeartbeat() {
|
||||||
logger := logging.New()
|
logger := logger.New()
|
||||||
log.Println("Started hearbeat")
|
log.Println("Started hearbeat")
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
@@ -156,7 +158,7 @@ func (c *Client) StartHeartbeat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
res := db.DB.Model(&db.ClientRecord{}).
|
res := db.DB.Model(&models.ClientRecord{}).
|
||||||
Where("client_id = ?", c.ClientID).
|
Where("client_id = ?", c.ClientID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"last_heartbeat": &now,
|
"last_heartbeat": &now,
|
||||||
@@ -220,12 +222,12 @@ func (c *Client) IsActive() bool {
|
|||||||
func (c *Client) updateHeartbeat() {
|
func (c *Client) updateHeartbeat() {
|
||||||
//fmt.Println("Updating heatbeat")
|
//fmt.Println("Updating heatbeat")
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
logger := logging.New()
|
logger := logger.New()
|
||||||
|
|
||||||
//fmt.Printf("Updating heartbeat for client: %s at %v\n", c.ClientID, now)
|
//fmt.Printf("Updating heartbeat for client: %s at %v\n", c.ClientID, now)
|
||||||
|
|
||||||
//db.DB = db.DB.Debug()
|
//db.DB = db.DB.Debug()
|
||||||
res := db.DB.Model(&db.ClientRecord{}).
|
res := db.DB.Model(&models.ClientRecord{}).
|
||||||
Where("client_id = ?", c.ClientID).
|
Where("client_id = ?", c.ClientID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"last_heartbeat": &now, // Explicit format
|
"last_heartbeat": &now, // Explicit format
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package websocket
|
package ws
|
||||||
|
|
||||||
// setup the notifiyer
|
// setup the notifiyer
|
||||||
|
|
||||||
@@ -23,13 +23,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
logging "lst.net/utils/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LogServices(broadcaster chan logging.Message) {
|
func LogServices(broadcaster chan logger.Message) {
|
||||||
logger := logging.New()
|
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",
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||||
os.Getenv("DB_HOST"),
|
os.Getenv("DB_HOST"),
|
||||||
@@ -42,7 +42,7 @@ func LogServices(broadcaster chan logging.Message) {
|
|||||||
listener := pq.NewListener(dsn, 10*time.Second, time.Minute, nil)
|
listener := pq.NewListener(dsn, 10*time.Second, time.Minute, nil)
|
||||||
err := listener.Listen("new_log")
|
err := listener.Listen("new_log")
|
||||||
if err != nil {
|
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(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -54,14 +54,14 @@ func LogServices(broadcaster chan logging.Message) {
|
|||||||
if notify != nil {
|
if notify != nil {
|
||||||
var logData map[string]interface{}
|
var logData map[string]interface{}
|
||||||
if err := json.Unmarshal([]byte(notify.Extra), &logData); err != nil {
|
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(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always send to logServices channel
|
// Always send to logServices channel
|
||||||
broadcaster <- logging.Message{
|
broadcaster <- logger.Message{
|
||||||
Channel: "logServices",
|
Channel: "logServices",
|
||||||
Data: logData,
|
Data: logData,
|
||||||
Meta: map[string]interface{}{
|
Meta: map[string]interface{}{
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package websocket
|
package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
logging "lst.net/utils/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
broadcaster = make(chan logging.Message)
|
broadcaster = make(chan logger.Message)
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterSocketRoutes(r *gin.Engine, base_url string) {
|
func RegisterSocketRoutes(r *gin.Engine, base_url string) {
|
||||||
65
backend/internal/router/router.go
Normal file
65
backend/internal/router/router.go
Normal file
@@ -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"})
|
||||||
|
}
|
||||||
65
backend/internal/system/servers/get_servers.go
Normal file
65
backend/internal/system/servers/get_servers.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
20
backend/internal/system/servers/new_server.go
Normal file
20
backend/internal/system/servers/new_server.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
11
backend/internal/system/servers/servers.go
Normal file
11
backend/internal/system/servers/servers.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
59
backend/internal/system/servers/update_server.go
Normal file
59
backend/internal/system/servers/update_server.go
Normal file
@@ -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
|
||||||
|
// }
|
||||||
41
backend/internal/system/settings/getSettings.go
Normal file
41
backend/internal/system/settings/getSettings.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package inputs
|
package settings
|
||||||
|
|
||||||
type SettingUpdateInput struct {
|
type SettingUpdateInput struct {
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
@@ -4,14 +4,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"lst.net/utils/db"
|
"lst.net/internal/db"
|
||||||
"lst.net/utils/inputs"
|
"lst.net/pkg/logger"
|
||||||
logging "lst.net/utils/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) {
|
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) {
|
||||||
// seed the db on start up
|
// seed the db on start up
|
||||||
db.SeedConfigs(db.DB)
|
db.SeedSettings(db.DB)
|
||||||
|
|
||||||
s := l.Group(baseUrl + "/api/v1")
|
s := l.Group(baseUrl + "/api/v1")
|
||||||
s.GET("/settings", getSettings)
|
s.GET("/settings", getSettings)
|
||||||
@@ -19,16 +18,17 @@ func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getSettings(c *gin.Context) {
|
func getSettings(c *gin.Context) {
|
||||||
logger := logging.New()
|
log := logger.New()
|
||||||
configs, err := db.GetAllConfigs(db.DB)
|
configs, err := GetAllSettings(db.DB)
|
||||||
logger.Info("Current Settings", "system", map[string]interface{}{
|
log.Info("Current Settings", "system", map[string]interface{}{
|
||||||
"endpoint": "/api/v1/settings",
|
"endpoint": "/api/v1/settings",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
"user_agent": c.Request.UserAgent(),
|
"user_agent": c.Request.UserAgent(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
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",
|
"endpoint": "/api/v1/settings",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
"user_agent": c.Request.UserAgent(),
|
"user_agent": c.Request.UserAgent(),
|
||||||
@@ -42,19 +42,19 @@ func getSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateSettingById(c *gin.Context) {
|
func updateSettingById(c *gin.Context) {
|
||||||
logger := logging.New()
|
log := logger.New()
|
||||||
settingID := c.Param("id")
|
settingID := c.Param("id")
|
||||||
|
|
||||||
if settingID == "" {
|
if settingID == "" {
|
||||||
c.JSON(500, gin.H{"message": "Invalid data"})
|
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",
|
"endpoint": "/api/v1/settings",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
"user_agent": c.Request.UserAgent(),
|
"user_agent": c.Request.UserAgent(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var setting inputs.SettingUpdateInput
|
var setting SettingUpdateInput
|
||||||
|
|
||||||
//err := c.ShouldBindBodyWithJSON(&setting)
|
//err := c.ShouldBindBodyWithJSON(&setting)
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ func updateSettingById(c *gin.Context) {
|
|||||||
|
|
||||||
if err := decoder.Decode(&setting); err != nil {
|
if err := decoder.Decode(&setting); err != nil {
|
||||||
c.JSON(400, gin.H{"message": "Invalid request body", "error": err.Error()})
|
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",
|
"endpoint": "/api/v1/settings",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
"user_agent": c.Request.UserAgent(),
|
"user_agent": c.Request.UserAgent(),
|
||||||
@@ -72,9 +72,9 @@ func updateSettingById(c *gin.Context) {
|
|||||||
return
|
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()})
|
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",
|
"endpoint": "/api/v1/settings",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
"user_agent": c.Request.UserAgent(),
|
"user_agent": c.Request.UserAgent(),
|
||||||
34
backend/internal/system/settings/update_setting.go
Normal file
34
backend/internal/system/settings/update_setting.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
108
backend/main.go
108
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-contrib/cors"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"lst.net/cmd/services/system/settings"
|
|
||||||
"lst.net/cmd/services/websocket"
|
|
||||||
|
|
||||||
// _ "lst.net/docs"
|
"lst.net/internal/db"
|
||||||
|
"lst.net/internal/router"
|
||||||
"lst.net/utils/db"
|
"lst.net/pkg/logger"
|
||||||
logging "lst.net/utils/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log := logging.New()
|
|
||||||
// Load .env only in dev (not Docker/production)
|
|
||||||
if os.Getenv("RUNNING_IN_DOCKER") != "true" {
|
if os.Getenv("RUNNING_IN_DOCKER") != "true" {
|
||||||
err := godotenv.Load("../.env")
|
err := godotenv.Load("../.env")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log := logger.New()
|
||||||
log.Info("Warning: .env file not found (ok in Docker/production)", "system", map[string]interface{}{})
|
log.Info("Warning: .env file not found (ok in Docker/production)", "system", map[string]interface{}{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize DB
|
// Initialize DB
|
||||||
if _, err := db.InitDB(); err != nil {
|
if _, err := db.InitDB(); err != nil {
|
||||||
|
log := logger.New()
|
||||||
log.Panic("Database intialize failed", "db", map[string]interface{}{
|
log.Panic("Database intialize failed", "db", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"casue": errors.Unwrap(err),
|
"casue": errors.Unwrap(err),
|
||||||
@@ -54,6 +33,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
log := logger.New()
|
||||||
sqlDB, _ := db.DB.DB()
|
sqlDB, _ := db.DB.DB()
|
||||||
sqlDB.Close()
|
sqlDB.Close()
|
||||||
log.Error("Recovered from panic during DB shutdown", "db", map[string]interface{}{
|
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
|
// Set basePath dynamically
|
||||||
basePath := "/"
|
basePath := "/"
|
||||||
|
|
||||||
@@ -69,79 +53,21 @@ func main() {
|
|||||||
basePath = "/lst" // Dev only
|
basePath = "/lst" // Dev only
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmt.Println(name)
|
|
||||||
fmt.Println("Welcome to lst backend where all the fun happens.")
|
fmt.Println("Welcome to lst backend where all the fun happens.")
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
if os.Getenv("APP_ENV") == "production" {
|
// Init Gin router and pass DB to services
|
||||||
gin.SetMode(gin.ReleaseMode)
|
r := router.Setup(db.DB, basePath)
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// get the server port
|
// get the server port
|
||||||
port := "8080"
|
port := "8080"
|
||||||
if os.Getenv("VITE_SERVER_PORT") != "" {
|
if os.Getenv("VITE_SERVER_PORT") != "" {
|
||||||
port = os.Getenv("VITE_SERVER_PORT")
|
port = os.Getenv("VITE_SERVER_PORT")
|
||||||
}
|
}
|
||||||
r.Run(":" + port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func serveViteApp(c *gin.Context) {
|
if err := r.Run(":" + port); err != nil {
|
||||||
// // Set proper Content-Type for HTML
|
log := logger.New()
|
||||||
// c.Header("Content-Type", "text/html")
|
log.Panic("Server failed to start", "system", map[string]interface{}{
|
||||||
// c.File("./dist/index.html")
|
"error": err,
|
||||||
// }
|
|
||||||
|
|
||||||
// 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"})
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
backend/pkg/json.go
Normal file
3
backend/pkg/json.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
type JSONB map[string]interface{}
|
||||||
18
backend/pkg/logger/create_log.go
Normal file
18
backend/pkg/logger/create_log.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package logging
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"lst.net/utils/db"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomLogger struct {
|
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{}) {
|
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 {
|
if err != nil {
|
||||||
// Fallback to console if DB fails
|
// Fallback to console if DB fails
|
||||||
log.Error().Err(err).Msg("Failed to write log to PostgreSQL")
|
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
|
Msg(message + " (PANIC)") // Explicitly mark as panic
|
||||||
|
|
||||||
// Log to PostgreSQL (sync to ensure it's saved before crashing)
|
// 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 {
|
if err != nil {
|
||||||
l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL")
|
l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL")
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user