add state history

This commit is contained in:
Fran Jurmanović
2025-05-27 20:18:58 +02:00
parent e52894c663
commit 56ef5e1484
12 changed files with 173 additions and 27 deletions

View File

@@ -25,11 +25,13 @@ func Init(di *dig.Container, app *fiber.App) {
}, },
}) })
serverIdGroup := groups.Group("/server/:id")
routeGroups := &common.RouteGroups{ routeGroups := &common.RouteGroups{
Api: groups.Group("/api"), Api: groups.Group("/api"),
Server: groups.Group("/server"), Server: groups.Group("/server"),
Config: groups.Group("/server/:id").Group("/config"), Config: serverIdGroup.Group("/config"),
Lookup: groups.Group("/lookup"), Lookup: groups.Group("/lookup"),
StateHistory: serverIdGroup.Group("/state-history"),
} }
groups.Use(basicAuthConfig) groups.Use(basicAuthConfig)

View File

@@ -40,6 +40,11 @@ func InitializeControllers(c *dig.Container) {
if err != nil { if err != nil {
log.Panic("unable to initialize lookup controller") log.Panic("unable to initialize lookup controller")
} }
err = c.Invoke(NewStateHistoryController)
if err != nil {
log.Panic("unable to initialize stateHistory controller")
}
} }
// FilteredResponse // FilteredResponse

View File

@@ -0,0 +1,43 @@
package controller
import (
"acc-server-manager/local/service"
"acc-server-manager/local/utl/common"
"github.com/gofiber/fiber/v2"
)
type StateHistoryController struct {
service *service.StateHistoryService
}
// NewStateHistoryController
// Initializes StateHistoryController.
//
// Args:
// *services.StateHistoryService: StateHistory service
// *Fiber.RouterGroup: Fiber Router Group
// Returns:
// *StateHistoryController: Controller for "StateHistory" interactions
func NewStateHistoryController(as *service.StateHistoryService, routeGroups *common.RouteGroups) *StateHistoryController {
ac := &StateHistoryController{
service: as,
}
routeGroups.StateHistory.Get("/", ac.getAll)
return ac
}
// getAll returns StateHistorys
//
// @Summary Return StateHistorys
// @Description Return StateHistorys
// @Tags StateHistory
// @Success 200 {array} string
// @Router /v1/StateHistory [get]
func (ac *StateHistoryController) getAll(c *fiber.Ctx) error {
StateHistoryID, _ := c.ParamsInt("id")
StateHistoryModel := ac.service.GetAll(c, StateHistoryID)
return c.JSON(StateHistoryModel)
}

View File

@@ -31,11 +31,6 @@ type PlayerState struct {
IsConnected bool IsConnected bool
} }
type AccServerInstance struct {
Model *Server
State *ServerState
}
type State struct { type State struct {
Session string `json:"session"` Session string `json:"session"`
SessionStart time.Time `json:"sessionStart"` SessionStart time.Time `json:"sessionStart"`
@@ -53,4 +48,12 @@ type ServerState struct {
MaxConnections int `json:"maxConnections"` MaxConnections int `json:"maxConnections"`
// Players map[int]*PlayerState // Players map[int]*PlayerState
// etc. // etc.
}
type StateHistory struct {
ID uint `gorm:"primaryKey" json:"id"`
ServerID uint `json:"serverId" gorm:"not null"`
Session string `json:"session"`
PlayerCount int `json:"playerCount"`
DateCreated time.Time `json:"dateCreated"`
} }

View File

@@ -11,6 +11,7 @@ import (
// *dig.Container: Dig Container // *dig.Container: Dig Container
func InitializeRepositories(c *dig.Container) { func InitializeRepositories(c *dig.Container) {
c.Provide(NewApiRepository) c.Provide(NewApiRepository)
c.Provide(NewStateHistoryRepository)
c.Provide(NewServerRepository) c.Provide(NewServerRepository)
c.Provide(NewConfigRepository) c.Provide(NewConfigRepository)
c.Provide(NewLookupRepository) c.Provide(NewLookupRepository)

View File

@@ -0,0 +1,45 @@
package repository
import (
"acc-server-manager/local/model"
"context"
"gorm.io/gorm"
)
type StateHistoryRepository struct {
db *gorm.DB
}
func NewStateHistoryRepository(db *gorm.DB) *StateHistoryRepository {
return &StateHistoryRepository{
db: db,
}
}
// GetAll
// Gets All rows from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ServerModel: Server object from database.
func (as StateHistoryRepository) GetAll(ctx context.Context, id int) *[]model.StateHistory {
db := as.db.WithContext(ctx)
ServerModel := new([]model.StateHistory)
db.Find(&ServerModel).Where("ID = ?", id)
return ServerModel
}
// UpdateServer
// Updates Server row from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// model.Server: Server object from database.
func (as StateHistoryRepository) Insert(ctx context.Context, body *model.StateHistory) *model.StateHistory {
db := as.db.WithContext(ctx)
db.Save(body)
return body
}

View File

@@ -8,22 +8,25 @@ import (
"log" "log"
"path/filepath" "path/filepath"
"sync" "sync"
"time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
type ServerService struct { type ServerService struct {
repository *repository.ServerRepository repository *repository.ServerRepository
stateHistoryRepo *repository.StateHistoryRepository
apiService *ApiService apiService *ApiService
instances sync.Map instances sync.Map
configService *ConfigService configService *ConfigService
} }
func NewServerService(repository *repository.ServerRepository, apiService *ApiService, configService *ConfigService) *ServerService { func NewServerService(repository *repository.ServerRepository, stateHistoryRepo *repository.StateHistoryRepository, apiService *ApiService, configService *ConfigService) *ServerService {
service := &ServerService{ service := &ServerService{
repository: repository, repository: repository,
apiService: apiService, apiService: apiService,
configService: configService, configService: configService,
stateHistoryRepo: stateHistoryRepo,
} }
servers := repository.GetAll(context.Background()) servers := repository.GetAll(context.Background())
for _, server := range *servers { for _, server := range *servers {
@@ -40,10 +43,13 @@ func NewServerService(repository *repository.ServerRepository, apiService *ApiSe
func (s *ServerService) StartAccServerRuntime(server *model.Server) { func (s *ServerService) StartAccServerRuntime(server *model.Server) {
s.instances.Delete(server.ID) s.instances.Delete(server.ID)
instance := tracking.NewAccServerInstance(server, func(states ...tracking.StateChange) { instance := tracking.NewAccServerInstance(server, func(state *model.ServerState, states ...tracking.StateChange) {
for _, state := range states { s.stateHistoryRepo.Insert(context.Background(), &model.StateHistory{
log.Println(tracking.StateChanges[state]) ServerID: server.ID,
} Session: state.Session,
PlayerCount: state.PlayerCount,
DateCreated: time.Now().UTC(),
})
}) })
config, _ := DecodeFileName(ConfigurationJson)(server.ConfigPath) config, _ := DecodeFileName(ConfigurationJson)(server.ConfigPath)
cfg := config.(model.Configuration) cfg := config.(model.Configuration)

View File

@@ -16,6 +16,7 @@ func InitializeServices(c *dig.Container) {
repository.InitializeRepositories(c) repository.InitializeRepositories(c)
c.Provide(NewServerService) c.Provide(NewServerService)
c.Provide(NewStateHistoryService)
c.Provide(NewApiService) c.Provide(NewApiService)
c.Provide(NewConfigService) c.Provide(NewConfigService)
c.Provide(NewLookupService) c.Provide(NewLookupService)

View File

@@ -0,0 +1,29 @@
package service
import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"github.com/gofiber/fiber/v2"
)
type StateHistoryService struct {
repository *repository.StateHistoryRepository
}
func NewStateHistoryService(repository *repository.StateHistoryRepository) *StateHistoryService {
return &StateHistoryService{
repository: repository,
}
}
// GetAll
// Gets All rows from StateHistory table.
//
// Args:
// context.Context: Application context
// Returns:
// string: Application version
func (as StateHistoryService) GetAll(ctx *fiber.Ctx, id int) *[]model.StateHistory {
return as.repository.GetAll(ctx.UserContext(), id)
}

View File

@@ -19,6 +19,7 @@ type RouteGroups struct {
Server fiber.Router Server fiber.Router
Config fiber.Router Config fiber.Router
Lookup fiber.Router Lookup fiber.Router
StateHistory fiber.Router
} }
func CheckError(err error) { func CheckError(err error) {

View File

@@ -55,6 +55,10 @@ func Migrate(db *gorm.DB) {
if err != nil { if err != nil {
panic("failed to migrate model.SessionType") panic("failed to migrate model.SessionType")
} }
err = db.AutoMigrate(&model.StateHistory{})
if err != nil {
panic("failed to migrate model.StateHistory")
}
db.FirstOrCreate(&model.ApiModel{Api: "Works"}) db.FirstOrCreate(&model.ApiModel{Api: "Works"})
Seed(db) Seed(db)

View File

@@ -24,10 +24,10 @@ var StateChanges = map[StateChange]string {
type AccServerInstance struct { type AccServerInstance struct {
Model *model.Server Model *model.Server
State *model.ServerState State *model.ServerState
OnStateChange func(...StateChange) OnStateChange func(*model.ServerState, ...StateChange)
} }
func NewAccServerInstance(server *model.Server, onStateChange func(...StateChange)) *AccServerInstance { func NewAccServerInstance(server *model.Server, onStateChange func(*model.ServerState, ...StateChange)) *AccServerInstance {
return &AccServerInstance{ return &AccServerInstance{
Model: server, Model: server,
State: &model.ServerState{PlayerCount: 0}, State: &model.ServerState{PlayerCount: 0},
@@ -109,8 +109,8 @@ var logStateContain = map[LogStateType]string {
var sessionChangeRegex = NewRegexHandler(`Session changed: (\w+) -> (\w+)`, logStateContain[SessionChange]) var sessionChangeRegex = NewRegexHandler(`Session changed: (\w+) -> (\w+)`, logStateContain[SessionChange])
var leaderboardUpdateRegex = NewRegexHandler(`Updated leaderboard for (\d+) clients`, logStateContain[LeaderboardUpdate]) var leaderboardUpdateRegex = NewRegexHandler(`Updated leaderboard for (\d+) clients`, logStateContain[LeaderboardUpdate])
var udpCountRegex = NewRegexHandler(`Udp message count ((\d+) client`, logStateContain[UDPCount]) var udpCountRegex = NewRegexHandler(`Udp message count (\d+) client`, logStateContain[UDPCount])
var clientsOnlineRegex = NewRegexHandler(`(\d+) client(s) online`, logStateContain[ClientsOnline]) var clientsOnlineRegex = NewRegexHandler(`(\d+) client\(s\) online`, logStateContain[ClientsOnline])
var logStateRegex = map[LogStateType]*StateRegexHandler { var logStateRegex = map[LogStateType]*StateRegexHandler {
SessionChange: sessionChangeRegex, SessionChange: sessionChangeRegex,
@@ -136,39 +136,45 @@ func (instance *AccServerInstance) HandleLogLine(line string) {
} }
} }
func (instance *AccServerInstance) UpdateState(callback func(state *model.ServerState)) { func (instance *AccServerInstance) UpdateState(callback func(state *model.ServerState, changes *[]StateChange)) {
state := instance.State state := instance.State
changes := []StateChange{}
state.Lock() state.Lock()
defer state.Unlock() defer state.Unlock()
callback(state) callback(state, &changes)
if (len(changes) > 0) {
instance.OnStateChange(state, changes...)
}
} }
func (instance *AccServerInstance) UpdatePlayerCount(count int) { func (instance *AccServerInstance) UpdatePlayerCount(count int) {
changes := []StateChange{} instance.UpdateState(func (state *model.ServerState, changes *[]StateChange) {
instance.UpdateState(func (state *model.ServerState) { if (count == state.PlayerCount) {
return
}
if (count > 0 && state.PlayerCount == 0) { if (count > 0 && state.PlayerCount == 0) {
state.SessionStart = time.Now() state.SessionStart = time.Now()
changes = append(changes, Session) *changes = append(*changes, Session)
} else if (count == 0) { } else if (count == 0) {
state.SessionStart = time.Time{} state.SessionStart = time.Time{}
changes = append(changes, Session) *changes = append(*changes, Session)
} }
state.PlayerCount = count state.PlayerCount = count
changes = append(changes, PlayerCount) *changes = append(*changes, PlayerCount)
}) })
instance.OnStateChange(changes...)
} }
func (instance *AccServerInstance) UpdateSessionChange(session string) { func (instance *AccServerInstance) UpdateSessionChange(session string) {
changes := []StateChange{} instance.UpdateState(func (state *model.ServerState, changes *[]StateChange) {
instance.UpdateState(func (state *model.ServerState) { if (session == state.Session) {
return
}
if (state.PlayerCount > 0) { if (state.PlayerCount > 0) {
state.SessionStart = time.Now() state.SessionStart = time.Now()
} else { } else {
state.SessionStart = time.Time{} state.SessionStart = time.Time{}
} }
state.Session = session state.Session = session
changes = append(changes, Session) *changes = append(*changes, Session)
}) })
instance.OnStateChange(changes...)
} }