api with functioning managment

This commit is contained in:
Fran Jurmanović
2025-02-05 00:26:12 +01:00
parent 954fe0ae82
commit 9118574203
26 changed files with 2017 additions and 55 deletions

View File

@@ -26,7 +26,10 @@ func Init(di *dig.Container, app *fiber.App) {
})
routeGroups := &common.RouteGroups{
Api: groups.Group("/api"),
Api: groups.Group("/api"),
Server: groups.Group("/server"),
Config: groups.Group("/server/:id").Group("/config"),
Lookup: groups.Group("/lookup"),
}
routeGroups.Api.Use(basicAuthConfig)

View File

@@ -25,7 +25,10 @@ func NewApiController(as *service.ApiService, routeGroups *common.RouteGroups) *
}
routeGroups.Api.Get("/", ac.getFirst)
routeGroups.Api.Post("/", ac.startServer)
routeGroups.Api.Get("/:service", ac.getStatus)
routeGroups.Api.Post("/start", ac.startServer)
routeGroups.Api.Post("/stop", ac.stopServer)
routeGroups.Api.Post("/restart", ac.restartServer)
return ac
}
@@ -42,14 +45,30 @@ func (ac *ApiController) getFirst(c *fiber.Ctx) error {
return c.SendString(apiModel.Api)
}
// startServer returns API
// getStatus returns service status
//
// @Summary Return API
// @Description Return API
// @Summary Return service status
// @Description Returns service status
// @Param service path string true "required"
// @Tags api
// @Success 200 {array} string
// @Router /v1/api/{service} [get]
func (ac *ApiController) getStatus(c *fiber.Ctx) error {
apiModel, err := ac.service.GetStatus(c)
if err != nil {
return c.Status(400).SendString(err.Error())
}
return c.SendString(apiModel)
}
// startServer starts service
//
// @Summary Start service
// @Description Starts service
// @Param name body string true "required"
// @Tags api
// @Success 200 {array} string
// @Router /v1/api [post]
// @Router /v1/api/start [post]
func (ac *ApiController) startServer(c *fiber.Ctx) error {
model := new(Service)
if err := c.BodyParser(model); err != nil {
@@ -58,7 +77,49 @@ func (ac *ApiController) startServer(c *fiber.Ctx) error {
c.Locals("service", model.Name)
apiModel, err := ac.service.ApiStartServer(c)
if err != nil {
return c.SendStatus(400)
return c.Status(400).SendString(err.Error())
}
return c.SendString(apiModel)
}
// stopServer stops service
//
// @Summary Stop service
// @Description Stops service
// @Param name body string true "required"
// @Tags api
// @Success 200 {array} string
// @Router /v1/api/stop [post]
func (ac *ApiController) stopServer(c *fiber.Ctx) error {
model := new(Service)
if err := c.BodyParser(model); err != nil {
c.SendStatus(400)
}
c.Locals("service", model.Name)
apiModel, err := ac.service.ApiStopServer(c)
if err != nil {
return c.Status(400).SendString(err.Error())
}
return c.SendString(apiModel)
}
// restartServer returns API
//
// @Summary Restart service
// @Description Restarts service
// @Param name body string true "required"
// @Tags api
// @Success 200 {array} string
// @Router /v1/api/restart [post]
func (ac *ApiController) restartServer(c *fiber.Ctx) error {
model := new(Service)
if err := c.BodyParser(model); err != nil {
c.SendStatus(400)
}
c.Locals("service", model.Name)
apiModel, err := ac.service.ApiRestartServer(c)
if err != nil {
return c.Status(400).SendString(err.Error())
}
return c.SendString(apiModel)
}

View File

@@ -0,0 +1,89 @@
package controller
import (
"acc-server-manager/local/service"
"acc-server-manager/local/utl/common"
"github.com/gofiber/fiber/v2"
)
type ConfigController struct {
service *service.ConfigService
}
// NewConfigController
// Initializes ConfigController.
//
// Args:
// *services.ConfigService: Config service
// *Fiber.RouterGroup: Fiber Router Group
// Returns:
// *ConfigController: Controller for "Config" interactions
func NewConfigController(as *service.ConfigService, routeGroups *common.RouteGroups) *ConfigController {
ac := &ConfigController{
service: as,
}
routeGroups.Config.Put("/:file", ac.updateConfig)
routeGroups.Config.Get("/:file", ac.getConfig)
routeGroups.Config.Get("/", ac.getConfigs)
return ac
}
// updateConfig returns Config
//
// @Summary Update config
// @Description Updates config
// @Param id path number true "required"
// @Param file path string true "required"
// @Param content body string true "required"
// @Tags Config
// @Success 200 {array} string
// @Router /v1/server/{id}/config/{file} [put]
func (ac *ConfigController) updateConfig(c *fiber.Ctx) error {
var config map[string]interface{}
if err := c.BodyParser(&config); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "Invalid config format"})
}
ConfigModel, err := ac.service.UpdateConfig(c, &config)
if err != nil {
return c.Status(400).SendString(err.Error())
}
return c.JSON(ConfigModel)
}
// getConfig returns Config
//
// @Summary Return Config file
// @Description Returns Config file
// @Param id path number true "required"
// @Param file path string true "required"
// @Tags Config
// @Success 200 {array} string
// @Router /v1/server/{id}/config/{file} [get]
func (ac *ConfigController) getConfig(c *fiber.Ctx) error {
Model, err := ac.service.GetConfig(c)
if err != nil {
return c.Status(400).SendString(err.Error())
}
return c.JSON(Model)
}
// getConfigs returns Config
//
// @Summary Return Configs
// @Description Return Config files
// @Param id path number true "required"
// @Tags Config
// @Success 200 {array} string
// @Router /v1/server/{id}/config [get]
func (ac *ConfigController) getConfigs(c *fiber.Ctx) error {
Model, err := ac.service.GetConfigs(c)
if err != nil {
return c.Status(400).SendString(err.Error())
}
return c.JSON(Model)
}

View File

@@ -24,6 +24,21 @@ func InitializeControllers(c *dig.Container) {
if err != nil {
panic("unable to initialize api controller")
}
err = c.Invoke(NewConfigController)
if err != nil {
panic("unable to initialize config controller")
}
err = c.Invoke(NewServerController)
if err != nil {
panic("unable to initialize server controller")
}
err = c.Invoke(NewLookupController)
if err != nil {
panic("unable to initialize lookup controller")
}
}
// FilteredResponse

View File

@@ -0,0 +1,93 @@
package controller
import (
"acc-server-manager/local/service"
"acc-server-manager/local/utl/common"
"github.com/gofiber/fiber/v2"
)
type LookupController struct {
service *service.LookupService
}
// NewLookupController
// Initializes LookupController.
//
// Args:
// *services.LookupService: Lookup service
// *Fiber.RouterGroup: Fiber Router Group
// Returns:
// *LookupController: Controller for "Lookup" interactions
func NewLookupController(as *service.LookupService, routeGroups *common.RouteGroups) *LookupController {
ac := &LookupController{
service: as,
}
routeGroups.Lookup.Get("/tracks", ac.getTracks)
routeGroups.Lookup.Get("/car-models", ac.getCarModels)
routeGroups.Lookup.Get("/driver-categories", ac.getDriverCategories)
routeGroups.Lookup.Get("/cup-categories", ac.getCupCategories)
routeGroups.Lookup.Get("/session-types", ac.getSessionTypes)
return ac
}
// getTracks returns Tracks
//
// @Summary Return Tracks Lookup
// @Description Return Tracks Lookup
// @Tags Lookup
// @Success 200 {array} string
// @Router /v1/lookup/tracks [get]
func (ac *LookupController) getTracks(c *fiber.Ctx) error {
LookupModel := ac.service.GetTracks(c)
return c.JSON(LookupModel)
}
// getCarModels returns CarModels
//
// @Summary Return CarModels Lookup
// @Description Return CarModels Lookup
// @Tags Lookup
// @Success 200 {array} string
// @Router /v1/lookup/car-models [get]
func (ac *LookupController) getCarModels(c *fiber.Ctx) error {
LookupModel := ac.service.GetCarModels(c)
return c.JSON(LookupModel)
}
// getDriverCategories returns DriverCategories
//
// @Summary Return DriverCategories Lookup
// @Description Return DriverCategories Lookup
// @Tags Lookup
// @Success 200 {array} string
// @Router /v1/lookup/driver-categories [get]
func (ac *LookupController) getDriverCategories(c *fiber.Ctx) error {
LookupModel := ac.service.GetDriverCategories(c)
return c.JSON(LookupModel)
}
// getCupCategories returns CupCategories
//
// @Summary Return CupCategories Lookup
// @Description Return CupCategories Lookup
// @Tags Lookup
// @Success 200 {array} string
// @Router /v1/lookup/cup-categories [get]
func (ac *LookupController) getCupCategories(c *fiber.Ctx) error {
LookupModel := ac.service.GetCupCategories(c)
return c.JSON(LookupModel)
}
// getSessionTypes returns SessionTypes
//
// @Summary Return SessionTypes Lookup
// @Description Return SessionTypes Lookup
// @Tags Lookup
// @Success 200 {array} string
// @Router /v1/lookup/session-types [get]
func (ac *LookupController) getSessionTypes(c *fiber.Ctx) error {
LookupModel := ac.service.GetSessionTypes(c)
return c.JSON(LookupModel)
}

View File

@@ -0,0 +1,42 @@
package controller
import (
"acc-server-manager/local/service"
"acc-server-manager/local/utl/common"
"github.com/gofiber/fiber/v2"
)
type ServerController struct {
service *service.ServerService
}
// NewServerController
// Initializes ServerController.
//
// Args:
// *services.ServerService: Server service
// *Fiber.RouterGroup: Fiber Router Group
// Returns:
// *ServerController: Controller for "Server" interactions
func NewServerController(as *service.ServerService, routeGroups *common.RouteGroups) *ServerController {
ac := &ServerController{
service: as,
}
routeGroups.Server.Get("/", ac.getAll)
return ac
}
// getAll returns Servers
//
// @Summary Return Servers
// @Description Return Servers
// @Tags Server
// @Success 200 {array} string
// @Router /v1/server [get]
func (ac *ServerController) getAll(c *fiber.Ctx) error {
ServerModel := ac.service.GetAll(c)
return c.JSON(ServerModel)
}

View File

@@ -3,3 +3,9 @@ package model
type ApiModel struct {
Api string `json:"api"`
}
// ServiceStatus represents a Windows service state
type ServiceStatus struct {
Name string `json:"name"`
Status string `json:"status"` // "running", "stopped", "pending"
}

21
local/model/config.go Normal file
View File

@@ -0,0 +1,21 @@
package model
import "time"
// Config tracks configuration modifications
type Config struct {
ID uint `gorm:"primaryKey"`
ServerID uint `gorm:"not null"`
ConfigFile string `gorm:"not null"` // e.g. "settings.json"
OldConfig string `gorm:"type:text"`
NewConfig string `gorm:"type:text"`
ChangedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
}
type Configurations struct {
Configuration map[string]interface{}
Entrylist map[string]interface{}
Event map[string]interface{}
EventRules map[string]interface{}
Settings map[string]interface{}
}

32
local/model/lookup.go Normal file
View File

@@ -0,0 +1,32 @@
package model
// Track represents a track and its capacity
type Track struct {
Name string `json:"track" gorm:"primaryKey;size:50"`
UniquePitBoxes int `json:"unique_pit_boxes"`
PrivateServerSlots int `json:"private_server_slots"`
}
// CarModel represents a car model mapping
type CarModel struct {
Value int `json:"value" gorm:"primaryKey"`
CarModel string `json:"car_model"`
}
// DriverCategory represents driver skill categories
type DriverCategory struct {
Value int `json:"value" gorm:"primaryKey"`
Category string `json:"category"`
}
// CupCategory represents championship cup categories
type CupCategory struct {
Value int `json:"value" gorm:"primaryKey"`
Category string `json:"category"`
}
// SessionType represents session types
type SessionType struct {
Value int `json:"value" gorm:"primaryKey"`
SessionType string `json:"session_type"`
}

11
local/model/server.go Normal file
View File

@@ -0,0 +1,11 @@
package model
// Server represents an ACC server instance
type Server struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
IP string `gorm:"not null"`
Port int `gorm:"not null"`
ConfigPath string `gorm:"not null"` // e.g. "/acc/servers/server1/"
ServiceName string `gorm:"not null"` // Windows service name
}

View File

@@ -3,6 +3,7 @@ package repository
import (
"acc-server-manager/local/model"
"context"
"errors"
"gorm.io/gorm"
)
@@ -27,6 +28,9 @@ func NewApiRepository(db *gorm.DB) *ApiRepository {
func (as ApiRepository) GetFirst(ctx context.Context) *model.ApiModel {
db := as.db.WithContext(ctx)
apiModel := new(model.ApiModel)
db.First(&apiModel)
result := db.First(&apiModel)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil
}
return apiModel
}

View File

@@ -0,0 +1,66 @@
package repository
import (
"acc-server-manager/local/model"
"context"
"errors"
"gorm.io/gorm"
)
type ConfigRepository struct {
db *gorm.DB
}
func NewConfigRepository(db *gorm.DB) *ConfigRepository {
return &ConfigRepository{
db: db,
}
}
// UpdateConfig
// Updates first row from Config table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ConfigModel: Config object from database.
func (as ConfigRepository) UpdateFirst(ctx context.Context) *model.Config {
db := as.db.WithContext(ctx)
ConfigModel := new(model.Config)
db.First(&ConfigModel)
return ConfigModel
}
// UpdateAll
// Updates All rows from Config table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ConfigModel: Config object from database.
func (as ConfigRepository) UpdateAll(ctx context.Context) *[]model.Config {
db := as.db.WithContext(ctx)
ConfigModel := new([]model.Config)
db.Find(&ConfigModel)
return ConfigModel
}
// UpdateConfig
// Updates Config row from Config table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ConfigModel: Config object from database.
func (as ConfigRepository) UpdateConfig(ctx context.Context, body *model.Config) *model.Config {
db := as.db.WithContext(ctx)
existingConfig := new(model.Config)
result := db.Where("server_id=?", body.ServerID).Where("config_file=?", body.ConfigFile).First(existingConfig)
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
body.ID = existingConfig.ID
}
db.Save(body)
return body
}

View File

@@ -0,0 +1,88 @@
package repository
import (
"acc-server-manager/local/model"
"context"
"gorm.io/gorm"
)
type LookupRepository struct {
db *gorm.DB
}
func NewLookupRepository(db *gorm.DB) *LookupRepository {
return &LookupRepository{
db: db,
}
}
// GetTracks
// Gets Tracks rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupRepository) GetTracks(ctx context.Context) *[]model.Track {
db := as.db.WithContext(ctx)
TrackModel := new([]model.Track)
db.Find(&TrackModel)
return TrackModel
}
// GetCarModels
// Gets CarModels rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupRepository) GetCarModels(ctx context.Context) *[]model.CarModel {
db := as.db.WithContext(ctx)
CarModelModel := new([]model.CarModel)
db.Find(&CarModelModel)
return CarModelModel
}
// GetDriverCategories
// Gets DriverCategories rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupRepository) GetDriverCategories(ctx context.Context) *[]model.DriverCategory {
db := as.db.WithContext(ctx)
DriverCategoryModel := new([]model.DriverCategory)
db.Find(&DriverCategoryModel)
return DriverCategoryModel
}
// GetCupCategories
// Gets CupCategories rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupRepository) GetCupCategories(ctx context.Context) *[]model.CupCategory {
db := as.db.WithContext(ctx)
CupCategoryModel := new([]model.CupCategory)
db.Find(&CupCategoryModel)
return CupCategoryModel
}
// GetSessionTypes
// Gets SessionTypes rows from Lookup table.
//
// Args:
// context.Context: Application context
// Returns:
// model.LookupModel: Lookup object from database.
func (as LookupRepository) GetSessionTypes(ctx context.Context) *[]model.SessionType {
db := as.db.WithContext(ctx)
SessionTypesModel := new([]model.SessionType)
db.Find(&SessionTypesModel)
return SessionTypesModel
}

View File

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

View File

@@ -0,0 +1,64 @@
package repository
import (
"acc-server-manager/local/model"
"context"
"errors"
"gorm.io/gorm"
)
type ServerRepository struct {
db *gorm.DB
}
func NewServerRepository(db *gorm.DB) *ServerRepository {
return &ServerRepository{
db: db,
}
}
// GetFirst
// Gets first row from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ServerModel: Server object from database.
func (as ServerRepository) GetFirst(ctx context.Context, serverId int) *model.Server {
db := as.db.WithContext(ctx)
ServerModel := new(model.Server)
db.Where("id=?", serverId).First(&ServerModel)
return ServerModel
}
// GetFirstByServiceName
// Gets first row from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ServerModel: Server object from database.
func (as ServerRepository) GetFirstByServiceName(ctx context.Context, serviceName string) *model.Server {
db := as.db.WithContext(ctx)
ServerModel := new(model.Server)
result := db.Where("service_name=?", serviceName).First(&ServerModel)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil
}
return ServerModel
}
// GetAll
// Gets All rows from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// model.ServerModel: Server object from database.
func (as ServerRepository) GetAll(ctx context.Context) *[]model.Server {
db := as.db.WithContext(ctx)
ServerModel := new([]model.Server)
db.Find(&ServerModel)
return ServerModel
}

View File

@@ -10,12 +10,15 @@ import (
)
type ApiService struct {
repository *repository.ApiRepository
repository *repository.ApiRepository
serverRepository *repository.ServerRepository
}
func NewApiService(repository *repository.ApiRepository) *ApiService {
func NewApiService(repository *repository.ApiRepository,
serverRepository *repository.ServerRepository) *ApiService {
return &ApiService{
repository: repository,
repository: repository,
serverRepository: serverRepository,
}
}
@@ -30,16 +33,17 @@ func (as ApiService) GetFirst(ctx *fiber.Ctx) *model.ApiModel {
return as.repository.GetFirst(ctx.UserContext())
}
func (as ApiService) GetStatus(ctx *fiber.Ctx) (string, error) {
service := ctx.Params("service")
return as.StatusServer(ctx, service)
}
func (as ApiService) ApiStartServer(ctx *fiber.Ctx) (string, error) {
service, ok := ctx.Locals("service").(string)
if !ok {
return "", errors.New("service name missing")
}
return as.StartServer(service)
}
func (as ApiService) StartServer(serviceName string) (string, error) {
return as.ManageService("start", serviceName)
return as.StartServer(ctx, service)
}
func (as ApiService) ApiStopServer(ctx *fiber.Ctx) (string, error) {
@@ -47,11 +51,7 @@ func (as ApiService) ApiStopServer(ctx *fiber.Ctx) (string, error) {
if !ok {
return "", errors.New("service name missing")
}
return as.StopServer(service)
}
func (as ApiService) StopServer(serviceName string) (string, error) {
return as.ManageService("stop", serviceName)
return as.StopServer(ctx, service)
}
func (as ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
@@ -59,18 +59,31 @@ func (as ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
if !ok {
return "", errors.New("service name missing")
}
return as.RestartServer(service)
return as.RestartServer(ctx, service)
}
func (as ApiService) RestartServer(serviceName string) (string, error) {
_, err := as.ManageService("stop", serviceName)
if err != nil {
return "", err
func (as ApiService) StatusServer(ctx *fiber.Ctx, serviceName string) (string, error) {
return as.ManageService(ctx, "status", serviceName)
}
func (as ApiService) StartServer(ctx *fiber.Ctx, serviceName string) (string, error) {
return as.ManageService(ctx, "start", serviceName)
}
func (as ApiService) StopServer(ctx *fiber.Ctx, serviceName string) (string, error) {
return as.ManageService(ctx, "stop", serviceName)
}
func (as ApiService) RestartServer(ctx *fiber.Ctx, serviceName string) (string, error) {
return as.ManageService(ctx, "restart", serviceName)
}
func (as ApiService) ManageService(ctx *fiber.Ctx, action string, serviceName string) (string, error) {
server := as.serverRepository.GetFirstByServiceName(ctx.UserContext(), serviceName)
if server == nil {
return "", fiber.NewError(404, "Server not found")
}
return as.ManageService("start", serviceName)
}
func (as ApiService) ManageService(action string, serviceName string) (string, error) {
output, err := common.RunElevatedCommand(action, serviceName)
if err != nil {
return "", err

238
local/service/config.go Normal file
View File

@@ -0,0 +1,238 @@
package service
import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"bytes"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"time"
"github.com/gofiber/fiber/v2"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
type ConfigService struct {
repository *repository.ConfigRepository
serverRepository *repository.ServerRepository
}
func NewConfigService(repository *repository.ConfigRepository, serverRepository *repository.ServerRepository) *ConfigService {
return &ConfigService{
repository: repository,
serverRepository: serverRepository,
}
}
// UpdateConfig
// Updates physical config file and caches it in database.
//
// Args:
// context.Context: Application context
// Returns:
// string: Application version
func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{}) (*model.Config, error) {
serverID, _ := ctx.ParamsInt("id")
configFile := ctx.Params("file")
server := as.serverRepository.GetFirst(ctx.UserContext(), serverID)
if server == nil {
return nil, fiber.NewError(404, "Server not found")
}
// Read existing config
configPath := filepath.Join(server.ConfigPath, "\\server\\cfg", configFile)
oldData, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
oldDataUTF8, err := DecodeUTF16LEBOM(oldData)
if err != nil {
return nil, err
}
// Write new config
newData, err := json.MarshalIndent(&body, "", " ")
if err != nil {
return nil, err
}
newDataUTF16, err := EncodeUTF16LEBOM(newData)
if err != nil {
return nil, err
}
if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil {
return nil, err
}
// Log change
return as.repository.UpdateConfig(ctx.UserContext(), &model.Config{
ServerID: uint(serverID),
ConfigFile: configFile,
OldConfig: string(oldDataUTF8),
NewConfig: string(newData),
ChangedAt: time.Now(),
}), nil
}
// GetConfig
// Gets physical config file and caches it in database.
//
// Args:
// context.Context: Application context
// Returns:
// string: Application version
func (as ConfigService) GetConfig(ctx *fiber.Ctx) (map[string]interface{}, error) {
serverID, _ := ctx.ParamsInt("id")
configFile := ctx.Params("file")
server := as.serverRepository.GetFirst(ctx.UserContext(), serverID)
if server == nil {
return nil, fiber.NewError(404, "Server not found")
}
config, err := readFile(server.ConfigPath, configFile)
if err != nil {
return nil, err
}
decoded, err := DecodeToMap(config)
if err != nil {
return nil, err
}
return decoded, nil
}
// GetConfigs
// Gets physical config file and caches it in database.
//
// Args:
// context.Context: Application context
// Returns:
// string: Application version
func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error) {
serverID, _ := ctx.ParamsInt("id")
server := as.serverRepository.GetFirst(ctx.UserContext(), serverID)
if server == nil {
return nil, fiber.NewError(404, "Server not found")
}
configuration, err := readFile(server.ConfigPath, "configuration.json")
if err != nil {
return nil, err
}
decodedconfiguration, err := DecodeToMap(configuration)
if err != nil {
return nil, err
}
entrylist, err := readFile(server.ConfigPath, "entrylist.json")
if err != nil {
return nil, err
}
decodedentrylist, err := DecodeToMap(entrylist)
if err != nil {
return nil, err
}
event, err := readFile(server.ConfigPath, "event.json")
if err != nil {
return nil, err
}
decodedevent, err := DecodeToMap(event)
if err != nil {
return nil, err
}
eventRules, err := readFile(server.ConfigPath, "eventRules.json")
if err != nil {
return nil, err
}
decodedeventRules, err := DecodeToMap(eventRules)
if err != nil {
return nil, err
}
settings, err := readFile(server.ConfigPath, "settings.json")
if err != nil {
return nil, err
}
decodedsettings, err := DecodeToMap(settings)
if err != nil {
return nil, err
}
return &model.Configurations{
Configuration: decodedconfiguration,
Event: decodedevent,
EventRules: decodedeventRules,
Settings: decodedsettings,
Entrylist: decodedentrylist,
}, nil
}
func readFile(path string, configFile string) ([]byte, error) {
configPath := filepath.Join(path, "\\server\\cfg", configFile)
oldData, err := os.ReadFile(configPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
} else if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return oldData, nil
}
func EncodeUTF16LEBOM(input []byte) ([]byte, error) {
encoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM)
return transformBytes(encoder.NewEncoder(), input)
}
func DecodeUTF16LEBOM(input []byte) ([]byte, error) {
decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM)
return transformBytes(decoder.NewDecoder(), input)
}
func DecodeToMap(input []byte) (map[string]interface{}, error) {
if input == nil {
return nil, nil
}
configUTF8 := new(map[string]interface{})
decoded, err := DecodeUTF16LEBOM(input)
if err != nil {
return nil, err
}
err = json.Unmarshal(decoded, configUTF8)
if err != nil {
return nil, err
}
return *configUTF8, nil
}
func transformBytes(t transform.Transformer, input []byte) ([]byte, error) {
var buf bytes.Buffer
w := transform.NewWriter(&buf, t)
if _, err := io.Copy(w, bytes.NewReader(input)); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

73
local/service/lookup.go Normal file
View File

@@ -0,0 +1,73 @@
package service
import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"github.com/gofiber/fiber/v2"
)
type LookupService struct {
repository *repository.LookupRepository
}
func NewLookupService(repository *repository.LookupRepository) *LookupService {
return &LookupService{
repository: repository,
}
}
// 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())
}
// 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())
}
// 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())
}
// 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())
}
// 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())
}

29
local/service/server.go Normal file
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 ServerService struct {
repository *repository.ServerRepository
}
func NewServerService(repository *repository.ServerRepository) *ServerService {
return &ServerService{
repository: repository,
}
}
// GetAll
// Gets All rows from Server table.
//
// Args:
// context.Context: Application context
// Returns:
// string: Application version
func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
return as.repository.GetAll(ctx.UserContext())
}

View File

@@ -15,4 +15,7 @@ func InitializeServices(c *dig.Container) {
repository.InitializeRepositories(c)
c.Provide(NewApiService)
c.Provide(NewConfigService)
c.Provide(NewServerService)
c.Provide(NewLookupService)
}

View File

@@ -13,7 +13,10 @@ import (
)
type RouteGroups struct {
Api fiber.Router
Api fiber.Router
Server fiber.Router
Config fiber.Router
Lookup fiber.Router
}
func CheckError(err error) {
@@ -58,10 +61,10 @@ func Find[T any](lst *[]T, callback func(item *T) bool) *T {
}
func RunElevatedCommand(command string, service string) (string, error) {
cmd := exec.Command("powershell", "-nologo", "-noprofile", "-File", "run_sc.ps1", command, service)
cmd := exec.Command("powershell", "-nologo", "-noprofile", ".\\nssm", command, service)
// cmd := exec.Command("powershell", "-nologo", "-noprofile", "-File", "run_sc.ps1", command, service)
output, err := cmd.CombinedOutput()
if err != nil {
log.Panic("error: %v, output: %s", err, string(output))
return "", fmt.Errorf("error: %v, output: %s", err, string(output))
}
return string(output), nil

View File

@@ -27,5 +27,173 @@ func Migrate(db *gorm.DB) {
if err != nil {
panic("failed to migrate model.ApiModel")
}
err = db.AutoMigrate(&model.Server{})
if err != nil {
panic("failed to migrate model.Server")
}
err = db.AutoMigrate(&model.Config{})
if err != nil {
panic("failed to migrate model.Config")
}
err = db.AutoMigrate(&model.Track{})
if err != nil {
panic("failed to migrate model.Track")
}
err = db.AutoMigrate(&model.CarModel{})
if err != nil {
panic("failed to migrate model.CarModel")
}
err = db.AutoMigrate(&model.CupCategory{})
if err != nil {
panic("failed to migrate model.CupCategory")
}
err = db.AutoMigrate(&model.DriverCategory{})
if err != nil {
panic("failed to migrate model.DriverCategory")
}
err = db.AutoMigrate(&model.SessionType{})
if err != nil {
panic("failed to migrate model.SessionType")
}
db.FirstOrCreate(&model.ApiModel{Api: "Works"})
Seed(db)
}
func Seed(db *gorm.DB) error {
if err := seedTracks(db); err != nil {
return err
}
if err := seedCarModels(db); err != nil {
return err
}
if err := seedDriverCategories(db); err != nil {
return err
}
if err := seedCupCategories(db); err != nil {
return err
}
if err := seedSessionTypes(db); err != nil {
return err
}
if err := seedServers(db); err != nil {
return err
}
return nil
}
func seedServers(db *gorm.DB) error {
servers := []model.Server{
{ID: 1, Name: "ACC Server - Barcelona", ServiceName: "ACC-Barcelona", ConfigPath: "C:\\steamcmd\\acc"},
{ID: 2, Name: "ACC Server - Monza", ServiceName: "ACC-Monza", ConfigPath: "C:\\steamcmd\\acc2"},
{ID: 3, Name: "ACC Server - Spa", ServiceName: "ACC-Spa", ConfigPath: "C:\\steamcmd\\acc3"},
{ID: 4, Name: "ACC Server - League", ServiceName: "ACC-League", ConfigPath: "C:\\steamcmd\\acc-league"},
}
for _, track := range servers {
if err := db.FirstOrCreate(&track).Error; err != nil {
return err
}
}
return nil
}
func seedTracks(db *gorm.DB) error {
tracks := []model.Track{
{Name: "monza", UniquePitBoxes: 29, PrivateServerSlots: 60},
{Name: "zolder", UniquePitBoxes: 34, PrivateServerSlots: 50},
{Name: "brands_hatch", UniquePitBoxes: 32, PrivateServerSlots: 50},
{Name: "silverstone", UniquePitBoxes: 36, PrivateServerSlots: 60},
{Name: "paul_ricard", UniquePitBoxes: 33, PrivateServerSlots: 80},
{Name: "misano", UniquePitBoxes: 30, PrivateServerSlots: 50},
{Name: "spa", UniquePitBoxes: 82, PrivateServerSlots: 82},
{Name: "nurburgring", UniquePitBoxes: 30, PrivateServerSlots: 50},
{Name: "barcelona", UniquePitBoxes: 29, PrivateServerSlots: 50},
{Name: "hungaroring", UniquePitBoxes: 27, PrivateServerSlots: 50},
{Name: "zandvoort", UniquePitBoxes: 25, PrivateServerSlots: 50},
{Name: "kyalami", UniquePitBoxes: 40, PrivateServerSlots: 50},
{Name: "mount_panorama", UniquePitBoxes: 36, PrivateServerSlots: 50},
{Name: "suzuka", UniquePitBoxes: 51, PrivateServerSlots: 105},
{Name: "laguna_seca", UniquePitBoxes: 30, PrivateServerSlots: 50},
{Name: "imola", UniquePitBoxes: 30, PrivateServerSlots: 50},
{Name: "oulton_park", UniquePitBoxes: 28, PrivateServerSlots: 50},
{Name: "donington", UniquePitBoxes: 37, PrivateServerSlots: 50},
{Name: "snetterton", UniquePitBoxes: 26, PrivateServerSlots: 50},
{Name: "cota", UniquePitBoxes: 30, PrivateServerSlots: 70},
{Name: "indianapolis", UniquePitBoxes: 30, PrivateServerSlots: 60},
{Name: "watkins_glen", UniquePitBoxes: 30, PrivateServerSlots: 60},
{Name: "valencia", UniquePitBoxes: 29, PrivateServerSlots: 50},
{Name: "nurburgring_24h", UniquePitBoxes: 50, PrivateServerSlots: 110},
}
for _, track := range tracks {
if err := db.FirstOrCreate(&track).Error; err != nil {
return err
}
}
return nil
}
func seedCarModels(db *gorm.DB) error {
carModels := []model.CarModel{
{Value: 0, CarModel: "Porsche 991 GT3 R"},
{Value: 1, CarModel: "Mercedes-AMG GT3"},
// ... Add all car models from your list
}
for _, cm := range carModels {
if err := db.FirstOrCreate(&cm).Error; err != nil {
return err
}
}
return nil
}
func seedDriverCategories(db *gorm.DB) error {
categories := []model.DriverCategory{
{Value: 3, Category: "Platinum"},
{Value: 2, Category: "Gold"},
{Value: 1, Category: "Silver"},
{Value: 0, Category: "Bronze"},
}
for _, cat := range categories {
if err := db.FirstOrCreate(&cat).Error; err != nil {
return err
}
}
return nil
}
func seedCupCategories(db *gorm.DB) error {
categories := []model.CupCategory{
{Value: 0, Category: "Overall"},
{Value: 1, Category: "ProAm"},
{Value: 2, Category: "Am"},
{Value: 3, Category: "Silver"},
{Value: 4, Category: "National"},
}
for _, cat := range categories {
if err := db.FirstOrCreate(&cat).Error; err != nil {
return err
}
}
return nil
}
func seedSessionTypes(db *gorm.DB) error {
sessionTypes := []model.SessionType{
{Value: 0, SessionType: "Practice"},
{Value: 4, SessionType: "Qualifying"},
{Value: 10, SessionType: "Race"},
}
for _, st := range sessionTypes {
if err := db.FirstOrCreate(&st).Error; err != nil {
return err
}
}
return nil
}