Compare commits
6 Commits
daf9e8a966
...
v0.0.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| b6968b7b67 | |||
| a0aa75c5a0 | |||
| 78be07c8bb | |||
| 0575a34422 | |||
| 3bc3801ffb | |||
| 4368111311 |
44
CHANGELOG.md
44
CHANGELOG.md
@@ -3,6 +3,50 @@
|
||||
All notable changes to LST will be documented in this file.
|
||||
|
||||
|
||||
## [0.0.1-alpha.6](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.5...v0.0.1-alpha.6) (2025-07-31)
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **logging:** added in db and logging with websocket ([52ef39f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/52ef39fd5c129ed02ed9f38dbf7e49ae06807ad6))
|
||||
* **settings:** migrated all settings endpoints confirmed as well for updates ([0575a34](https://git.tuffraid.net/cowch/logistics_support_tool/commit/0575a344229ba0ff5c0f47781c6d596e5c08e5eb))
|
||||
* **ws server:** added in a websocket on port system to help with better logging ([5bcbdaf](https://git.tuffraid.net/cowch/logistics_support_tool/commit/5bcbdaf3d0e889729d4dce3df51f4330d7793868))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **update server:** fixed to make sure everything is stopped before doing the remaining update ([13e282e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/13e282e815c1c95a0a5298ede2f6497cdf036440))
|
||||
* **websocket:** errors in saving client info during ping ping ([4368111](https://git.tuffraid.net/cowch/logistics_support_tool/commit/4368111311c48e73a11a6b24febdcc3be31a2a59))
|
||||
* **wrapper:** corrections to properly handle websockets :D ([a761a36](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a761a3634b6cb0aeeb571dd634bd158cee530779))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **.env example:** added postrgres example ([14dd87e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/14dd87e335a63d76d64c07a15cf593cb286a9833))
|
||||
* **dockerbuild:** comments as a reminder for my seld ([52956ec](https://git.tuffraid.net/cowch/logistics_support_tool/commit/52956ecaa45cd556ba7832d6cb9ec2cf883d983a))
|
||||
* **docker:** docs about the custom network for the db is seperated ([6a631be](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6a631be909b56a899af393510edffd70d7901a7a))
|
||||
* **wss:** more ws stuff ([63c053b](https://git.tuffraid.net/cowch/logistics_support_tool/commit/63c053b38ce3ab3c3a94cda620da930f4e8615bd))
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **app port:** changed to have the port be dyncamic on the iis side ([074032f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/074032f20dc90810416c5899e44fefe86b52f98a))
|
||||
* **build:** added back in the build name stuff ([92ce51e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/92ce51eb7cf14ebb599c29fea4721e21badafbf6))
|
||||
* **config:** changed to settings to match the other lst in node. makes it more easy to manage ([3bc3801](https://git.tuffraid.net/cowch/logistics_support_tool/commit/3bc3801ffbb544a814d52c72e566e8d4866a7f38))
|
||||
* **createzip:** added in env-example to the zip file ([6c8ac33](https://git.tuffraid.net/cowch/logistics_support_tool/commit/6c8ac33be73f203137b883e33feb625ccc0945e9))
|
||||
* **docker compose example:** added in postgress stuff plus network ([623e19f](https://git.tuffraid.net/cowch/logistics_support_tool/commit/623e19f028d27fbfc46bee567ce78169cddba8fb))
|
||||
* **settings:** changed config to settings and added in the update method for this as well ([a0aa75c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a0aa75c5a0b4a6e3a10b88bbcccf43d096e532b4))
|
||||
* **wrapper:** removed the logger stuff so we dont fill up space ([8a08d3e](https://git.tuffraid.net/cowch/logistics_support_tool/commit/8a08d3eac6540b00ff23115936d56b4f22f16d53))
|
||||
* **ws:** ws logging and channel manager added no auth currently ([a1a30cf](https://git.tuffraid.net/cowch/logistics_support_tool/commit/a1a30cffd18e02e1061959fa3164f8237522880c))
|
||||
|
||||
### 🚀 Performance
|
||||
|
||||
* **websocket:** added in base url to help with ssl stuff and iis ([daf9e8a](https://git.tuffraid.net/cowch/logistics_support_tool/commit/daf9e8a966fd440723b1aec932a02873a5e27eb7))
|
||||
|
||||
### 📝 Testing Code
|
||||
|
||||
* **iis:** wrapper test for ws ([75c17d2](https://git.tuffraid.net/cowch/logistics_support_tool/commit/75c17d20659dcc5a762e00928709c4d3dd277284))
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **hotreload:** added in air for hot reloading ([78be07c](https://git.tuffraid.net/cowch/logistics_support_tool/commit/78be07c8bbf5acbcdac65351f693941f47be4cb5))
|
||||
|
||||
## [0.0.1-alpha.5](https://git.tuffraid.net/cowch/logistics_support_tool/compare/v0.0.1-alpha.4...v0.0.1-alpha.5) (2025-07-21)
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
@@ -10,3 +10,5 @@ this will also include a primary server to house all the common configs across a
|
||||
|
||||
The new lst will run in docker by building your own image and deploying or pulling the image down.
|
||||
you will also be able to run it in windows or linux.
|
||||
|
||||
when developing in lst and you want to run hotloads installed and configure https://github.com/air-verse/air
|
||||
|
||||
0
backend/.air.toml
Normal file
0
backend/.air.toml
Normal file
@@ -1,53 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"lst.net/utils/db"
|
||||
logging "lst.net/utils/logger"
|
||||
)
|
||||
|
||||
type ConfigUpdateInput struct {
|
||||
Description *string `json:"description"`
|
||||
Value *string `json:"value"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
AppService *string `json:"app_service"`
|
||||
}
|
||||
|
||||
func RegisterConfigRoutes(l *gin.Engine, baseUrl string) {
|
||||
// seed the db on start up
|
||||
db.SeedConfigs(db.DB)
|
||||
|
||||
configGroup := l.Group(baseUrl + "/api/config")
|
||||
configGroup.GET("/configs", getconfigs)
|
||||
configGroup.POST("/configs", newConfig)
|
||||
}
|
||||
|
||||
func getconfigs(c *gin.Context) {
|
||||
log := logging.New()
|
||||
configs, err := db.GetAllConfigs(db.DB)
|
||||
log.Info("Current Configs", "system", map[string]interface{}{
|
||||
"endpoint": "/api/config/configs",
|
||||
"client_ip": c.ClientIP(),
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": "There was an error getting the configs", "error": err})
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"message": "Current configs", "data": configs})
|
||||
}
|
||||
|
||||
func newConfig(c *gin.Context) {
|
||||
|
||||
var config ConfigUpdateInput
|
||||
|
||||
err := c.ShouldBindBodyWithJSON(&config)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": "Internal Server Error"})
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"message": "New config was just added", "data": config})
|
||||
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
package system
|
||||
package servers
|
||||
|
||||
88
backend/cmd/services/system/settings/settings.go
Normal file
88
backend/cmd/services/system/settings/settings.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"lst.net/utils/db"
|
||||
"lst.net/utils/inputs"
|
||||
logging "lst.net/utils/logger"
|
||||
)
|
||||
|
||||
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) {
|
||||
// seed the db on start up
|
||||
db.SeedConfigs(db.DB)
|
||||
|
||||
s := l.Group(baseUrl + "/api/v1")
|
||||
s.GET("/settings", getSettings)
|
||||
s.PATCH("/settings/:id", updateSettingById)
|
||||
}
|
||||
|
||||
func getSettings(c *gin.Context) {
|
||||
logger := logging.New()
|
||||
configs, err := db.GetAllConfigs(db.DB)
|
||||
logger.Info("Current Settings", "system", map[string]interface{}{
|
||||
"endpoint": "/api/v1/settings",
|
||||
"client_ip": c.ClientIP(),
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Current Settings", "system", map[string]interface{}{
|
||||
"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": configs})
|
||||
}
|
||||
|
||||
func updateSettingById(c *gin.Context) {
|
||||
logger := logging.New()
|
||||
settingID := c.Param("id")
|
||||
|
||||
if settingID == "" {
|
||||
c.JSON(500, gin.H{"message": "Invalid data"})
|
||||
logger.Error("Invalid data", "system", map[string]interface{}{
|
||||
"endpoint": "/api/v1/settings",
|
||||
"client_ip": c.ClientIP(),
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
})
|
||||
return
|
||||
}
|
||||
var setting inputs.SettingUpdateInput
|
||||
|
||||
//err := c.ShouldBindBodyWithJSON(&setting)
|
||||
|
||||
decoder := json.NewDecoder(c.Request.Body) // more strict and will force us to have correct data
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
if err := decoder.Decode(&setting); err != nil {
|
||||
c.JSON(400, gin.H{"message": "Invalid request body", "error": err.Error()})
|
||||
logger.Error("Invalid request body", "system", map[string]interface{}{
|
||||
"endpoint": "/api/v1/settings",
|
||||
"client_ip": c.ClientIP(),
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
"error": err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.UpdateConfig(db.DB, settingID, setting); err != nil {
|
||||
c.JSON(500, gin.H{"message": "Failed to update setting", "error": err.Error()})
|
||||
logger.Error("Failed to update setting", "system", map[string]interface{}{
|
||||
"endpoint": "/api/v1/settings",
|
||||
"client_ip": c.ClientIP(),
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
"error": err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"message": "Setting was just updated", "data": setting})
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package system
|
||||
@@ -83,6 +83,7 @@ func CleanupChannels() {
|
||||
}
|
||||
|
||||
func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Channel) {
|
||||
logger := logging.New()
|
||||
go func() {
|
||||
for msg := range broadcaster {
|
||||
switch msg.Channel {
|
||||
@@ -90,7 +91,9 @@ func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Ch
|
||||
// Just forward the message - filtering happens in RunChannel()
|
||||
messageBytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling message: %v", err)
|
||||
logger.Error("Error marshaling message", "websocket", map[string]interface{}{
|
||||
"errors": err,
|
||||
})
|
||||
continue
|
||||
}
|
||||
channels["logServices"].Broadcast <- messageBytes
|
||||
@@ -99,7 +102,9 @@ func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Ch
|
||||
// Future labels handling
|
||||
messageBytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling message: %v", err)
|
||||
logger.Error("Error marshaling message", "websocket", map[string]interface{}{
|
||||
"errors": err,
|
||||
})
|
||||
continue
|
||||
}
|
||||
channels["labels"].Broadcast <- messageBytes
|
||||
|
||||
@@ -27,9 +27,10 @@ import (
|
||||
)
|
||||
|
||||
func LogServices(broadcaster chan logging.Message) {
|
||||
fmt.Println("[LogServices] started - single channel for all logs")
|
||||
logger := logging.New()
|
||||
|
||||
logger.Info("[LogServices] started - single channel for all logs", "websocket", map[string]interface{}{})
|
||||
|
||||
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
os.Getenv("DB_HOST"),
|
||||
os.Getenv("DB_PORT"),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"lst.net/utils/db"
|
||||
logging "lst.net/utils/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,7 +32,8 @@ type Client struct {
|
||||
ConnectedAt time.Time `json:"connected_at"`
|
||||
done chan struct{} // For graceful shutdown
|
||||
isAlive atomic.Bool
|
||||
//mu sync.Mutex // Protects isAlive if not using atomic
|
||||
lastActive time.Time // Tracks last activity
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) SaveToDB() {
|
||||
@@ -59,7 +62,10 @@ func (c *Client) SaveToDB() {
|
||||
}
|
||||
|
||||
func (c *Client) MarkDisconnected() {
|
||||
log.Printf("Client %v just lefts us", c.ClientID)
|
||||
logger := logging.New()
|
||||
clientData := fmt.Sprintf("Client %v just lefts us", c.ClientID)
|
||||
logger.Info(clientData, "websocket", map[string]interface{}{})
|
||||
|
||||
now := time.Now()
|
||||
res := db.DB.Model(&db.ClientRecord{}).
|
||||
Where("client_id = ?", c.ClientID).
|
||||
@@ -68,10 +74,17 @@ func (c *Client) MarkDisconnected() {
|
||||
})
|
||||
|
||||
if res.RowsAffected == 0 {
|
||||
log.Println("⚠️ No rows updated for client_id:", c.ClientID)
|
||||
|
||||
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||
"clientID": c.ClientID,
|
||||
})
|
||||
}
|
||||
if res.Error != nil {
|
||||
log.Println("❌ Error updating disconnected_at:", res.Error)
|
||||
|
||||
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||
"clientID": c.ClientID,
|
||||
"error": res.Error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +136,8 @@ const (
|
||||
)
|
||||
|
||||
func (c *Client) StartHeartbeat() {
|
||||
logger := logging.New()
|
||||
log.Println("Started hearbeat")
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -140,6 +155,27 @@ func (c *Client) StartHeartbeat() {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
res := db.DB.Model(&db.ClientRecord{}).
|
||||
Where("client_id = ?", c.ClientID).
|
||||
Updates(map[string]interface{}{
|
||||
"last_heartbeat": &now,
|
||||
})
|
||||
|
||||
if res.RowsAffected == 0 {
|
||||
|
||||
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||
"clientID": c.ClientID,
|
||||
})
|
||||
}
|
||||
if res.Error != nil {
|
||||
|
||||
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||
"clientID": c.ClientID,
|
||||
"error": res.Error,
|
||||
})
|
||||
}
|
||||
|
||||
case <-c.done:
|
||||
return
|
||||
}
|
||||
@@ -155,6 +191,75 @@ func (c *Client) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) startServerPings() {
|
||||
ticker := time.NewTicker(60 * time.Second) // Ping every 30s
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
c.Close() // Disconnect if ping fails
|
||||
return
|
||||
}
|
||||
case <-c.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) markActive() {
|
||||
c.lastActive = time.Now() // No mutex needed if single-writer
|
||||
}
|
||||
|
||||
func (c *Client) IsActive() bool {
|
||||
return time.Since(c.lastActive) < 45*time.Second // 1.5x ping interval
|
||||
}
|
||||
|
||||
func (c *Client) updateHeartbeat() {
|
||||
//fmt.Println("Updating heatbeat")
|
||||
now := time.Now()
|
||||
logger := logging.New()
|
||||
|
||||
//fmt.Printf("Updating heartbeat for client: %s at %v\n", c.ClientID, now)
|
||||
|
||||
//db.DB = db.DB.Debug()
|
||||
res := db.DB.Model(&db.ClientRecord{}).
|
||||
Where("client_id = ?", c.ClientID).
|
||||
Updates(map[string]interface{}{
|
||||
"last_heartbeat": &now, // Explicit format
|
||||
})
|
||||
//fmt.Printf("Executed SQL: %v\n", db.DB.Statement.SQL.String())
|
||||
if res.RowsAffected == 0 {
|
||||
|
||||
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||
"clientID": c.ClientID,
|
||||
})
|
||||
}
|
||||
if res.Error != nil {
|
||||
|
||||
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||
"clientID": c.ClientID,
|
||||
"error": res.Error,
|
||||
})
|
||||
}
|
||||
// 2. Verify DB connection
|
||||
if db.DB == nil {
|
||||
logger.Error("DB connection is nil", "websocket", map[string]interface{}{})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Test raw SQL execution first
|
||||
testRes := db.DB.Exec("SELECT 1")
|
||||
if testRes.Error != nil {
|
||||
logger.Error("DB ping failed", "websocket", map[string]interface{}{
|
||||
"error": testRes.Error,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// work on this stats later
|
||||
// Add to your admin endpoint
|
||||
// type ConnectionStats struct {
|
||||
|
||||
@@ -35,22 +35,10 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
||||
}
|
||||
//defer conn.Close()
|
||||
|
||||
// Set ping handler on the connection
|
||||
conn.SetPingHandler(func(appData string) error {
|
||||
log.Println("Received ping:", appData)
|
||||
conn.SetReadDeadline(time.Now().Add(60 * time.Second)) // Reset read timeout
|
||||
return nil // Return nil to send pong automatically
|
||||
})
|
||||
|
||||
// Optional: Custom pong handler
|
||||
conn.SetPongHandler(func(appData string) error {
|
||||
log.Println("Received pong:", appData)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Create new client
|
||||
client := &Client{
|
||||
Conn: conn,
|
||||
APIKey: "exampleAPIKey",
|
||||
Send: make(chan []byte, 256), // Buffered channel
|
||||
Channels: make(map[string]bool),
|
||||
IPAddress: c.ClientIP(),
|
||||
@@ -73,8 +61,22 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
||||
// return
|
||||
// }
|
||||
|
||||
//client.StartHeartbeat()
|
||||
// Cleanup on disconnect
|
||||
// Set handlers
|
||||
conn.SetPingHandler(func(string) error {
|
||||
return nil // Auto-responds with pong
|
||||
})
|
||||
|
||||
conn.SetPongHandler(func(string) error {
|
||||
now := time.Now()
|
||||
client.markActive() // Track last pong time
|
||||
client.lastActive = now
|
||||
client.updateHeartbeat()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Start server-side ping ticker
|
||||
go client.startServerPings()
|
||||
|
||||
defer func() {
|
||||
// Unregister from all channels
|
||||
for channelName := range client.Channels {
|
||||
@@ -96,11 +98,6 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
||||
log.Printf("Client disconnected: %s", client.ClientID)
|
||||
}()
|
||||
|
||||
client.Conn.SetPingHandler(func(appData string) error {
|
||||
log.Printf("Custom ping handler for client %s", client.ClientID)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Send welcome message immediately
|
||||
welcomeMsg := map[string]string{
|
||||
"status": "connected",
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/swaggo/swag v1.16.5
|
||||
github.com/swaggo/swag v1.16.6
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
"lst.net/cmd/services/system/config"
|
||||
"lst.net/cmd/services/system/settings"
|
||||
"lst.net/cmd/services/websocket"
|
||||
|
||||
_ "lst.net/docs"
|
||||
// _ "lst.net/docs"
|
||||
|
||||
"lst.net/utils/db"
|
||||
logging "lst.net/utils/logger"
|
||||
@@ -115,7 +115,7 @@ func main() {
|
||||
|
||||
//logging.RegisterLoggerRoutes(r, basePath)
|
||||
websocket.RegisterSocketRoutes(r, basePath)
|
||||
config.RegisterConfigRoutes(r, basePath)
|
||||
settings.RegisterSettingsRoutes(r, basePath)
|
||||
|
||||
r.Any(basePath+"/api", errorApiLoc)
|
||||
|
||||
|
||||
@@ -2,14 +2,17 @@ package db
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"lst.net/utils/inputs"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
ConfigID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
|
||||
SettingID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
|
||||
Name string `gorm:"uniqueIndex;not null"`
|
||||
Description string `gorm:"type:text"`
|
||||
Value string `gorm:"not null"`
|
||||
@@ -21,16 +24,48 @@ type Settings struct {
|
||||
}
|
||||
|
||||
var seedConfigData = []Settings{
|
||||
{Name: "serverPort", Description: "The port the server will listen on if not running in docker", Value: "4000", Enabled: true},
|
||||
{Name: "server", Description: "The server we will use when connecting to the alplaprod sql", Value: "usmcd1vms006", Enabled: true},
|
||||
{Name: "serverPort", Description: "The port the server will listen on if not running in docker", Value: "4000", Enabled: true, AppService: "server"},
|
||||
{Name: "server", Description: "The server we will use when connecting to the alplaprod sql", Value: "usmcd1vms006", Enabled: true, AppService: "server"},
|
||||
{Name: "timezone", Value: "America/Chicago", Description: "What time zone is the server in this is used for cronjobs and some other time stuff", AppService: "server", Enabled: true},
|
||||
{Name: "dbUser", Value: "alplaprod", Description: "What is the db userName", AppService: "server", Enabled: true},
|
||||
{Name: "dbPass", Value: "b2JlbGl4", Description: "What is the db password", AppService: "server", Enabled: true},
|
||||
{Name: "tcpPort", Value: "2222", Description: "TCP port for printers to connect send data and the zedra cameras", AppService: "server", Enabled: true},
|
||||
{Name: "prolinkCheck", Value: "1", Description: "Will prolink be considered to check if matches, maninly used in plants that do not fully utilize prolink + ocp", AppService: "production", Enabled: true},
|
||||
{Name: "bookin", Value: "1", Description: "do we want to book in after a label is printed", AppService: "ocp", Enabled: true},
|
||||
{Name: "dbServer", Value: "usmcd1vms036", Description: "What server is the prod db on?", AppService: "server", Enabled: true},
|
||||
{Name: "printDelay", Value: "90", Description: "How long in seconds between prints", AppService: "ocp", Enabled: true},
|
||||
{Name: "plantToken", Value: "test3", Description: "What is the plant token", AppService: "server", Enabled: true},
|
||||
{Name: "dualPrinting", Value: "0", Description: "Dose the plant have 2 machines that go to 1?", AppService: "ocp", Enabled: true},
|
||||
{Name: "ocmeService", Value: "0", Description: "Is the ocme service enabled. this is gernerally only for Dayton.", AppService: "ocme", Enabled: true},
|
||||
{Name: "fifoCheck", Value: "45", Description: "How far back do we want to check for fifo default 45, putting 0 will ignore.", AppService: "ocme", Enabled: true},
|
||||
{Name: "dayCheck", Value: "3", Description: "how many days +/- to check for shipments in alplaprod", AppService: "ocme", Enabled: true},
|
||||
{Name: "maxLotPerTruck", Value: "3", Description: "How mant lots can we have per truck?", AppService: "ocme", Enabled: true},
|
||||
{Name: "monitorAddress", Value: "8", Description: "What address is monitored to be limited to the amount of lots that can be added to a truck.", AppService: "ocme", Enabled: true},
|
||||
{Name: "ocmeCycleCount", Value: "1", Description: "Are we allowing ocme cycle counts?", AppService: "ocme", Enabled: true},
|
||||
{Name: "devDir", Value: "", Description: "This is the dev dir and strictly only for updating the servers.", AppService: "server", Enabled: true},
|
||||
{Name: "demandMGTActivated", Value: "0", Description: "Do we allow for new fake edi?", AppService: "logistics", Enabled: true},
|
||||
{Name: "qualityRequest", Value: "0", Description: "quality request module?", AppService: "quality", Enabled: true},
|
||||
{Name: "ocpLogsCheck", Value: "4", Description: "How long do we want to allow logs to show that have not been cleared?", AppService: "ocp", Enabled: true},
|
||||
{Name: "inhouseDelivery", Value: "0", Description: "Are we doing auto inhouse delivery?", AppService: "ocp", Enabled: true},
|
||||
// dyco settings
|
||||
{Name: "dycoConnect", Value: "0", Description: "Are we running the dyco system?", AppService: "dycp", Enabled: true},
|
||||
{Name: "dycoPrint", Value: "0", Description: "Are we using the dyco to get the labels or the rfid?", AppService: "dyco", Enabled: true},
|
||||
{Name: "strapperCheck", Value: "1", Description: "Are we monitoring the strapper for faults?", AppService: "dyco", Enabled: true},
|
||||
// ocp
|
||||
{Name: "ocpActive", Value: `1`, Description: "Are we pritning on demand?", AppService: "ocp", Enabled: true},
|
||||
{Name: "ocpCycleDelay", Value: `10`, Description: "How long between printer cycles do we want to monitor.", AppService: "ocp", Enabled: true},
|
||||
{Name: "pNgAddress", Value: `139`, Description: "What is the address for p&g so we can make sure we have the correct fake edi forcast going in.", AppService: "logisitcs", 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: "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 {
|
||||
|
||||
for _, cfg := range seedConfigData {
|
||||
var existing Settings
|
||||
// Try to find config by unique name
|
||||
result := db.Where("name =?", cfg.Name).First(&existing)
|
||||
// Try to find config by unique Name
|
||||
result := db.Where("Name =?", cfg.Name).First(&existing)
|
||||
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
@@ -38,7 +73,7 @@ func SeedConfigs(db *gorm.DB) error {
|
||||
if err := db.Create(&cfg).Error; err != nil {
|
||||
log.Printf("Failed to seed config %s: %v", cfg.Name, err)
|
||||
}
|
||||
log.Printf("Seeded new config: %s", cfg.Name)
|
||||
//log.Printf("Seeded new config: %s", cfg.Name)
|
||||
} else {
|
||||
// Some other error
|
||||
return result.Error
|
||||
@@ -50,18 +85,83 @@ func SeedConfigs(db *gorm.DB) error {
|
||||
log.Printf("Failed to update config %s: %v", cfg.Name, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Updated existing config: %s", cfg.Name)
|
||||
//log.Printf("Updated existing config: %s", cfg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAllConfigs(db *gorm.DB) ([]Settings, error) {
|
||||
func GetAllConfigs(db *gorm.DB) ([]map[string]interface{}, error) {
|
||||
var settings []Settings
|
||||
|
||||
result := db.Find(&settings)
|
||||
|
||||
return settings, result.Error
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
// Function to convert struct to map with lowercase keys
|
||||
toLowercase := func(s Settings) map[string]interface{} {
|
||||
t := reflect.TypeOf(s)
|
||||
v := reflect.ValueOf(s)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := strings.ToLower(t.Field(i).Name)
|
||||
data[field] = v.Field(i).Interface()
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Convert each struct in settings slice to a map with lowercase keys
|
||||
var lowercaseSettings []map[string]interface{}
|
||||
for _, setting := range settings {
|
||||
lowercaseSettings = append(lowercaseSettings, toLowercase(setting))
|
||||
}
|
||||
|
||||
return lowercaseSettings, nil
|
||||
}
|
||||
|
||||
func UpdateConfig(db *gorm.DB, id string, input inputs.SettingUpdateInput) error {
|
||||
var cfg Settings
|
||||
if err := db.Where("setting_id =?", id).First(&cfg).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
|
||||
if input.Description != nil {
|
||||
updates["description"] = *input.Description
|
||||
}
|
||||
if input.Value != nil {
|
||||
updates["value"] = *input.Value
|
||||
}
|
||||
if input.Enabled != nil {
|
||||
updates["enabled"] = *input.Enabled
|
||||
}
|
||||
if input.AppService != nil {
|
||||
updates["app_service"] = *input.AppService
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
return nil // nothing to update
|
||||
}
|
||||
|
||||
return db.Model(&cfg).Updates(updates).Error
|
||||
}
|
||||
|
||||
func DeleteConfig(db *gorm.DB, id uint) error {
|
||||
// Soft delete by ID
|
||||
return db.Delete(&Settings{}, id).Error
|
||||
}
|
||||
|
||||
func RestoreConfig(db *gorm.DB, id uint) error {
|
||||
var cfg Settings
|
||||
if err := db.Unscoped().First(&cfg, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.DeletedAt = gorm.DeletedAt{}
|
||||
return db.Unscoped().Save(&cfg).Error
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type ClientRecord struct {
|
||||
IPAddress string `gorm:"not null"`
|
||||
UserAgent string `gorm:"size:255"`
|
||||
ConnectedAt time.Time `gorm:"index"`
|
||||
LastHeartbeat time.Time `gorm:"index"`
|
||||
LastHeartbeat time.Time `gorm:"column:last_heartbeat"`
|
||||
Channels JSONB `gorm:"type:jsonb"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
|
||||
8
backend/utils/inputs/settingsInput.go
Normal file
8
backend/utils/inputs/settingsInput.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package inputs
|
||||
|
||||
type SettingUpdateInput struct {
|
||||
Description *string `json:"description"`
|
||||
Value *string `json:"value"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
AppService *string `json:"app_service"`
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "logistics_support_tool",
|
||||
"version": "0.0.1-alpha.5",
|
||||
"version": "0.0.1-alpha.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "logistics_support_tool",
|
||||
"version": "0.0.1-alpha.5",
|
||||
"version": "0.0.1-alpha.6",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "logistics_support_tool",
|
||||
"version": "0.0.1-alpha.5",
|
||||
"version": "0.0.1-alpha.6",
|
||||
"description": "This is the new logisitcs support tool",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
|
||||
Reference in New Issue
Block a user