add caching

This commit is contained in:
Fran Jurmanović
2025-05-28 19:59:43 +02:00
parent 0ced45ce55
commit 3dfbe77219
8 changed files with 382 additions and 73 deletions

View File

@@ -7,22 +7,28 @@ import (
"context"
"errors"
"strings"
"time"
"github.com/gofiber/fiber/v2"
)
type ApiService struct {
repository *repository.ApiRepository
serverRepository *repository.ServerRepository
serverService *ServerService
serverService *ServerService
statusCache *model.ServerStatusCache
}
func NewApiService(repository *repository.ApiRepository,
serverRepository *repository.ServerRepository,) *ApiService {
serverRepository *repository.ServerRepository) *ApiService {
return &ApiService{
repository: repository,
serverRepository: serverRepository,
statusCache: model.NewServerStatusCache(model.CacheConfig{
ExpirationTime: 30 * time.Second, // Cache expires after 30 seconds
ThrottleTime: 5 * time.Second, // Minimum 5 seconds between checks
DefaultStatus: model.StatusRunning, // Default to running if throttled
}),
}
}
@@ -35,9 +41,22 @@ func (as ApiService) GetStatus(ctx *fiber.Ctx) (string, error) {
if err != nil {
return "", err
}
status, err := as.StatusServer(serviceName)
return status, err
// Try to get status from cache
if status, shouldCheck := as.statusCache.GetStatus(serviceName); !shouldCheck {
return status.String(), nil
}
// If cache miss or expired, check actual status
statusStr, err := as.StatusServer(serviceName)
if err != nil {
return "", err
}
// Parse and update cache with new status
status := model.ParseServiceStatus(statusStr)
as.statusCache.UpdateStatus(serviceName, status)
return status.String(), nil
}
func (as ApiService) ApiStartServer(ctx *fiber.Ctx) (string, error) {
@@ -45,7 +64,19 @@ func (as ApiService) ApiStartServer(ctx *fiber.Ctx) (string, error) {
if err != nil {
return "", err
}
return as.StartServer(serviceName)
// Update status cache for this service before starting
as.statusCache.UpdateStatus(serviceName, model.StatusStarting)
statusStr, err := as.StartServer(serviceName)
if err != nil {
return "", err
}
// Parse and update cache with new status
status := model.ParseServiceStatus(statusStr)
as.statusCache.UpdateStatus(serviceName, status)
return status.String(), nil
}
func (as ApiService) ApiStopServer(ctx *fiber.Ctx) (string, error) {
@@ -53,7 +84,19 @@ func (as ApiService) ApiStopServer(ctx *fiber.Ctx) (string, error) {
if err != nil {
return "", err
}
return as.StopServer(serviceName)
// Update status cache for this service before stopping
as.statusCache.UpdateStatus(serviceName, model.StatusStopping)
statusStr, err := as.StopServer(serviceName)
if err != nil {
return "", err
}
// Parse and update cache with new status
status := model.ParseServiceStatus(statusStr)
as.statusCache.UpdateStatus(serviceName, status)
return status.String(), nil
}
func (as ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
@@ -61,7 +104,19 @@ func (as ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
if err != nil {
return "", err
}
return as.RestartServer(serviceName)
// Update status cache for this service before restarting
as.statusCache.UpdateStatus(serviceName, model.StatusRestarting)
statusStr, err := as.RestartServer(serviceName)
if err != nil {
return "", err
}
// Parse and update cache with new status
status := model.ParseServiceStatus(statusStr)
as.statusCache.UpdateStatus(serviceName, status)
return status.String(), nil
}
func (as ApiService) StatusServer(serviceName string) (string, error) {
@@ -99,7 +154,12 @@ func ManageService(serviceName string, action string) (string, error) {
return "", err
}
return strings.ReplaceAll(output, "\x00", ""), nil
// Clean up NSSM output by removing null bytes and trimming whitespace
cleaned := strings.TrimSpace(strings.ReplaceAll(output, "\x00", ""))
// Remove \r\n from status strings
cleaned = strings.TrimSuffix(cleaned, "\r\n")
return cleaned, nil
}
func (as ApiService) GetServiceName(ctx *fiber.Ctx) (string, error) {

View File

@@ -9,65 +9,76 @@ import (
type LookupService struct {
repository *repository.LookupRepository
cache *model.LookupCache
}
func NewLookupService(repository *repository.LookupRepository) *LookupService {
return &LookupService{
repository: repository,
cache: model.NewLookupCache(),
}
}
// GetTracks
// Gets Tracks rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// string: Application version
func (as LookupService) GetTracks(ctx *fiber.Ctx) *[]model.Track {
return as.repository.GetTracks(ctx.UserContext())
func (s *LookupService) GetTracks(ctx *fiber.Ctx) (interface{}, error) {
if cached, exists := s.cache.Get("tracks"); exists {
return cached, nil
}
tracks := s.repository.GetTracks(ctx.UserContext())
s.cache.Set("tracks", tracks)
return tracks, nil
}
// GetCarModels
// Gets CarModels rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupService) GetCarModels(ctx *fiber.Ctx) *[]model.CarModel {
return as.repository.GetCarModels(ctx.UserContext())
func (s *LookupService) GetCarModels(ctx *fiber.Ctx) (interface{}, error) {
if cached, exists := s.cache.Get("cars"); exists {
return cached, nil
}
cars := s.repository.GetCarModels(ctx.UserContext())
s.cache.Set("cars", cars)
return cars, nil
}
// GetDriverCategories
// Gets DriverCategories rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupService) GetDriverCategories(ctx *fiber.Ctx) *[]model.DriverCategory {
return as.repository.GetDriverCategories(ctx.UserContext())
func (s *LookupService) GetDriverCategories(ctx *fiber.Ctx) (interface{}, error) {
if cached, exists := s.cache.Get("drivers"); exists {
return cached, nil
}
categories := s.repository.GetDriverCategories(ctx.UserContext())
s.cache.Set("drivers", categories)
return categories, nil
}
// GetCupCategories
// Gets CupCategories rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupService) GetCupCategories(ctx *fiber.Ctx) *[]model.CupCategory {
return as.repository.GetCupCategories(ctx.UserContext())
func (s *LookupService) GetCupCategories(ctx *fiber.Ctx) (interface{}, error) {
if cached, exists := s.cache.Get("cups"); exists {
return cached, nil
}
categories := s.repository.GetCupCategories(ctx.UserContext())
s.cache.Set("cups", categories)
return categories, nil
}
// GetSessionTypes
// Gets SessionTypes rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupService) GetSessionTypes(ctx *fiber.Ctx) *[]model.SessionType {
return as.repository.GetSessionTypes(ctx.UserContext())
func (s *LookupService) GetSessionTypes(ctx *fiber.Ctx) (interface{}, error) {
if cached, exists := s.cache.Get("sessions"); exists {
return cached, nil
}
types := s.repository.GetSessionTypes(ctx.UserContext())
s.cache.Set("sessions", types)
return types, nil
}
// ClearCache clears all cached lookup data
func (s *LookupService) ClearCache() {
s.cache.Clear()
}
// PreloadCache loads all lookup data into cache
func (s *LookupService) PreloadCache(ctx *fiber.Ctx) {
s.GetTracks(ctx)
s.GetCarModels(ctx)
s.GetDriverCategories(ctx)
s.GetCupCategories(ctx)
s.GetSessionTypes(ctx)
}

View File

@@ -145,7 +145,7 @@ func (as ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]m
if err != nil {
log.Print(err.Error())
}
(*servers)[i].Status = model.ServiceStatus(status)
(*servers)[i].Status = model.ParseServiceStatus(status)
instance, ok := as.instances.Load(server.ID)
if !ok {
log.Print("Unable to retrieve instance for server of ID: ", server.ID)
@@ -176,7 +176,7 @@ func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) (*model.Server, er
if err != nil {
log.Print(err.Error())
}
server.Status = model.ServiceStatus(status)
server.Status = model.ParseServiceStatus(status)
instance, ok := as.instances.Load(server.ID)
if !ok {
log.Print("Unable to retrieve instance for server of ID: ", server.ID)

View File

@@ -2,6 +2,7 @@ package service
import (
"acc-server-manager/local/repository"
"context"
"log"
"go.uber.org/dig"
@@ -21,11 +22,18 @@ func InitializeServices(c *dig.Container) {
c.Provide(NewConfigService)
c.Provide(NewLookupService)
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService) {
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, lookup *LookupService) {
api.SetServerService(server)
config.SetServerService(server)
// 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()))
})
if err != nil {
log.Panic("unable to initialize server service in api service")
log.Panic("unable to initialize services:", err)
}
}