Compare commits
6 Commits
188331c1ad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c7af1901aa | |||
| 2473bfa702 | |||
| 4dd842b3b8 | |||
| 89ef04cc6f | |||
| 3cec883356 | |||
| 0ecbe29ec1 |
@@ -7,6 +7,10 @@ VITE_SERVER_PORT=4000
|
|||||||
# lstv2 loc
|
# lstv2 loc
|
||||||
LSTV2="C\drive\loc"
|
LSTV2="C\drive\loc"
|
||||||
|
|
||||||
|
# discord - this us used to monitor the logs and make sure we never have a critial shut down.
|
||||||
|
# this will be for other critical stuff like nice label and some other events to make sure we are still in a good spot and dont need to jump in
|
||||||
|
WEBHOOK=
|
||||||
|
|
||||||
# dev stuff below
|
# dev stuff below
|
||||||
|
|
||||||
# Gitea Info
|
# Gitea Info
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module lst.net
|
|||||||
go 1.24.3
|
go 1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bensch777/discord-webhook-golang v0.0.6
|
||||||
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/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ package ws
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"lst.net/internal/db"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/models"
|
"lst.net/internal/models"
|
||||||
"lst.net/pkg"
|
"lst.net/pkg"
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
@@ -38,7 +37,8 @@ type Client struct {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SaveToDB() {
|
func (c *Client) SaveToDB(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
// Convert c.Channels (map[string]bool) to map[string]interface{} for JSONB
|
// Convert c.Channels (map[string]bool) to map[string]interface{} for JSONB
|
||||||
channels := make(map[string]interface{})
|
channels := make(map[string]interface{})
|
||||||
for ch := range c.Channels {
|
for ch := range c.Channels {
|
||||||
@@ -54,22 +54,27 @@ func (c *Client) SaveToDB() {
|
|||||||
LastHeartbeat: time.Now(),
|
LastHeartbeat: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DB.Create(&clientRecord).Error; err != nil {
|
if err := db.Create(&clientRecord).Error; err != nil {
|
||||||
log.Println("❌ Error saving client:", err)
|
log.Error("❌ Error saving client", "websocket", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
c.ClientID = clientRecord.ClientID
|
c.ClientID = clientRecord.ClientID
|
||||||
c.ConnectedAt = clientRecord.ConnectedAt
|
c.ConnectedAt = clientRecord.ConnectedAt
|
||||||
|
|
||||||
|
clientData := fmt.Sprintf("A new client %v, just connected", c.ClientID)
|
||||||
|
log.Info(clientData, "websocket", map[string]interface{}{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) MarkDisconnected() {
|
func (c *Client) MarkDisconnected(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
logger := logger.New()
|
|
||||||
clientData := fmt.Sprintf("Client %v just lefts us", c.ClientID)
|
clientData := fmt.Sprintf("Client %v Dicconected", c.ClientID)
|
||||||
logger.Info(clientData, "websocket", map[string]interface{}{})
|
log.Info(clientData, "websocket", map[string]interface{}{})
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
res := db.DB.Model(&models.ClientRecord{}).
|
res := 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,
|
||||||
@@ -77,13 +82,13 @@ func (c *Client) MarkDisconnected() {
|
|||||||
|
|
||||||
if res.RowsAffected == 0 {
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
log.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
"clientID": c.ClientID,
|
"clientID": c.ClientID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
|
|
||||||
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
log.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
"clientID": c.ClientID,
|
"clientID": c.ClientID,
|
||||||
"error": res.Error,
|
"error": res.Error,
|
||||||
})
|
})
|
||||||
@@ -137,28 +142,31 @@ const (
|
|||||||
writeWait = 10 * time.Second
|
writeWait = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) StartHeartbeat() {
|
func (c *Client) StartHeartbeat(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
logger := logger.New()
|
|
||||||
log.Println("Started hearbeat")
|
log.Debug("Started hearbeat", "websocket", map[string]interface{}{})
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if !c.isAlive.Load() { // Correct way to read atomic.Bool
|
if !c.isAlive.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
log.Printf("Heartbeat failed for %s: %v", c.ClientID, err)
|
log.Error("Heartbeat failed", "websocket", map[string]interface{}{
|
||||||
c.Close()
|
"client_id": c.ClientID,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
c.Close(log, db)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
res := db.DB.Model(&models.ClientRecord{}).
|
res := 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,
|
||||||
@@ -166,17 +174,21 @@ func (c *Client) StartHeartbeat() {
|
|||||||
|
|
||||||
if res.RowsAffected == 0 {
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
log.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
"clientID": c.ClientID,
|
"clientID": c.ClientID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
|
|
||||||
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
log.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
"clientID": c.ClientID,
|
"clientID": c.ClientID,
|
||||||
"error": res.Error,
|
"error": res.Error,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
clientStuff := fmt.Sprintf("HeartBeat just done on: %v", c.ClientID)
|
||||||
|
log.Info(clientStuff, "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
return
|
return
|
||||||
@@ -184,16 +196,16 @@ func (c *Client) StartHeartbeat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() {
|
func (c *Client) Close(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
if c.isAlive.CompareAndSwap(true, false) { // Atomic swap
|
if c.isAlive.CompareAndSwap(true, false) { // Atomic swap
|
||||||
close(c.done)
|
close(c.done)
|
||||||
c.Conn.Close()
|
c.Conn.Close()
|
||||||
// Add any other cleanup here
|
// Add any other cleanup here
|
||||||
c.MarkDisconnected()
|
c.MarkDisconnected(log, db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) startServerPings() {
|
func (c *Client) startServerPings(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
ticker := time.NewTicker(60 * time.Second) // Ping every 30s
|
ticker := time.NewTicker(60 * time.Second) // Ping every 30s
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -202,7 +214,13 @@ func (c *Client) startServerPings() {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
c.Close() // Disconnect if ping fails
|
|
||||||
|
log.Error("Server Ping failed", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.Close(log, db)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
@@ -219,15 +237,14 @@ func (c *Client) IsActive() bool {
|
|||||||
return time.Since(c.lastActive) < 45*time.Second // 1.5x ping interval
|
return time.Since(c.lastActive) < 45*time.Second // 1.5x ping interval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) updateHeartbeat() {
|
func (c *Client) updateHeartbeat(log *logger.CustomLogger, db *gorm.DB) {
|
||||||
//fmt.Println("Updating heatbeat")
|
//fmt.Println("Updating heatbeat")
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
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(&models.ClientRecord{}).
|
res := 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
|
||||||
@@ -235,27 +252,27 @@ func (c *Client) updateHeartbeat() {
|
|||||||
//fmt.Printf("Executed SQL: %v\n", db.DB.Statement.SQL.String())
|
//fmt.Printf("Executed SQL: %v\n", db.DB.Statement.SQL.String())
|
||||||
if res.RowsAffected == 0 {
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
log.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
"clientID": c.ClientID,
|
"clientID": c.ClientID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
|
|
||||||
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
log.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
"clientID": c.ClientID,
|
"clientID": c.ClientID,
|
||||||
"error": res.Error,
|
"error": res.Error,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 2. Verify DB connection
|
// 2. Verify DB connection
|
||||||
if db.DB == nil {
|
if db == nil {
|
||||||
logger.Error("DB connection is nil", "websocket", map[string]interface{}{})
|
log.Error("DB connection is nil", "websocket", map[string]interface{}{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Test raw SQL execution first
|
// 3. Test raw SQL execution first
|
||||||
testRes := db.DB.Exec("SELECT 1")
|
testRes := db.Exec("SELECT 1")
|
||||||
if testRes.Error != nil {
|
if testRes.Error != nil {
|
||||||
logger.Error("DB ping failed", "websocket", map[string]interface{}{
|
log.Error("DB ping failed", "websocket", map[string]interface{}{
|
||||||
"error": testRes.Error,
|
"error": testRes.Error,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package ws
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JoinPayload struct {
|
type JoinPayload struct {
|
||||||
@@ -26,11 +27,11 @@ var upgrader = websocket.Upgrader{
|
|||||||
EnableCompression: true,
|
EnableCompression: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
func SocketHandler(c *gin.Context, channels map[string]*Channel, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
// Upgrade HTTP to WebSocket
|
// Upgrade HTTP to WebSocket
|
||||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("WebSocket upgrade failed:", err)
|
log.Error("WebSocket upgrade failed", "websocket", map[string]interface{}{"error": err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//defer conn.Close()
|
//defer conn.Close()
|
||||||
@@ -53,7 +54,7 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
clientsMu.Unlock()
|
clientsMu.Unlock()
|
||||||
|
|
||||||
// Save initial connection to DB
|
// Save initial connection to DB
|
||||||
client.SaveToDB()
|
client.SaveToDB(log, db)
|
||||||
// Save initial connection to DB
|
// Save initial connection to DB
|
||||||
// if err := client.SaveToDB(); err != nil {
|
// if err := client.SaveToDB(); err != nil {
|
||||||
// log.Println("Failed to save client to DB:", err)
|
// log.Println("Failed to save client to DB:", err)
|
||||||
@@ -70,12 +71,12 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
client.markActive() // Track last pong time
|
client.markActive() // Track last pong time
|
||||||
client.lastActive = now
|
client.lastActive = now
|
||||||
client.updateHeartbeat()
|
client.updateHeartbeat(log, db)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start server-side ping ticker
|
// Start server-side ping ticker
|
||||||
go client.startServerPings()
|
go client.startServerPings(log, db)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Unregister from all channels
|
// Unregister from all channels
|
||||||
@@ -91,11 +92,13 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
clientsMu.Unlock()
|
clientsMu.Unlock()
|
||||||
|
|
||||||
// Mark disconnected in DB
|
// Mark disconnected in DB
|
||||||
client.MarkDisconnected()
|
client.MarkDisconnected(log, db)
|
||||||
|
|
||||||
// Close connection
|
// Close connection
|
||||||
conn.Close()
|
conn.Close()
|
||||||
log.Printf("Client disconnected: %s", client.ClientID)
|
log.Info("Client disconnected", "websocket", map[string]interface{}{
|
||||||
|
"client": client.ClientID,
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Send welcome message immediately
|
// Send welcome message immediately
|
||||||
@@ -104,7 +107,7 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
"message": "Welcome to the WebSocket server. Send subscription request to begin.",
|
"message": "Welcome to the WebSocket server. Send subscription request to begin.",
|
||||||
}
|
}
|
||||||
if err := conn.WriteJSON(welcomeMsg); err != nil {
|
if err := conn.WriteJSON(welcomeMsg); err != nil {
|
||||||
log.Println("Failed to send welcome message:", err)
|
log.Error("Failed to send welcome message", "websocket", map[string]interface{}{"error": err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,14 +121,14 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(client.Send)
|
close(client.Send)
|
||||||
client.MarkDisconnected()
|
client.MarkDisconnected(log, db)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, msg, err := conn.ReadMessage()
|
_, msg, err := conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||||
log.Printf("Client disconnected unexpectedly: %v", err)
|
log.Error("Client disconnected unexpectedl", "websocket", map[string]interface{}{"error": err})
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -173,6 +176,7 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
client.Channels["logServices"] = true
|
client.Channels["logServices"] = true
|
||||||
|
|
||||||
conn.WriteJSON(map[string]string{
|
conn.WriteJSON(map[string]string{
|
||||||
|
"message": "You are now subscribed to the the service channel",
|
||||||
"status": "subscribed",
|
"status": "subscribed",
|
||||||
"channel": "logServices",
|
"channel": "logServices",
|
||||||
})
|
})
|
||||||
@@ -194,12 +198,13 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
client.Channels["labels"] = true
|
client.Channels["labels"] = true
|
||||||
|
|
||||||
// Update DB record
|
// Update DB record
|
||||||
client.SaveToDB()
|
client.SaveToDB(log, db)
|
||||||
// if err := client.SaveToDB(); err != nil {
|
// if err := client.SaveToDB(); err != nil {
|
||||||
// log.Println("Failed to update client labels:", err)
|
// log.Println("Failed to update client labels:", err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
conn.WriteJSON(map[string]interface{}{
|
conn.WriteJSON(map[string]interface{}{
|
||||||
|
"message": "You are now subscribed to the label channel",
|
||||||
"status": "subscribed",
|
"status": "subscribed",
|
||||||
"channel": "labels",
|
"channel": "labels",
|
||||||
"filters": client.Labels,
|
"filters": client.Labels,
|
||||||
@@ -217,7 +222,7 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
// Send messages to client
|
// Send messages to client
|
||||||
for message := range client.Send {
|
for message := range client.Send {
|
||||||
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||||
log.Println("Write error:", err)
|
log.Error("Write erro", "websocket", map[string]interface{}{"error": err})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import (
|
|||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LogServices(broadcaster chan logger.Message) {
|
func LogServices(broadcaster chan logger.Message, log *logger.CustomLogger) {
|
||||||
log := logger.New()
|
|
||||||
|
|
||||||
log.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{}{})
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ func LogServices(broadcaster chan logger.Message) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Listening for all logs through single logServices channel...")
|
log.Info("Listening for all logs through single logServices channel...", "wbsocker", map[string]interface{}{})
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case notify := <-listener.Notify:
|
case notify := <-listener.Notify:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ var (
|
|||||||
broadcaster = make(chan logger.Message)
|
broadcaster = make(chan logger.Message)
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterSocketRoutes(r *gin.Engine, base_url string) {
|
func RegisterSocketRoutes(r *gin.Engine, base_url string, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
// Initialize all channels
|
// Initialize all channels
|
||||||
InitializeChannels()
|
InitializeChannels()
|
||||||
|
|
||||||
@@ -19,12 +20,12 @@ func RegisterSocketRoutes(r *gin.Engine, base_url string) {
|
|||||||
StartAllChannels()
|
StartAllChannels()
|
||||||
|
|
||||||
// Start background services
|
// Start background services
|
||||||
go LogServices(broadcaster)
|
go LogServices(broadcaster, log)
|
||||||
go StartBroadcasting(broadcaster, channels)
|
go StartBroadcasting(broadcaster, channels)
|
||||||
|
|
||||||
// WebSocket route
|
// WebSocket route
|
||||||
r.GET(base_url+"/ws", func(c *gin.Context) {
|
r.GET(base_url+"/ws", func(c *gin.Context) {
|
||||||
SocketHandler(c, channels)
|
SocketHandler(c, channels, log, db)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.GET(base_url+"/ws/clients", AdminAuthMiddleware(), handleGetClients)
|
r.GET(base_url+"/ws/clients", AdminAuthMiddleware(), handleGetClients)
|
||||||
|
|||||||
41
backend/internal/router/middleware/settings_Check.go
Normal file
41
backend/internal/router/middleware/settings_Check.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"lst.net/internal/system/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SettingCheckMiddleware(settingName string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Debug: Log the setting name we're checking
|
||||||
|
//log.Printf("Checking setting '%s' for path: %s", settingName, c.Request.URL.Path)
|
||||||
|
|
||||||
|
// Get the current setting value
|
||||||
|
value, err := settings.GetString(settingName)
|
||||||
|
if err != nil {
|
||||||
|
//log.Printf("Error getting setting '%s': %v", settingName, err)
|
||||||
|
c.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"error": "endpoint not available",
|
||||||
|
"details": "setting error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log the actual value received
|
||||||
|
//log.Printf("Setting '%s' value: '%s'", settingName, value)
|
||||||
|
|
||||||
|
// Changed condition to check for "1" (enable) instead of "0" (disable)
|
||||||
|
if value != "1" {
|
||||||
|
//log.Printf("Setting '%s' not enabled (value: '%s')", settingName, value)
|
||||||
|
c.AbortWithStatusJSON(404, gin.H{
|
||||||
|
"error": "endpoint not available",
|
||||||
|
"details": "required feature is disabled",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log successful check
|
||||||
|
//log.Printf("Setting check passed for '%s'", settingName)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,14 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/notifications/ws"
|
"lst.net/internal/notifications/ws"
|
||||||
|
"lst.net/internal/router/middleware"
|
||||||
"lst.net/internal/system/servers"
|
"lst.net/internal/system/servers"
|
||||||
"lst.net/internal/system/settings"
|
"lst.net/internal/system/settings"
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Setup(db *gorm.DB, basePath string) *gin.Engine {
|
func Setup(db *gorm.DB, basePath string, log *logger.CustomLogger) *gin.Engine {
|
||||||
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
if os.Getenv("APP_ENV") == "production" {
|
if os.Getenv("APP_ENV") == "production" {
|
||||||
@@ -34,8 +36,12 @@ func Setup(db *gorm.DB, basePath string) *gin.Engine {
|
|||||||
r.StaticFS(basePath+"/docs", http.Dir("docs"))
|
r.StaticFS(basePath+"/docs", http.Dir("docs"))
|
||||||
r.StaticFS(basePath+"/app", http.Dir("frontend"))
|
r.StaticFS(basePath+"/app", http.Dir("frontend"))
|
||||||
|
|
||||||
r.GET(basePath+"/api/ping", func(c *gin.Context) {
|
// all routes to there respective systems.
|
||||||
log := logger.New()
|
ws.RegisterSocketRoutes(r, basePath, log, db)
|
||||||
|
settings.RegisterSettingsRoutes(r, basePath, log, db)
|
||||||
|
servers.RegisterServersRoutes(r, basePath, log, db)
|
||||||
|
|
||||||
|
r.GET(basePath+"/api/ping", middleware.SettingCheckMiddleware("testingApiFunction"), func(c *gin.Context) {
|
||||||
log.Info("Checking if the server is up", "system", map[string]interface{}{
|
log.Info("Checking if the server is up", "system", map[string]interface{}{
|
||||||
"endpoint": "/api/ping",
|
"endpoint": "/api/ping",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
@@ -44,22 +50,17 @@ func Setup(db *gorm.DB, basePath string) *gin.Engine {
|
|||||||
c.JSON(200, gin.H{"message": "pong"})
|
c.JSON(200, gin.H{"message": "pong"})
|
||||||
})
|
})
|
||||||
|
|
||||||
// all routes to there respective systems.
|
r.Any(basePath+"/", func(c *gin.Context) { errorApiLoc(c, log) })
|
||||||
ws.RegisterSocketRoutes(r, basePath)
|
|
||||||
settings.RegisterSettingsRoutes(r, basePath)
|
|
||||||
servers.RegisterServersRoutes(r, basePath)
|
|
||||||
|
|
||||||
r.Any(basePath+"/api", errorApiLoc)
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorApiLoc(c *gin.Context) {
|
func errorApiLoc(c *gin.Context, log *logger.CustomLogger) {
|
||||||
log := logger.New()
|
|
||||||
log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{
|
log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{
|
||||||
"endpoint": "/api",
|
"endpoint": c.Request.URL.Path,
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
"user_agent": c.Request.UserAgent(),
|
"user_agent": c.Request.UserAgent(),
|
||||||
})
|
})
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered an api route that dose not exist"})
|
c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered a route that dose not exist"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"lst.net/internal/db"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/models"
|
"lst.net/internal/models"
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getServers(c *gin.Context) {
|
func getServers(c *gin.Context, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
log := logger.New()
|
|
||||||
servers, err := GetServers()
|
servers, err := GetServers(log, db)
|
||||||
log.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(),
|
||||||
@@ -20,7 +20,7 @@ func getServers(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log := logger.New()
|
|
||||||
log.Error("Current Settings", "system", map[string]interface{}{
|
log.Error("Current Settings", "system", map[string]interface{}{
|
||||||
"endpoint": "/api/v1/settings",
|
"endpoint": "/api/v1/settings",
|
||||||
"client_ip": c.ClientIP(),
|
"client_ip": c.ClientIP(),
|
||||||
@@ -34,9 +34,9 @@ func getServers(c *gin.Context) {
|
|||||||
c.JSON(200, gin.H{"message": "Current settings", "data": servers})
|
c.JSON(200, gin.H{"message": "Current settings", "data": servers})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetServers() ([]map[string]interface{}, error) {
|
func GetServers(log *logger.CustomLogger, db *gorm.DB) ([]map[string]interface{}, error) {
|
||||||
var servers []models.Servers
|
var servers []models.Servers
|
||||||
res := db.DB.Find(&servers)
|
res := db.Find(&servers)
|
||||||
|
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
return nil, res.Error
|
return nil, res.Error
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
package servers
|
package servers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"lst.net/internal/db"
|
|
||||||
"lst.net/internal/models"
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(serverData models.Servers) (string, error) {
|
func NewServer(serverData models.Servers, log *logger.CustomLogger, db *gorm.DB) (string, error) {
|
||||||
|
|
||||||
err := db.DB.Create(&serverData).Error
|
err := db.Create(&serverData).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("There was an error adding the new server")
|
log.Error("There was an error adding the new server", "server", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
return "There was an error adding the new server", err
|
return "There was an error adding the new server", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package servers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterServersRoutes(l *gin.Engine, baseUrl string) {
|
func RegisterServersRoutes(l *gin.Engine, baseUrl string, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
|
|
||||||
s := l.Group(baseUrl + "/api/v1")
|
s := l.Group(baseUrl + "/api/v1")
|
||||||
s.GET("/servers", getServers)
|
s.GET("/servers", func(c *gin.Context) { getServers(c, log, db) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
39
backend/internal/system/settings/get_settings.go
Normal file
39
backend/internal/system/settings/get_settings.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
// }
|
||||||
|
|
||||||
|
convertedSettings := GetMap()
|
||||||
|
|
||||||
|
return convertedSettings, nil
|
||||||
|
}
|
||||||
@@ -4,23 +4,23 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"lst.net/internal/db"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) {
|
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
// seed the db on start up
|
// seed the db on start up
|
||||||
db.SeedSettings(db.DB)
|
SeedSettings(db, log)
|
||||||
|
|
||||||
s := l.Group(baseUrl + "/api/v1")
|
s := l.Group(baseUrl + "/api/v1")
|
||||||
s.GET("/settings", getSettings)
|
s.GET("/settings", func(c *gin.Context) { getSettings(c, log, db) })
|
||||||
s.PATCH("/settings/:id", updateSettingById)
|
s.PATCH("/settings/:id", func(c *gin.Context) { updateSettingById(c, log, db) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSettings(c *gin.Context) {
|
func getSettings(c *gin.Context, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
log := logger.New()
|
configs, err := GetAllSettings(db)
|
||||||
configs, err := GetAllSettings(db.DB)
|
log.Info("Current Settings", "settings", 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(),
|
||||||
@@ -28,7 +28,7 @@ func getSettings(c *gin.Context) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log := logger.New()
|
log := logger.New()
|
||||||
log.Error("Current Settings", "system", map[string]interface{}{
|
log.Error("Current Settings", "settings", 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(),
|
||||||
@@ -41,13 +41,13 @@ func getSettings(c *gin.Context) {
|
|||||||
c.JSON(200, gin.H{"message": "Current settings", "data": configs})
|
c.JSON(200, gin.H{"message": "Current settings", "data": configs})
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSettingById(c *gin.Context) {
|
func updateSettingById(c *gin.Context, log *logger.CustomLogger, db *gorm.DB) {
|
||||||
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"})
|
||||||
log.Error("Invalid data", "system", map[string]interface{}{
|
log.Error("Invalid data", "settings", 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(),
|
||||||
@@ -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()})
|
||||||
log.Error("Invalid request body", "system", map[string]interface{}{
|
log.Error("Invalid request body", "settings", 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 := UpdateSetting(db.DB, settingID, setting); err != nil {
|
if err := UpdateSetting(log, 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()})
|
||||||
log.Error("Failed to update setting", "system", map[string]interface{}{
|
log.Error("Failed to update setting", "settings", 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(),
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package db
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/models"
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var seedConfigData = []models.Settings{
|
var seedConfigData = []models.Settings{
|
||||||
@@ -42,36 +44,85 @@ var seedConfigData = []models.Settings{
|
|||||||
{Name: "scannerID", Value: `500`, Description: "What scanner id will we be using for the app", AppService: "logistics", Enabled: true},
|
{Name: "scannerID", Value: `500`, Description: "What scanner id will we be using for the app", AppService: "logistics", Enabled: true},
|
||||||
{Name: "scannerPort", Value: `50002`, Description: "What port instance will we be using?", AppService: "logistics", Enabled: true},
|
{Name: "scannerPort", Value: `50002`, Description: "What port instance will we be using?", AppService: "logistics", Enabled: true},
|
||||||
{Name: "stagingReturnLocations", Value: `30125,31523`, Description: "What are the staging location IDs we will use to select from. seperated by commas", AppService: "logistics", Enabled: true},
|
{Name: "stagingReturnLocations", Value: `30125,31523`, Description: "What are the staging location IDs we will use to select from. seperated by commas", AppService: "logistics", Enabled: true},
|
||||||
|
{Name: "testingApiFunction", Value: `1`, Description: "This is a test to validate if we set to 0 it will actaully not allow the route", AppService: "logistics", Enabled: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
func SeedSettings(db *gorm.DB) error {
|
func SeedSettings(db *gorm.DB, log *logger.CustomLogger) error {
|
||||||
|
|
||||||
for _, cfg := range seedConfigData {
|
for _, cfg := range seedConfigData {
|
||||||
var existing models.Settings
|
var existing models.Settings
|
||||||
// Try to find config by unique Name
|
if err := db.Unscoped().Where("name = ?", cfg.Name).First(&existing).Error; err == nil {
|
||||||
result := db.Where("Name =?", cfg.Name).First(&existing)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
if existing.DeletedAt.Valid {
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
// Undelete by setting DeletedAt to NULL
|
||||||
// not here lets add it
|
if err := db.Unscoped().Model(&existing).Update("DeletedAt", gorm.DeletedAt{}).Error; err != nil {
|
||||||
if err := db.Create(&cfg).Error; err != nil {
|
log.Error("Failed to undelete settings", "settings", map[string]interface{}{
|
||||||
log.Printf("Failed to seed config %s: %v", cfg.Name, err)
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
//log.Printf("Seeded new config: %s", cfg.Name)
|
|
||||||
} else {
|
|
||||||
// Some other error
|
|
||||||
return result.Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if err := db.Create(&cfg).Error; err != nil {
|
||||||
|
log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Try to find config by unique Name
|
||||||
|
// result := db.Where("Name =?", cfg.Name).First(&existing)
|
||||||
|
|
||||||
|
// if result.Error != nil {
|
||||||
|
// if result.Error == gorm.ErrRecordNotFound && cfg.Enabled {
|
||||||
|
// // not here lets add it
|
||||||
|
|
||||||
|
// if err := db.Create(&cfg).Error; err != nil && !existing.DeletedAt.Valid {
|
||||||
|
// log.Error("Failed to seed settings", "settings", map[string]interface{}{
|
||||||
|
// "name": cfg.Name,
|
||||||
|
// "error": err,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //log.Printf("Seeded new config: %s", cfg.Name)
|
||||||
|
// } else {
|
||||||
|
// // Some other error
|
||||||
|
// return result.Error
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
// only update the fields we want to update.
|
// remove the setting if we change to false this will help with future proofing our seeder in the event we need to add it back
|
||||||
existing.Description = cfg.Description
|
if cfg.Enabled {
|
||||||
if err := db.Save(&existing).Error; err != nil {
|
existing.Description = cfg.Description
|
||||||
log.Printf("Failed to update config %s: %v", cfg.Name, err)
|
existing.Name = cfg.Name
|
||||||
return err
|
existing.AppService = cfg.AppService
|
||||||
|
if err := db.Save(&existing).Error; err != nil {
|
||||||
|
log.Error("Failed to update ettings.", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we delete the setting so its no longer there
|
||||||
|
if err := db.Delete(&existing).Error; err != nil {
|
||||||
|
log.Error("Failed to delete ettings.", "settings", map[string]interface{}{
|
||||||
|
"name": cfg.Name,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settingDelete := fmt.Sprintf("Updated existing config: %s", cfg.Name)
|
||||||
|
log.Info(settingDelete, "settings", map[string]interface{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("Updated existing config: %s", cfg.Name)
|
//log.Printf("Updated existing config: %s", cfg.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("All settings added or updated.", "settings", map[string]interface{}{})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
110
backend/internal/system/settings/settings_states.go
Normal file
110
backend/internal/system/settings/settings_states.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"lst.net/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Global state
|
||||||
|
appSettings []models.Settings
|
||||||
|
appSettingsLock sync.RWMutex
|
||||||
|
dbInstance *gorm.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize loads settings into memory at startup
|
||||||
|
func Initialize(db *gorm.DB) error {
|
||||||
|
dbInstance = db
|
||||||
|
return Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh reloads settings from DB (call after updates)
|
||||||
|
func Refresh() error {
|
||||||
|
appSettingsLock.Lock()
|
||||||
|
defer appSettingsLock.Unlock()
|
||||||
|
|
||||||
|
var settings []models.Settings
|
||||||
|
if err := dbInstance.Find(&settings).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appSettings = settings
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns a thread-safe copy of settings
|
||||||
|
func GetAll() []models.Settings {
|
||||||
|
appSettingsLock.RLock()
|
||||||
|
defer appSettingsLock.RUnlock()
|
||||||
|
|
||||||
|
// Return copy to prevent external modification
|
||||||
|
copied := make([]models.Settings, len(appSettings))
|
||||||
|
copy(copied, appSettings)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMap returns settings as []map[string]interface{}
|
||||||
|
func GetMap() []map[string]interface{} {
|
||||||
|
return convertToMap(GetAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToMap helper (move your existing conversion logic here)
|
||||||
|
func convertToMap(settings []models.Settings) []map[string]interface{} {
|
||||||
|
toLowercase := func(s models.Settings) map[string]interface{} {
|
||||||
|
t := reflect.TypeOf(s)
|
||||||
|
v := reflect.ValueOf(s)
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := strings.ToLower(t.Field(i).Name)
|
||||||
|
data[field] = v.Field(i).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert each struct in settings slice to a map with lowercase keys
|
||||||
|
var lowercaseSettings []map[string]interface{}
|
||||||
|
for _, setting := range settings {
|
||||||
|
lowercaseSettings = append(lowercaseSettings, toLowercase(setting))
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowercaseSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetString(name string) (string, error) {
|
||||||
|
appSettingsLock.RLock()
|
||||||
|
defer appSettingsLock.RUnlock()
|
||||||
|
|
||||||
|
for _, s := range appSettings {
|
||||||
|
if s.Name == name { // assuming your model has a "Name" field
|
||||||
|
fmt.Println(s.Value)
|
||||||
|
return s.Value, nil // assuming your model has a "Value" field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("setting not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetTemp(name, value string) {
|
||||||
|
appSettingsLock.Lock()
|
||||||
|
defer appSettingsLock.Unlock()
|
||||||
|
|
||||||
|
for i, s := range appSettings {
|
||||||
|
if s.Name == name {
|
||||||
|
appSettings[i].Value = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If not found, add new setting
|
||||||
|
appSettings = append(appSettings, models.Settings{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,9 +3,10 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"lst.net/internal/models"
|
"lst.net/internal/models"
|
||||||
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpdateSetting(db *gorm.DB, id string, input SettingUpdateInput) error {
|
func UpdateSetting(log *logger.CustomLogger, db *gorm.DB, id string, input SettingUpdateInput) error {
|
||||||
var cfg models.Settings
|
var cfg models.Settings
|
||||||
if err := db.Where("setting_id =?", id).First(&cfg).Error; err != nil {
|
if err := db.Where("setting_id =?", id).First(&cfg).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -30,5 +31,26 @@ func UpdateSetting(db *gorm.DB, id string, input SettingUpdateInput) error {
|
|||||||
return nil // nothing to update
|
return nil // nothing to update
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Model(&cfg).Updates(updates).Error
|
settingUpdate := db.Model(&cfg).Updates(updates)
|
||||||
|
|
||||||
|
if settingUpdate.Error != nil {
|
||||||
|
log.Error("There was an error updating the setting", "settings", map[string]interface{}{
|
||||||
|
"error": settingUpdate.Error,
|
||||||
|
})
|
||||||
|
return settingUpdate.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Refresh(); err != nil {
|
||||||
|
log.Error("There was an error refreshing the settings after a setting update", "settings", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("The setting was just updated", "settings", map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"name": cfg.Name,
|
||||||
|
"updated": updates,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import (
|
|||||||
|
|
||||||
"lst.net/internal/db"
|
"lst.net/internal/db"
|
||||||
"lst.net/internal/router"
|
"lst.net/internal/router"
|
||||||
|
"lst.net/internal/system/settings"
|
||||||
"lst.net/pkg/logger"
|
"lst.net/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log := logger.New()
|
||||||
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 {
|
||||||
@@ -23,17 +25,17 @@ func main() {
|
|||||||
|
|
||||||
// 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, please check the server asap.", "db", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"casue": errors.Unwrap(err),
|
"cause": errors.Unwrap(err),
|
||||||
"timeout": "30s",
|
"timeout": "30s",
|
||||||
"details": fmt.Sprintf("%+v", err), // Full stack trace if available
|
"details": fmt.Sprintf("%+v", err), // Full stack trace if available
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
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{}{
|
||||||
@@ -42,6 +44,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if err := settings.Initialize(db.DB); err != nil {
|
||||||
|
log.Panic("There was an error intilizing the settings", "settings", map[string]interface{}{
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// long lived process like ocp running all the time should go here and base the db struct over.
|
// long lived process like ocp running all the time should go here and base the db struct over.
|
||||||
// go ocp.MonitorPrinters
|
// go ocp.MonitorPrinters
|
||||||
// go notifcations.Processor
|
// go notifcations.Processor
|
||||||
@@ -53,10 +61,9 @@ func main() {
|
|||||||
basePath = "/lst" // Dev only
|
basePath = "/lst" // Dev only
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Welcome to lst backend where all the fun happens.")
|
log.Info("Welcome to lst backend where all the fun happens.", "system", map[string]interface{}{})
|
||||||
|
|
||||||
// Init Gin router and pass DB to services
|
// Init Gin router and pass DB to services
|
||||||
r := router.Setup(db.DB, basePath)
|
r := router.Setup(db.DB, basePath, log)
|
||||||
|
|
||||||
// get the server port
|
// get the server port
|
||||||
port := "8080"
|
port := "8080"
|
||||||
@@ -65,7 +72,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := r.Run(":" + port); err != nil {
|
if err := r.Run(":" + port); err != nil {
|
||||||
log := logger.New()
|
|
||||||
log.Panic("Server failed to start", "system", map[string]interface{}{
|
log.Panic("Server failed to start", "system", map[string]interface{}{
|
||||||
"error": err,
|
"error": err,
|
||||||
})
|
})
|
||||||
|
|||||||
77
backend/pkg/logger/discord.go
Normal file
77
backend/pkg/logger/discord.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
discordwebhook "github.com/bensch777/discord-webhook-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateDiscordMsg(message string) {
|
||||||
|
// we will only run the discord bot if we actaully put a url in the.
|
||||||
|
if os.Getenv("WEBHOOK") != "" {
|
||||||
|
var webhookurl = os.Getenv("WEBHOOK")
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
embed := discordwebhook.Embed{
|
||||||
|
Title: "A new crash report from lst.",
|
||||||
|
Color: 15277667,
|
||||||
|
Url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
// Thumbnail: discordwebhook.Thumbnail{
|
||||||
|
// Url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
// },
|
||||||
|
// Author: discordwebhook.Author{
|
||||||
|
// Name: "Author Name",
|
||||||
|
// Icon_URL: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
// },
|
||||||
|
Fields: []discordwebhook.Field{
|
||||||
|
discordwebhook.Field{
|
||||||
|
Name: host,
|
||||||
|
Value: message,
|
||||||
|
Inline: false,
|
||||||
|
},
|
||||||
|
// discordwebhook.Field{
|
||||||
|
// Name: "Error reason",
|
||||||
|
// Value: stack,
|
||||||
|
// Inline: false,
|
||||||
|
// },
|
||||||
|
// discordwebhook.Field{
|
||||||
|
// Name: "Field 3",
|
||||||
|
// Value: "Field Value 3",
|
||||||
|
// Inline: false,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
// Footer: discordwebhook.Footer{
|
||||||
|
// Text: "Footer Text",
|
||||||
|
// Icon_url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
|
||||||
|
SendEmbed(webhookurl, embed)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendEmbed(link string, embeds discordwebhook.Embed) error {
|
||||||
|
logging := New()
|
||||||
|
logging.Info("new messege being posted to discord", "logger", map[string]interface{}{
|
||||||
|
"message": "Message",
|
||||||
|
})
|
||||||
|
hook := discordwebhook.Hook{
|
||||||
|
Username: "Captain Hook",
|
||||||
|
Avatar_url: "https://avatars.githubusercontent.com/u/6016509?s=48&v=4",
|
||||||
|
Content: "Message",
|
||||||
|
Embeds: []discordwebhook.Embed{embeds},
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(hook)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = discordwebhook.ExecuteWebhook(link, payload)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
@@ -107,6 +107,7 @@ func (l *CustomLogger) Panic(message, service string, fields map[string]interfac
|
|||||||
l.consoleLogger.Warn().Msg("Additional panic context captured")
|
l.consoleLogger.Warn().Msg("Additional panic context captured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CreateDiscordMsg(message)
|
||||||
panic(message)
|
panic(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user