add filtering and base repository
This commit is contained in:
@@ -43,8 +43,7 @@ func NewApiController(as *service.ApiService, routeGroups *common.RouteGroups) *
|
|||||||
// @Success 200 {array} string
|
// @Success 200 {array} string
|
||||||
// @Router /v1/api [get]
|
// @Router /v1/api [get]
|
||||||
func (ac *ApiController) getFirst(c *fiber.Ctx) error {
|
func (ac *ApiController) getFirst(c *fiber.Ctx) error {
|
||||||
apiModel := ac.service.GetFirst(c)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
return c.SendString(apiModel.Api)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStatus returns service status
|
// getStatus returns service status
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
|
||||||
"acc-server-manager/local/service"
|
"acc-server-manager/local/service"
|
||||||
"acc-server-manager/local/utl/common"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"go.uber.org/dig"
|
"go.uber.org/dig"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,34 +40,3 @@ func InitializeControllers(c *dig.Container) {
|
|||||||
log.Panic("unable to initialize stateHistory controller")
|
log.Panic("unable to initialize stateHistory controller")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilteredResponse
|
|
||||||
// Gets query parameters and populates FilteredResponse model.
|
|
||||||
//
|
|
||||||
// Args:
|
|
||||||
// *gin.Context: Gin Application Context
|
|
||||||
// Returns:
|
|
||||||
// *model.FilteredResponse: Filtered response
|
|
||||||
func FilteredResponse(c *fiber.Ctx) *model.FilteredResponse {
|
|
||||||
filtered := new(model.FilteredResponse)
|
|
||||||
page := c.Params("page")
|
|
||||||
rpp := c.Params("rpp")
|
|
||||||
sortBy := c.Params("sortBy")
|
|
||||||
|
|
||||||
dividers := [5]string{"|", " ", ".", "/", ","}
|
|
||||||
|
|
||||||
for _, div := range dividers {
|
|
||||||
sortArr := strings.Split(sortBy, div)
|
|
||||||
|
|
||||||
if len(sortArr) >= 2 {
|
|
||||||
sortBy = fmt.Sprintf("%s %s", common.ToSnakeCase(sortArr[0]), strings.ToUpper(sortArr[1]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered.Embed = c.Params("embed")
|
|
||||||
filtered.Page, _ = strconv.Atoi(page)
|
|
||||||
filtered.Rpp, _ = strconv.Atoi(rpp)
|
|
||||||
filtered.SortBy = sortBy
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/service"
|
"acc-server-manager/local/service"
|
||||||
"acc-server-manager/local/utl/common"
|
"acc-server-manager/local/utl/common"
|
||||||
|
|
||||||
@@ -38,7 +39,16 @@ func NewServerController(as *service.ServerService, routeGroups *common.RouteGro
|
|||||||
// @Success 200 {array} string
|
// @Success 200 {array} string
|
||||||
// @Router /v1/server [get]
|
// @Router /v1/server [get]
|
||||||
func (ac *ServerController) getAll(c *fiber.Ctx) error {
|
func (ac *ServerController) getAll(c *fiber.Ctx) error {
|
||||||
ServerModel := ac.service.GetAll(c)
|
var filter model.ServerFilter
|
||||||
|
if err := common.ParseQueryFilter(c, &filter); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ServerModel, err := ac.service.GetAll(c, &filter)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
return c.JSON(ServerModel)
|
return c.JSON(ServerModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +61,9 @@ func (ac *ServerController) getAll(c *fiber.Ctx) error {
|
|||||||
// @Router /v1/server [get]
|
// @Router /v1/server [get]
|
||||||
func (ac *ServerController) getById(c *fiber.Ctx) error {
|
func (ac *ServerController) getById(c *fiber.Ctx) error {
|
||||||
serverID, _ := c.ParamsInt("id")
|
serverID, _ := c.ParamsInt("id")
|
||||||
ServerModel := ac.service.GetById(c, serverID)
|
ServerModel, err := ac.service.GetById(c, serverID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
return c.JSON(ServerModel)
|
return c.JSON(ServerModel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/service"
|
"acc-server-manager/local/service"
|
||||||
"acc-server-manager/local/utl/common"
|
"acc-server-manager/local/utl/common"
|
||||||
|
|
||||||
@@ -37,7 +38,19 @@ func NewStateHistoryController(as *service.StateHistoryService, routeGroups *com
|
|||||||
// @Success 200 {array} string
|
// @Success 200 {array} string
|
||||||
// @Router /v1/StateHistory [get]
|
// @Router /v1/StateHistory [get]
|
||||||
func (ac *StateHistoryController) getAll(c *fiber.Ctx) error {
|
func (ac *StateHistoryController) getAll(c *fiber.Ctx) error {
|
||||||
StateHistoryID, _ := c.ParamsInt("id")
|
var filter model.StateHistoryFilter
|
||||||
StateHistoryModel := ac.service.GetAll(c, StateHistoryID)
|
if err := common.ParseQueryFilter(c, &filter); err != nil {
|
||||||
return c.JSON(StateHistoryModel)
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ac.service.GetAll(c, &filter)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error retrieving state history",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(result)
|
||||||
}
|
}
|
||||||
74
local/model/filter.go
Normal file
74
local/model/filter.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// BaseFilter contains common filter fields that can be embedded in other filters
|
||||||
|
type BaseFilter struct {
|
||||||
|
Page int `query:"page"`
|
||||||
|
PageSize int `query:"page_size"`
|
||||||
|
SortBy string `query:"sort_by"`
|
||||||
|
SortDesc bool `query:"sort_desc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateRangeFilter adds date range filtering capabilities
|
||||||
|
type DateRangeFilter struct {
|
||||||
|
StartDate time.Time `query:"start_date" time_format:"2006-01-02T15:04:05Z07:00"`
|
||||||
|
EndDate time.Time `query:"end_date" time_format:"2006-01-02T15:04:05Z07:00"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerBasedFilter adds server ID filtering capability
|
||||||
|
type ServerBasedFilter struct {
|
||||||
|
ServerID int `param:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerFilter defines filtering options for Server queries
|
||||||
|
type ServerFilter struct {
|
||||||
|
BaseFilter
|
||||||
|
ServerBasedFilter
|
||||||
|
Name string `query:"name"`
|
||||||
|
ServiceName string `query:"service_name"`
|
||||||
|
Status string `query:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigFilter defines filtering options for Config queries
|
||||||
|
type ConfigFilter struct {
|
||||||
|
BaseFilter
|
||||||
|
ServerBasedFilter
|
||||||
|
ConfigFile string `query:"config_file"`
|
||||||
|
ChangedAt time.Time `query:"changed_at" time_format:"2006-01-02T15:04:05Z07:00"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApiFilter defines filtering options for Api queries
|
||||||
|
type ApiFilter struct {
|
||||||
|
BaseFilter
|
||||||
|
Api string `query:"api"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination returns the offset and limit for database queries
|
||||||
|
func (f *BaseFilter) Pagination() (offset, limit int) {
|
||||||
|
if f.Page < 1 {
|
||||||
|
f.Page = 1
|
||||||
|
}
|
||||||
|
if f.PageSize < 1 {
|
||||||
|
f.PageSize = 10 // Default page size
|
||||||
|
}
|
||||||
|
offset = (f.Page - 1) * f.PageSize
|
||||||
|
limit = f.PageSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSorting returns the sort field and direction for database queries
|
||||||
|
func (f *BaseFilter) GetSorting() (field string, desc bool) {
|
||||||
|
if f.SortBy == "" {
|
||||||
|
return "id", false // Default sorting
|
||||||
|
}
|
||||||
|
return f.SortBy, f.SortDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDateRangeValid checks if both dates are set and start date is before end date
|
||||||
|
func (f *DateRangeFilter) IsDateRangeValid() bool {
|
||||||
|
if f.StartDate.IsZero() || f.EndDate.IsZero() {
|
||||||
|
return true // If either date is not set, consider it valid
|
||||||
|
}
|
||||||
|
return f.StartDate.Before(f.EndDate)
|
||||||
|
}
|
||||||
@@ -49,11 +49,3 @@ type ServerState struct {
|
|||||||
// 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"`
|
|
||||||
}
|
|
||||||
59
local/model/stateHistory.go
Normal file
59
local/model/stateHistory.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateHistoryFilter combines common filter capabilities
|
||||||
|
type StateHistoryFilter struct {
|
||||||
|
BaseFilter // Adds pagination and sorting
|
||||||
|
ServerBasedFilter // Adds server ID from path parameter
|
||||||
|
DateRangeFilter // Adds date range filtering
|
||||||
|
|
||||||
|
// Additional fields specific to state history
|
||||||
|
Session string `query:"session"`
|
||||||
|
MinPlayers *int `query:"min_players"`
|
||||||
|
MaxPlayers *int `query:"max_players"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyFilter implements the Filterable interface
|
||||||
|
func (f *StateHistoryFilter) ApplyFilter(query *gorm.DB) *gorm.DB {
|
||||||
|
// Apply server filter
|
||||||
|
if f.ServerID != 0 {
|
||||||
|
query = query.Where("server_id = ?", f.ServerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply date range filter if set
|
||||||
|
timeZero := time.Time{}
|
||||||
|
if f.StartDate != timeZero {
|
||||||
|
query = query.Where("date_created >= ?", f.StartDate)
|
||||||
|
}
|
||||||
|
if f.EndDate != timeZero {
|
||||||
|
query = query.Where("date_created <= ?", f.EndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply session filter if set
|
||||||
|
if f.Session != "" {
|
||||||
|
query = query.Where("session = ?", f.Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply player count filters if set
|
||||||
|
if f.MinPlayers != nil {
|
||||||
|
query = query.Where("player_count >= ?", *f.MinPlayers)
|
||||||
|
}
|
||||||
|
if f.MaxPlayers != nil {
|
||||||
|
query = query.Where("player_count <= ?", *f.MaxPlayers)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
@@ -2,35 +2,16 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApiRepository struct {
|
type ApiRepository struct {
|
||||||
db *gorm.DB
|
*BaseRepository[model.ApiModel, model.ApiFilter]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApiRepository(db *gorm.DB) *ApiRepository {
|
func NewApiRepository(db *gorm.DB) *ApiRepository {
|
||||||
return &ApiRepository{
|
return &ApiRepository{
|
||||||
db: db,
|
BaseRepository: NewBaseRepository[model.ApiModel, model.ApiFilter](db, model.ApiModel{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirst
|
|
||||||
// Gets first row from API table.
|
|
||||||
//
|
|
||||||
// Args:
|
|
||||||
// context.Context: Application context
|
|
||||||
// Returns:
|
|
||||||
// model.ApiModel: Api object from database.
|
|
||||||
func (as ApiRepository) GetFirst(ctx context.Context) *model.ApiModel {
|
|
||||||
db := as.db.WithContext(ctx)
|
|
||||||
apiModel := new(model.ApiModel)
|
|
||||||
result := db.First(&apiModel)
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return apiModel
|
|
||||||
}
|
|
||||||
|
|||||||
121
local/repository/base.go
Normal file
121
local/repository/base.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BaseRepository provides generic CRUD operations for any model
|
||||||
|
type BaseRepository[T any, F any] struct {
|
||||||
|
db *gorm.DB
|
||||||
|
modelType T
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBaseRepository creates a new base repository for the given model type
|
||||||
|
func NewBaseRepository[T any, F any](db *gorm.DB, model T) *BaseRepository[T, F] {
|
||||||
|
return &BaseRepository[T, F]{
|
||||||
|
db: db,
|
||||||
|
modelType: model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll retrieves all records based on the filter
|
||||||
|
func (r *BaseRepository[T, F]) GetAll(ctx context.Context, filter *F) (*[]T, error) {
|
||||||
|
result := new([]T)
|
||||||
|
query := r.db.WithContext(ctx).Model(&r.modelType)
|
||||||
|
|
||||||
|
// Apply filter conditions if filter implements Filterable
|
||||||
|
if filterable, ok := any(filter).(Filterable); ok {
|
||||||
|
query = filterable.ApplyFilter(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination if filter implements Pageable
|
||||||
|
if pageable, ok := any(filter).(Pageable); ok {
|
||||||
|
offset, limit := pageable.Pagination()
|
||||||
|
query = query.Offset(offset).Limit(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sorting if filter implements Sortable
|
||||||
|
if sortable, ok := any(filter).(Sortable); ok {
|
||||||
|
field, desc := sortable.GetSorting()
|
||||||
|
if desc {
|
||||||
|
query = query.Order(field + " DESC")
|
||||||
|
} else {
|
||||||
|
query = query.Order(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Find(result).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID retrieves a single record by ID
|
||||||
|
func (r *BaseRepository[T, F]) GetByID(ctx context.Context, id interface{}) (*T, error) {
|
||||||
|
result := new(T)
|
||||||
|
if err := r.db.WithContext(ctx).First(result, id).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("error getting record by ID: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert creates a new record
|
||||||
|
func (r *BaseRepository[T, F]) Insert(ctx context.Context, model *T) error {
|
||||||
|
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
|
||||||
|
return fmt.Errorf("error creating record: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update modifies an existing record
|
||||||
|
func (r *BaseRepository[T, F]) Update(ctx context.Context, model *T) error {
|
||||||
|
if err := r.db.WithContext(ctx).Save(model).Error; err != nil {
|
||||||
|
return fmt.Errorf("error updating record: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a record by ID
|
||||||
|
func (r *BaseRepository[T, F]) Delete(ctx context.Context, id interface{}) error {
|
||||||
|
if err := r.db.WithContext(ctx).Delete(new(T), id).Error; err != nil {
|
||||||
|
return fmt.Errorf("error deleting record: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the total number of records matching the filter
|
||||||
|
func (r *BaseRepository[T, F]) Count(ctx context.Context, filter *F) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
query := r.db.WithContext(ctx).Model(&r.modelType)
|
||||||
|
|
||||||
|
if filterable, ok := any(filter).(Filterable); ok {
|
||||||
|
query = filterable.ApplyFilter(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Count(&count).Error; err != nil {
|
||||||
|
return 0, fmt.Errorf("error counting records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interfaces for filter capabilities
|
||||||
|
|
||||||
|
type Filterable interface {
|
||||||
|
ApplyFilter(*gorm.DB) *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pageable interface {
|
||||||
|
Pagination() (offset, limit int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sortable interface {
|
||||||
|
GetSorting() (field string, desc bool)
|
||||||
|
}
|
||||||
@@ -3,64 +3,27 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigRepository struct {
|
type ConfigRepository struct {
|
||||||
db *gorm.DB
|
*BaseRepository[model.Config, model.ConfigFilter]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigRepository(db *gorm.DB) *ConfigRepository {
|
func NewConfigRepository(db *gorm.DB) *ConfigRepository {
|
||||||
return &ConfigRepository{
|
return &ConfigRepository{
|
||||||
db: db,
|
BaseRepository: NewBaseRepository[model.Config, model.ConfigFilter](db, model.Config{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateConfig
|
// UpdateConfig updates or creates a Config record
|
||||||
// Updates first row from Config table.
|
func (r *ConfigRepository) UpdateConfig(ctx context.Context, config *model.Config) *model.Config {
|
||||||
//
|
if err := r.Update(ctx, config); err != nil {
|
||||||
// Args:
|
// If update fails, try to insert
|
||||||
// context.Context: Application context
|
if err := r.Insert(ctx, config); err != nil {
|
||||||
// Returns:
|
return nil
|
||||||
// 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
|
return config
|
||||||
}
|
}
|
||||||
@@ -9,28 +9,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ServerRepository struct {
|
type ServerRepository struct {
|
||||||
db *gorm.DB
|
*BaseRepository[model.Server, model.ServerFilter]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerRepository(db *gorm.DB) *ServerRepository {
|
func NewServerRepository(db *gorm.DB) *ServerRepository {
|
||||||
return &ServerRepository{
|
return &ServerRepository{
|
||||||
db: db,
|
BaseRepository: NewBaseRepository[model.Server, model.ServerFilter](db, model.Server{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// GetFirstByServiceName
|
||||||
// Gets first row from Server table.
|
// Gets first row from Server table.
|
||||||
@@ -39,45 +26,13 @@ func (as ServerRepository) GetFirst(ctx context.Context, serverId int) *model.Se
|
|||||||
// context.Context: Application context
|
// context.Context: Application context
|
||||||
// Returns:
|
// Returns:
|
||||||
// model.ServerModel: Server object from database.
|
// model.ServerModel: Server object from database.
|
||||||
func (as ServerRepository) GetFirstByServiceName(ctx context.Context, serviceName string) *model.Server {
|
func (r *ServerRepository) GetFirstByServiceName(ctx context.Context, serviceName string) (*model.Server, error) {
|
||||||
db := as.db.WithContext(ctx)
|
result := new(model.Server)
|
||||||
ServerModel := new(model.Server)
|
if err := r.db.WithContext(ctx).Where("service_name = ?", serviceName).First(result).Error; err != nil {
|
||||||
result := db.Where("service_name=?", serviceName).First(&ServerModel)
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return ServerModel
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateServer
|
|
||||||
// Updates Server row from Server table.
|
|
||||||
//
|
|
||||||
// Args:
|
|
||||||
// context.Context: Application context
|
|
||||||
// Returns:
|
|
||||||
// model.Server: Server object from database.
|
|
||||||
func (as ServerRepository) UpdateServer(ctx context.Context, body *model.Server) *model.Server {
|
|
||||||
db := as.db.WithContext(ctx)
|
|
||||||
|
|
||||||
existingServer := new(model.Server)
|
|
||||||
result := db.Where("id=?", body.ID).First(existingServer)
|
|
||||||
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
body.ID = existingServer.ID
|
|
||||||
}
|
}
|
||||||
db.Save(body)
|
return result, nil
|
||||||
return body
|
|
||||||
}
|
}
|
||||||
@@ -8,38 +8,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StateHistoryRepository struct {
|
type StateHistoryRepository struct {
|
||||||
db *gorm.DB
|
*BaseRepository[model.StateHistory, model.StateHistoryFilter]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStateHistoryRepository(db *gorm.DB) *StateHistoryRepository {
|
func NewStateHistoryRepository(db *gorm.DB) *StateHistoryRepository {
|
||||||
return &StateHistoryRepository{
|
return &StateHistoryRepository{
|
||||||
db: db,
|
BaseRepository: NewBaseRepository[model.StateHistory, model.StateHistoryFilter](db, model.StateHistory{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll
|
// GetAll retrieves all state history records with the given filter
|
||||||
// Gets All rows from Server table.
|
func (r *StateHistoryRepository) GetAll(ctx context.Context, filter *model.StateHistoryFilter) (*[]model.StateHistory, error) {
|
||||||
//
|
return r.BaseRepository.GetAll(ctx, filter)
|
||||||
// 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
|
// Insert creates a new state history record
|
||||||
// Updates Server row from Server table.
|
func (r *StateHistoryRepository) Insert(ctx context.Context, model *model.StateHistory) error {
|
||||||
//
|
return r.BaseRepository.Insert(ctx, model)
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,17 +30,6 @@ func (as *ApiService) SetServerService(serverService *ServerService) {
|
|||||||
as.serverService = serverService
|
as.serverService = serverService
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirst
|
|
||||||
// Gets first row from API table.
|
|
||||||
//
|
|
||||||
// Args:
|
|
||||||
// context.Context: Application context
|
|
||||||
// Returns:
|
|
||||||
// string: Application version
|
|
||||||
func (as ApiService) GetFirst(ctx *fiber.Ctx) *model.ApiModel {
|
|
||||||
return as.repository.GetFirst(ctx.UserContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as ApiService) GetStatus(ctx *fiber.Ctx) (string, error) {
|
func (as ApiService) GetStatus(ctx *fiber.Ctx) (string, error) {
|
||||||
serviceName, err := as.GetServiceName(ctx)
|
serviceName, err := as.GetServiceName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,7 +71,7 @@ func (as ApiService) StatusServer(serviceName string) (string, error) {
|
|||||||
func (as ApiService) StartServer(serviceName string) (string, error) {
|
func (as ApiService) StartServer(serviceName string) (string, error) {
|
||||||
status, err := ManageService(serviceName, "start")
|
status, err := ManageService(serviceName, "start")
|
||||||
|
|
||||||
server := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
as.serverService.StartAccServerRuntime(server)
|
as.serverService.StartAccServerRuntime(server)
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
@@ -90,7 +79,7 @@ func (as ApiService) StartServer(serviceName string) (string, error) {
|
|||||||
func (as ApiService) StopServer(serviceName string) (string, error) {
|
func (as ApiService) StopServer(serviceName string) (string, error) {
|
||||||
status, err := ManageService(serviceName, "stop")
|
status, err := ManageService(serviceName, "stop")
|
||||||
|
|
||||||
server := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
as.serverService.instances.Delete(server.ID)
|
as.serverService.instances.Delete(server.ID)
|
||||||
|
|
||||||
return status, err
|
return status, err
|
||||||
@@ -99,7 +88,7 @@ func (as ApiService) StopServer(serviceName string) (string, error) {
|
|||||||
func (as ApiService) RestartServer(serviceName string) (string, error) {
|
func (as ApiService) RestartServer(serviceName string) (string, error) {
|
||||||
status, err := ManageService(serviceName, "restart")
|
status, err := ManageService(serviceName, "restart")
|
||||||
|
|
||||||
server := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
server, err := as.serverRepository.GetFirstByServiceName(context.Background(), serviceName)
|
||||||
as.serverService.StartAccServerRuntime(server)
|
as.serverService.StartAccServerRuntime(server)
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
@@ -115,18 +104,19 @@ func ManageService(serviceName string, action string) (string, error) {
|
|||||||
|
|
||||||
func (as ApiService) GetServiceName(ctx *fiber.Ctx) (string, error) {
|
func (as ApiService) GetServiceName(ctx *fiber.Ctx) (string, error) {
|
||||||
var server *model.Server
|
var server *model.Server
|
||||||
|
var err error
|
||||||
serviceName, ok := ctx.Locals("service").(string)
|
serviceName, ok := ctx.Locals("service").(string)
|
||||||
if !ok || serviceName == "" {
|
if !ok || serviceName == "" {
|
||||||
serverId, ok2 := ctx.Locals("serverId").(int)
|
serverId, ok2 := ctx.Locals("serverId").(int)
|
||||||
if !ok2 || serverId == 0 {
|
if !ok2 || serverId == 0 {
|
||||||
return "", errors.New("service name missing")
|
return "", errors.New("service name missing")
|
||||||
}
|
}
|
||||||
server = as.serverRepository.GetFirst(ctx.UserContext(), serverId)
|
server, err = as.serverRepository.GetByID(ctx.UserContext(), serverId)
|
||||||
} else {
|
} else {
|
||||||
server = as.serverRepository.GetFirstByServiceName(ctx.UserContext(), serviceName)
|
server, err = as.serverRepository.GetFirstByServiceName(ctx.UserContext(), serviceName)
|
||||||
}
|
}
|
||||||
if server == nil {
|
if err != nil {
|
||||||
return "", fiber.NewError(404, "Server not found")
|
return "", err
|
||||||
}
|
}
|
||||||
return server.ServiceName, nil
|
return server.ServiceName, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{
|
|||||||
configFile := ctx.Params("file")
|
configFile := ctx.Params("file")
|
||||||
override := ctx.QueryBool("override")
|
override := ctx.QueryBool("override")
|
||||||
|
|
||||||
server := as.serverRepository.GetFirst(ctx.UserContext(), serverID)
|
server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID)
|
||||||
|
|
||||||
if server == nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(404, "Server not found")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read existing config
|
// Read existing config
|
||||||
@@ -159,9 +159,9 @@ func (as ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
|
|||||||
serverID, _ := ctx.ParamsInt("id")
|
serverID, _ := ctx.ParamsInt("id")
|
||||||
configFile := ctx.Params("file")
|
configFile := ctx.Params("file")
|
||||||
|
|
||||||
server := as.serverRepository.GetFirst(ctx.UserContext(), serverID)
|
server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID)
|
||||||
|
|
||||||
if server == nil {
|
if err != nil {
|
||||||
log.Print("Server not found")
|
log.Print("Server not found")
|
||||||
return nil, fiber.NewError(404, "Server not found")
|
return nil, fiber.NewError(404, "Server not found")
|
||||||
}
|
}
|
||||||
@@ -184,9 +184,9 @@ func (as ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
|
|||||||
func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error) {
|
func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error) {
|
||||||
serverID, _ := ctx.ParamsInt("id")
|
serverID, _ := ctx.ParamsInt("id")
|
||||||
|
|
||||||
server := as.serverRepository.GetFirst(ctx.UserContext(), serverID)
|
server, err := as.serverRepository.GetByID(ctx.UserContext(), serverID)
|
||||||
|
|
||||||
if server == nil {
|
if err != nil {
|
||||||
log.Print("Server not found")
|
log.Print("Server not found")
|
||||||
return nil, fiber.NewError(404, "Server not found")
|
return nil, fiber.NewError(404, "Server not found")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ type ServerService struct {
|
|||||||
apiService *ApiService
|
apiService *ApiService
|
||||||
instances sync.Map
|
instances sync.Map
|
||||||
configService *ConfigService
|
configService *ConfigService
|
||||||
|
lastInsertTimes sync.Map // Track last insert time per server
|
||||||
|
debouncers sync.Map // Track debounce timers per server
|
||||||
|
}
|
||||||
|
|
||||||
|
type pendingState struct {
|
||||||
|
timer *time.Timer
|
||||||
|
state *model.ServerState
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerService(repository *repository.ServerRepository, stateHistoryRepo *repository.StateHistoryRepository, apiService *ApiService, configService *ConfigService) *ServerService {
|
func NewServerService(repository *repository.ServerRepository, stateHistoryRepo *repository.StateHistoryRepository, apiService *ApiService, configService *ConfigService) *ServerService {
|
||||||
@@ -28,7 +35,10 @@ func NewServerService(repository *repository.ServerRepository, stateHistoryRepo
|
|||||||
configService: configService,
|
configService: configService,
|
||||||
stateHistoryRepo: stateHistoryRepo,
|
stateHistoryRepo: stateHistoryRepo,
|
||||||
}
|
}
|
||||||
servers := repository.GetAll(context.Background())
|
servers, err := repository.GetAll(context.Background(), &model.ServerFilter{})
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err.Error())
|
||||||
|
}
|
||||||
for _, server := range *servers {
|
for _, server := range *servers {
|
||||||
status, err := service.apiService.StatusServer(server.ServiceName)
|
status, err := service.apiService.StatusServer(server.ServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,15 +51,69 @@ func NewServerService(repository *repository.ServerRepository, stateHistoryRepo
|
|||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) StartAccServerRuntime(server *model.Server) {
|
func (s *ServerService) shouldInsertStateHistory(serverID uint) bool {
|
||||||
s.instances.Delete(server.ID)
|
insertInterval := 5 * time.Minute // Configure this as needed
|
||||||
instance := tracking.NewAccServerInstance(server, func(state *model.ServerState, states ...tracking.StateChange) {
|
|
||||||
|
lastInsertInterface, exists := s.lastInsertTimes.Load(serverID)
|
||||||
|
if !exists {
|
||||||
|
s.lastInsertTimes.Store(serverID, time.Now().UTC())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInsert := lastInsertInterface.(time.Time)
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
if now.Sub(lastInsert) >= insertInterval {
|
||||||
|
s.lastInsertTimes.Store(serverID, now)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) insertStateHistory(serverID uint, state *model.ServerState) {
|
||||||
s.stateHistoryRepo.Insert(context.Background(), &model.StateHistory{
|
s.stateHistoryRepo.Insert(context.Background(), &model.StateHistory{
|
||||||
ServerID: server.ID,
|
ServerID: serverID,
|
||||||
Session: state.Session,
|
Session: state.Session,
|
||||||
PlayerCount: state.PlayerCount,
|
PlayerCount: state.PlayerCount,
|
||||||
DateCreated: time.Now().UTC(),
|
DateCreated: time.Now().UTC(),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) handleStateChange(server *model.Server, state *model.ServerState) {
|
||||||
|
// Cancel existing timer if any
|
||||||
|
if debouncer, exists := s.debouncers.Load(server.ID); exists {
|
||||||
|
pending := debouncer.(*pendingState)
|
||||||
|
pending.timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new timer
|
||||||
|
timer := time.NewTimer(5 * time.Minute)
|
||||||
|
s.debouncers.Store(server.ID, &pendingState{
|
||||||
|
timer: timer,
|
||||||
|
state: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start goroutine to handle the delayed insert
|
||||||
|
go func() {
|
||||||
|
<-timer.C
|
||||||
|
if debouncer, exists := s.debouncers.Load(server.ID); exists {
|
||||||
|
pending := debouncer.(*pendingState)
|
||||||
|
s.insertStateHistory(server.ID, pending.state)
|
||||||
|
s.debouncers.Delete(server.ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If enough time has passed since last insert, insert immediately
|
||||||
|
if s.shouldInsertStateHistory(server.ID) {
|
||||||
|
s.insertStateHistory(server.ID, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) StartAccServerRuntime(server *model.Server) {
|
||||||
|
s.instances.Delete(server.ID)
|
||||||
|
instance := tracking.NewAccServerInstance(server, func(state *model.ServerState, states ...tracking.StateChange) {
|
||||||
|
s.handleStateChange(server, state)
|
||||||
})
|
})
|
||||||
config, _ := DecodeFileName(ConfigurationJson)(server.ConfigPath)
|
config, _ := DecodeFileName(ConfigurationJson)(server.ConfigPath)
|
||||||
cfg := config.(model.Configuration)
|
cfg := config.(model.Configuration)
|
||||||
@@ -70,8 +134,11 @@ func (s *ServerService) StartAccServerRuntime(server *model.Server) {
|
|||||||
// context.Context: Application context
|
// context.Context: Application context
|
||||||
// Returns:
|
// Returns:
|
||||||
// string: Application version
|
// string: Application version
|
||||||
func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
|
func (as ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]model.Server, error) {
|
||||||
servers := as.repository.GetAll(ctx.UserContext())
|
servers, err := as.repository.GetAll(ctx.UserContext(), filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for i, server := range *servers {
|
for i, server := range *servers {
|
||||||
status, err := as.apiService.StatusServer(server.ServiceName)
|
status, err := as.apiService.StatusServer(server.ServiceName)
|
||||||
@@ -90,7 +157,7 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers
|
return servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetById
|
// GetById
|
||||||
@@ -100,8 +167,11 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
|
|||||||
// context.Context: Application context
|
// context.Context: Application context
|
||||||
// Returns:
|
// Returns:
|
||||||
// string: Application version
|
// string: Application version
|
||||||
func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) *model.Server {
|
func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) (*model.Server, error) {
|
||||||
server := as.repository.GetFirst(ctx.UserContext(), serverID)
|
server, err := as.repository.GetByID(ctx.UserContext(), serverID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
status, err := as.apiService.StatusServer(server.ServiceName)
|
status, err := as.apiService.StatusServer(server.ServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err.Error())
|
log.Print(err.Error())
|
||||||
@@ -117,5 +187,5 @@ func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) *model.Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return server
|
return server, nil
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
"acc-server-manager/local/repository"
|
"acc-server-manager/local/repository"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -24,6 +25,19 @@ func NewStateHistoryService(repository *repository.StateHistoryRepository) *Stat
|
|||||||
// context.Context: Application context
|
// context.Context: Application context
|
||||||
// Returns:
|
// Returns:
|
||||||
// string: Application version
|
// string: Application version
|
||||||
func (as StateHistoryService) GetAll(ctx *fiber.Ctx, id int) *[]model.StateHistory {
|
func (s *StateHistoryService) GetAll(ctx *fiber.Ctx, filter *model.StateHistoryFilter) (*[]model.StateHistory, error) {
|
||||||
return as.repository.GetAll(ctx.UserContext(), id)
|
result, err := s.repository.GetAll(ctx.UserContext(), filter)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting state history: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateHistoryService) Insert(ctx *fiber.Ctx, model *model.StateHistory) error {
|
||||||
|
if err := s.repository.Insert(ctx.UserContext(), model); err != nil {
|
||||||
|
log.Printf("Error inserting state history: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -82,3 +85,131 @@ func IndentJson(body []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return unmarshaledBody.Bytes(), nil
|
return unmarshaledBody.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseQueryFilter parses query parameters into a filter struct using reflection.
|
||||||
|
// It supports various field types and uses struct tags to determine parsing behavior.
|
||||||
|
// Supported tags:
|
||||||
|
// - `query:"field_name"` - specifies the query parameter name
|
||||||
|
// - `param:"param_name"` - specifies the path parameter name
|
||||||
|
// - `time_format:"format"` - specifies the time format for parsing dates (default: RFC3339)
|
||||||
|
func ParseQueryFilter(c *fiber.Ctx, filter interface{}) error {
|
||||||
|
val := reflect.ValueOf(filter)
|
||||||
|
if val.Kind() != reflect.Ptr || val.IsNil() {
|
||||||
|
return fmt.Errorf("filter must be a non-nil pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := val.Elem()
|
||||||
|
typ := elem.Type()
|
||||||
|
|
||||||
|
for i := 0; i < elem.NumField(); i++ {
|
||||||
|
field := elem.Field(i)
|
||||||
|
fieldType := typ.Field(i)
|
||||||
|
|
||||||
|
// Skip if field cannot be set
|
||||||
|
if !field.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for param tag first (path parameters)
|
||||||
|
if paramName := fieldType.Tag.Get("param"); paramName != "" {
|
||||||
|
if err := parsePathParam(c, field, paramName); err != nil {
|
||||||
|
return fmt.Errorf("error parsing path parameter %s: %v", paramName, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check for query tag
|
||||||
|
queryName := fieldType.Tag.Get("query")
|
||||||
|
if queryName == "" {
|
||||||
|
queryName = ToSnakeCase(fieldType.Name) // Default to snake_case of field name
|
||||||
|
}
|
||||||
|
|
||||||
|
queryVal := c.Query(queryName)
|
||||||
|
if queryVal == "" {
|
||||||
|
continue // Skip empty values
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseValue(field, queryVal, fieldType.Tag); err != nil {
|
||||||
|
return fmt.Errorf("error parsing query parameter %s: %v", queryName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePathParam(c *fiber.Ctx, field reflect.Value, paramName string) error {
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
val, err := c.ParamsInt(paramName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetInt(int64(val))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
val, err := c.ParamsInt(paramName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
case reflect.String:
|
||||||
|
field.SetString(c.Params(paramName))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported path parameter type: %v", field.Kind())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValue(field reflect.Value, value string, tag reflect.StructTag) error {
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
field.SetString(value)
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
val, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetInt(val)
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
val, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(val)
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
val, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetFloat(val)
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
val, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetBool(val)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
if field.Type() == reflect.TypeOf(time.Time{}) {
|
||||||
|
format := tag.Get("time_format")
|
||||||
|
if format == "" {
|
||||||
|
format = time.RFC3339
|
||||||
|
}
|
||||||
|
t, err := time.Parse(format, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(t))
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("unsupported struct type: %v", field.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported field type: %v", field.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user