security measures

This commit is contained in:
Fran Jurmanović
2025-06-25 22:37:38 +02:00
parent 1ecd558e18
commit 69733e4940
16 changed files with 614 additions and 506 deletions

View File

@@ -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())
}

View File

@@ -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())

View File

@@ -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
}

View File

@@ -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
}