security measures
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
|
||||
@@ -10,85 +9,36 @@ import (
|
||||
|
||||
type LookupService struct {
|
||||
repository *repository.LookupRepository
|
||||
cache *model.LookupCache
|
||||
}
|
||||
|
||||
func NewLookupService(repository *repository.LookupRepository, cache *model.LookupCache) *LookupService {
|
||||
func NewLookupService(repository *repository.LookupRepository) *LookupService {
|
||||
logging.Debug("Initializing LookupService")
|
||||
return &LookupService{
|
||||
repository: repository,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LookupService) GetTracks(ctx *fiber.Ctx) (interface{}, error) {
|
||||
if cached, exists := s.cache.Get("tracks"); exists {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading tracks from database")
|
||||
tracks := s.repository.GetTracks(ctx.UserContext())
|
||||
s.cache.Set("tracks", tracks)
|
||||
return tracks, nil
|
||||
logging.Debug("Getting tracks")
|
||||
return s.repository.GetTracks(ctx.UserContext())
|
||||
}
|
||||
|
||||
func (s *LookupService) GetCarModels(ctx *fiber.Ctx) (interface{}, error) {
|
||||
if cached, exists := s.cache.Get("cars"); exists {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading car models from database")
|
||||
cars := s.repository.GetCarModels(ctx.UserContext())
|
||||
s.cache.Set("cars", cars)
|
||||
return cars, nil
|
||||
logging.Debug("Getting car models")
|
||||
return s.repository.GetCarModels(ctx.UserContext())
|
||||
}
|
||||
|
||||
func (s *LookupService) GetDriverCategories(ctx *fiber.Ctx) (interface{}, error) {
|
||||
if cached, exists := s.cache.Get("drivers"); exists {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading driver categories from database")
|
||||
categories := s.repository.GetDriverCategories(ctx.UserContext())
|
||||
s.cache.Set("drivers", categories)
|
||||
return categories, nil
|
||||
logging.Debug("Getting driver categories")
|
||||
return s.repository.GetDriverCategories(ctx.UserContext())
|
||||
}
|
||||
|
||||
func (s *LookupService) GetCupCategories(ctx *fiber.Ctx) (interface{}, error) {
|
||||
if cached, exists := s.cache.Get("cups"); exists {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading cup categories from database")
|
||||
categories := s.repository.GetCupCategories(ctx.UserContext())
|
||||
s.cache.Set("cups", categories)
|
||||
return categories, nil
|
||||
logging.Debug("Getting cup categories")
|
||||
return s.repository.GetCupCategories(ctx.UserContext())
|
||||
}
|
||||
|
||||
func (s *LookupService) GetSessionTypes(ctx *fiber.Ctx) (interface{}, error) {
|
||||
if cached, exists := s.cache.Get("sessions"); exists {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
logging.Debug("Loading session types from database")
|
||||
types := s.repository.GetSessionTypes(ctx.UserContext())
|
||||
s.cache.Set("sessions", types)
|
||||
return types, nil
|
||||
}
|
||||
|
||||
// ClearCache clears all cached lookup data
|
||||
func (s *LookupService) ClearCache() {
|
||||
logging.Debug("Clearing all lookup cache data")
|
||||
s.cache.Clear()
|
||||
}
|
||||
|
||||
// PreloadCache loads all lookup data into cache
|
||||
func (s *LookupService) PreloadCache(ctx *fiber.Ctx) {
|
||||
logging.Debug("Preloading all lookup cache data")
|
||||
s.GetTracks(ctx)
|
||||
s.GetCarModels(ctx)
|
||||
s.GetDriverCategories(ctx)
|
||||
s.GetCupCategories(ctx)
|
||||
s.GetSessionTypes(ctx)
|
||||
logging.Debug("Completed preloading lookup cache data")
|
||||
logging.Debug("Getting session types")
|
||||
return s.repository.GetSessionTypes(ctx.UserContext())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"context"
|
||||
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
@@ -18,12 +16,6 @@ func InitializeServices(c *dig.Container) {
|
||||
logging.Debug("Initializing repositories")
|
||||
repository.InitializeRepositories(c)
|
||||
|
||||
// Provide caches
|
||||
logging.Debug("Creating lookup cache instance")
|
||||
c.Provide(func() *model.LookupCache {
|
||||
return model.NewLookupCache()
|
||||
})
|
||||
|
||||
logging.Debug("Registering services")
|
||||
// Provide services
|
||||
c.Provide(NewServerService)
|
||||
@@ -37,26 +29,12 @@ func InitializeServices(c *dig.Container) {
|
||||
c.Provide(NewFirewallService)
|
||||
|
||||
logging.Debug("Initializing service dependencies")
|
||||
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, lookup *LookupService, systemConfig *SystemConfigService) {
|
||||
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, systemConfig *SystemConfigService) {
|
||||
logging.Debug("Setting up service cross-references")
|
||||
api.SetServerService(server)
|
||||
config.SetServerService(server)
|
||||
|
||||
logging.Debug("Initializing lookup data cache")
|
||||
// Initialize lookup data using repository directly
|
||||
lookup.cache.Set("tracks", lookup.repository.GetTracks(context.Background()))
|
||||
lookup.cache.Set("cars", lookup.repository.GetCarModels(context.Background()))
|
||||
lookup.cache.Set("drivers", lookup.repository.GetDriverCategories(context.Background()))
|
||||
lookup.cache.Set("cups", lookup.repository.GetCupCategories(context.Background()))
|
||||
lookup.cache.Set("sessions", lookup.repository.GetSessionTypes(context.Background()))
|
||||
logging.Debug("Completed initializing lookup data cache")
|
||||
|
||||
logging.Debug("Initializing system config service")
|
||||
// Initialize system config service
|
||||
if err := systemConfig.Initialize(context.Background()); err != nil {
|
||||
logging.Panic("failed to initialize system config service: " + err.Error())
|
||||
}
|
||||
logging.Debug("Completed initializing system config service")
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
logging.Panic("unable to initialize services: " + err.Error())
|
||||
|
||||
@@ -3,11 +3,11 @@ package service
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"sort"
|
||||
"time"
|
||||
"acc-server-manager/pkg/logging"
|
||||
"sync"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type StateHistoryService struct {
|
||||
@@ -15,18 +15,9 @@ type StateHistoryService struct {
|
||||
}
|
||||
|
||||
func NewStateHistoryService(repository *repository.StateHistoryRepository) *StateHistoryService {
|
||||
return &StateHistoryService{
|
||||
repository: repository,
|
||||
}
|
||||
return &StateHistoryService{repository: repository}
|
||||
}
|
||||
|
||||
// GetAll
|
||||
// Gets All rows from StateHistory table.
|
||||
//
|
||||
// Args:
|
||||
// context.Context: Application context
|
||||
// Returns:
|
||||
// string: Application version
|
||||
func (s *StateHistoryService) GetAll(ctx *fiber.Ctx, filter *model.StateHistoryFilter) (*[]model.StateHistory, error) {
|
||||
result, err := s.repository.GetAll(ctx.UserContext(), filter)
|
||||
if err != nil {
|
||||
@@ -44,168 +35,99 @@ func (s *StateHistoryService) Insert(ctx *fiber.Ctx, model *model.StateHistory)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StateHistoryService) GetLastSessionID(ctx *fiber.Ctx, serverID uint) (uint, error) {
|
||||
return s.repository.GetLastSessionID(ctx.UserContext(), serverID)
|
||||
}
|
||||
|
||||
func (s *StateHistoryService) GetStatistics(ctx *fiber.Ctx, filter *model.StateHistoryFilter) (*model.StateHistoryStats, error) {
|
||||
// Get all state history entries based on filter
|
||||
entries, err := s.repository.GetAll(ctx.UserContext(), filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting state history for statistics: %v", err)
|
||||
stats := &model.StateHistoryStats{}
|
||||
var mu sync.Mutex
|
||||
|
||||
eg, gCtx := errgroup.WithContext(ctx.UserContext())
|
||||
|
||||
// Get Summary Stats (Peak/Avg Players, Total Sessions)
|
||||
eg.Go(func() error {
|
||||
summary, err := s.repository.GetSummaryStats(gCtx, filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting summary stats: %v", err)
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
stats.PeakPlayers = summary.PeakPlayers
|
||||
stats.AveragePlayers = summary.AveragePlayers
|
||||
stats.TotalSessions = summary.TotalSessions
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Get Total Playtime
|
||||
eg.Go(func() error {
|
||||
playtime, err := s.repository.GetTotalPlaytime(gCtx, filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting total playtime: %v", err)
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
stats.TotalPlaytime = playtime
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Get Player Count Over Time
|
||||
eg.Go(func() error {
|
||||
playerCount, err := s.repository.GetPlayerCountOverTime(gCtx, filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting player count over time: %v", err)
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
stats.PlayerCountOverTime = playerCount
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Get Session Types
|
||||
eg.Go(func() error {
|
||||
sessionTypes, err := s.repository.GetSessionTypes(gCtx, filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting session types: %v", err)
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
stats.SessionTypes = sessionTypes
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Get Daily Activity
|
||||
eg.Go(func() error {
|
||||
dailyActivity, err := s.repository.GetDailyActivity(gCtx, filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting daily activity: %v", err)
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
stats.DailyActivity = dailyActivity
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Get Recent Sessions
|
||||
eg.Go(func() error {
|
||||
recentSessions, err := s.repository.GetRecentSessions(gCtx, filter)
|
||||
if err != nil {
|
||||
logging.Error("Error getting recent sessions: %v", err)
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
stats.RecentSessions = recentSessions
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats := &model.StateHistoryStats{
|
||||
PlayerCountOverTime: make([]model.PlayerCountPoint, 0),
|
||||
SessionTypes: make([]model.SessionCount, 0),
|
||||
DailyActivity: make([]model.DailyActivity, 0),
|
||||
RecentSessions: make([]model.RecentSession, 0),
|
||||
}
|
||||
|
||||
if len(*entries) == 0 {
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// Maps to track unique sessions and their details
|
||||
sessionMap := make(map[uint]*struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Session string
|
||||
Track string
|
||||
MaxPlayers int
|
||||
SessionConcluded bool
|
||||
})
|
||||
|
||||
// Maps for aggregating statistics
|
||||
dailySessionCount := make(map[string]int)
|
||||
sessionTypeCount := make(map[string]int)
|
||||
totalPlayers := 0
|
||||
peakPlayers := 0
|
||||
|
||||
// Process each state history entry
|
||||
for _, entry := range *entries {
|
||||
// Track player count over time
|
||||
stats.PlayerCountOverTime = append(stats.PlayerCountOverTime, model.PlayerCountPoint{
|
||||
Timestamp: entry.DateCreated,
|
||||
Count: entry.PlayerCount,
|
||||
})
|
||||
|
||||
// Update peak players
|
||||
if entry.PlayerCount > peakPlayers {
|
||||
peakPlayers = entry.PlayerCount
|
||||
}
|
||||
|
||||
totalPlayers += entry.PlayerCount
|
||||
|
||||
// Process session information using SessionID
|
||||
if _, exists := sessionMap[entry.SessionID]; !exists {
|
||||
sessionMap[entry.SessionID] = &struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Session string
|
||||
Track string
|
||||
MaxPlayers int
|
||||
SessionConcluded bool
|
||||
}{
|
||||
StartTime: entry.DateCreated,
|
||||
Session: entry.Session,
|
||||
Track: entry.Track,
|
||||
MaxPlayers: entry.PlayerCount,
|
||||
SessionConcluded: false,
|
||||
}
|
||||
|
||||
// Count session types
|
||||
sessionTypeCount[entry.Session]++
|
||||
|
||||
// Count daily sessions
|
||||
dateStr := entry.DateCreated.Format("2006-01-02")
|
||||
dailySessionCount[dateStr]++
|
||||
} else {
|
||||
session := sessionMap[entry.SessionID]
|
||||
if session.SessionConcluded {
|
||||
continue
|
||||
}
|
||||
if (entry.PlayerCount == 0) {
|
||||
session.SessionConcluded = true
|
||||
}
|
||||
session.EndTime = entry.DateCreated
|
||||
if entry.PlayerCount > session.MaxPlayers {
|
||||
session.MaxPlayers = entry.PlayerCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, session := range sessionMap {
|
||||
if !session.SessionConcluded {
|
||||
session.SessionConcluded = true
|
||||
}
|
||||
if (session.MaxPlayers == 0) {
|
||||
delete(sessionMap, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate statistics
|
||||
stats.PeakPlayers = peakPlayers
|
||||
stats.TotalSessions = len(sessionMap)
|
||||
if len(*entries) > 0 {
|
||||
stats.AveragePlayers = float64(totalPlayers) / float64(len(*entries))
|
||||
}
|
||||
|
||||
// Process session types
|
||||
for sessionType, count := range sessionTypeCount {
|
||||
stats.SessionTypes = append(stats.SessionTypes, model.SessionCount{
|
||||
Name: sessionType,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
||||
// Process daily activity
|
||||
for dateStr, count := range dailySessionCount {
|
||||
date, _ := time.Parse("2006-01-02", dateStr)
|
||||
stats.DailyActivity = append(stats.DailyActivity, model.DailyActivity{
|
||||
Date: date,
|
||||
SessionsCount: count,
|
||||
})
|
||||
}
|
||||
|
||||
// Calculate total playtime and prepare recent sessions
|
||||
var recentSessions []model.RecentSession
|
||||
totalPlaytime := 0
|
||||
|
||||
for sessionID, session := range sessionMap {
|
||||
if !session.EndTime.IsZero() {
|
||||
duration := int(session.EndTime.Sub(session.StartTime).Minutes())
|
||||
totalPlaytime += duration
|
||||
|
||||
recentSessions = append(recentSessions, model.RecentSession{
|
||||
ID: sessionID,
|
||||
Date: session.StartTime,
|
||||
Type: session.Session,
|
||||
Track: session.Track,
|
||||
Duration: duration,
|
||||
Players: session.MaxPlayers,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
stats.TotalPlaytime = totalPlaytime
|
||||
|
||||
// Sort recent sessions by date (newest first) and limit to last 10
|
||||
sort.Slice(recentSessions, func(i, j int) bool {
|
||||
return recentSessions[i].Date.After(recentSessions[j].Date)
|
||||
})
|
||||
|
||||
if len(recentSessions) > 10 {
|
||||
recentSessions = recentSessions[:10]
|
||||
}
|
||||
stats.RecentSessions = recentSessions
|
||||
|
||||
// Sort daily activity by date
|
||||
sort.Slice(stats.DailyActivity, func(i, j int) bool {
|
||||
return stats.DailyActivity[i].Date.Before(stats.DailyActivity[j].Date)
|
||||
})
|
||||
|
||||
// Sort player count over time by timestamp
|
||||
sort.Slice(stats.PlayerCountOverTime, func(i, j int) bool {
|
||||
return stats.PlayerCountOverTime[i].Timestamp.Before(stats.PlayerCountOverTime[j].Timestamp)
|
||||
})
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -3,79 +3,41 @@ package service
|
||||
import (
|
||||
"acc-server-manager/local/model"
|
||||
"acc-server-manager/local/repository"
|
||||
"acc-server-manager/local/utl/cache"
|
||||
"acc-server-manager/local/utl/logging"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
"go.uber.org/dig"
|
||||
const (
|
||||
configCacheDuration = 24 * time.Hour
|
||||
)
|
||||
|
||||
type SystemConfigService struct {
|
||||
repository *repository.SystemConfigRepository
|
||||
cache *model.LookupCache
|
||||
}
|
||||
|
||||
// SystemConfigServiceParams holds the dependencies for SystemConfigService
|
||||
type SystemConfigServiceParams struct {
|
||||
dig.In
|
||||
|
||||
Repository *repository.SystemConfigRepository
|
||||
Cache *model.LookupCache
|
||||
cache *cache.InMemoryCache
|
||||
}
|
||||
|
||||
// NewSystemConfigService creates a new SystemConfigService with dependencies injected by dig
|
||||
func NewSystemConfigService(params SystemConfigServiceParams) *SystemConfigService {
|
||||
func NewSystemConfigService(repository *repository.SystemConfigRepository, cache *cache.InMemoryCache) *SystemConfigService {
|
||||
logging.Debug("Initializing SystemConfigService")
|
||||
return &SystemConfigService{
|
||||
repository: params.Repository,
|
||||
cache: params.Cache,
|
||||
repository: repository,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) Initialize(ctx context.Context) error {
|
||||
logging.Debug("Initializing system config cache")
|
||||
// Cache all configs
|
||||
configs, err := s.repository.GetAll(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get configs for caching: %v", err)
|
||||
}
|
||||
|
||||
for _, config := range *configs {
|
||||
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, config.Key)
|
||||
s.cache.Set(cacheKey, &config)
|
||||
logging.Debug("Cached system config: %s", config.Key)
|
||||
}
|
||||
|
||||
logging.Debug("Completed initializing system config cache")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) GetConfig(ctx context.Context, key string) (*model.SystemConfig, error) {
|
||||
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, key)
|
||||
|
||||
// Try to get from cache first
|
||||
if cached, exists := s.cache.Get(cacheKey); exists {
|
||||
if config, ok := cached.(*model.SystemConfig); ok {
|
||||
return config, nil
|
||||
}
|
||||
logging.Debug("Invalid type in cache for key: %s", key)
|
||||
|
||||
fetcher := func() (*model.SystemConfig, error) {
|
||||
logging.Debug("Loading system config from database: %s", key)
|
||||
return s.repository.Get(ctx, key)
|
||||
}
|
||||
|
||||
// If not in cache, get from database
|
||||
logging.Debug("Loading system config from database: %s", key)
|
||||
config, err := s.repository.Get(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
logging.Error("Configuration not found for key: %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
s.cache.Set(cacheKey, config)
|
||||
return config, nil
|
||||
return cache.GetOrSet(s.cache, cacheKey, configCacheDuration, fetcher)
|
||||
}
|
||||
|
||||
func (s *SystemConfigService) GetAllConfigs(ctx context.Context) (*[]model.SystemConfig, error) {
|
||||
@@ -88,10 +50,10 @@ func (s *SystemConfigService) UpdateConfig(ctx context.Context, config *model.Sy
|
||||
return err
|
||||
}
|
||||
|
||||
// Update cache
|
||||
// Invalidate cache
|
||||
cacheKey := fmt.Sprintf(model.CacheKeySystemConfig, config.Key)
|
||||
s.cache.Set(cacheKey, config)
|
||||
logging.Debug("Updated system config in cache: %s", config.Key)
|
||||
s.cache.Delete(cacheKey)
|
||||
logging.Debug("Invalidated system config in cache: %s", config.Key)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user