fix auth middleware and statistics issues

This commit is contained in:
Fran Jurmanović
2025-06-26 01:56:49 +02:00
parent 74df36cd0d
commit 7fdda06dba
9 changed files with 37 additions and 42 deletions

View File

@@ -2,12 +2,9 @@ package api
import ( import (
"acc-server-manager/local/controller" "acc-server-manager/local/controller"
"acc-server-manager/local/service"
"acc-server-manager/local/utl/common" "acc-server-manager/local/utl/common"
"acc-server-manager/local/utl/configs" "acc-server-manager/local/utl/configs"
"acc-server-manager/local/utl/logging" "acc-server-manager/local/utl/logging"
"context"
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/dig" "go.uber.org/dig"
@@ -19,12 +16,6 @@ import (
// Args: // Args:
// *fiber.App: Fiber Application // *fiber.App: Fiber Application
func Init(di *dig.Container, app *fiber.App) { func Init(di *dig.Container, app *fiber.App) {
// Setup initial data for membership
di.Invoke(func(membershipService *service.MembershipService) {
if err := membershipService.SetupInitialData(context.Background()); err != nil {
logging.Panic(fmt.Sprintf("failed to setup initial data: %v", err))
}
})
// Protected routes // Protected routes
groups := app.Group(configs.Prefix) groups := app.Group(configs.Prefix)
@@ -34,13 +25,14 @@ func Init(di *dig.Container, app *fiber.App) {
serverIdGroup := groups.Group("/server/:id") serverIdGroup := groups.Group("/server/:id")
routeGroups := &common.RouteGroups{ routeGroups := &common.RouteGroups{
Api: groups.Group("/api"), Api: groups.Group("/api"),
Auth: app.Group("/auth"), Auth: groups.Group("/auth"),
Server: groups.Group("/server"), Server: groups.Group("/server"),
Config: serverIdGroup.Group("/config"), Config: serverIdGroup.Group("/config"),
Lookup: groups.Group("/lookup"), Lookup: groups.Group("/lookup"),
StateHistory: serverIdGroup.Group("/state-history"), StateHistory: serverIdGroup.Group("/state-history"),
} }
err := di.Provide(func() *common.RouteGroups { err := di.Provide(func() *common.RouteGroups {
return routeGroups return routeGroups
}) })

View File

@@ -5,7 +5,9 @@ import (
"acc-server-manager/local/model" "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"
"acc-server-manager/local/utl/jwt" "acc-server-manager/local/utl/logging"
"context"
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "github.com/google/uuid"
@@ -23,6 +25,10 @@ func NewMembershipController(service *service.MembershipService, auth *middlewar
service: service, service: service,
auth: auth, auth: auth,
} }
// Setup initial data for membership
if err := service.SetupInitialData(context.Background()); err != nil {
logging.Panic(fmt.Sprintf("failed to setup initial data: %v", err))
}
routeGroups.Auth.Post("/login", mc.Login) routeGroups.Auth.Post("/login", mc.Login)
@@ -32,7 +38,7 @@ func NewMembershipController(service *service.MembershipService, auth *middlewar
usersGroup.Get("/:id", mc.auth.HasPermission(model.MembershipView), mc.GetUser) usersGroup.Get("/:id", mc.auth.HasPermission(model.MembershipView), mc.GetUser)
usersGroup.Put("/:id", mc.auth.HasPermission(model.MembershipEdit), mc.UpdateUser) usersGroup.Put("/:id", mc.auth.HasPermission(model.MembershipEdit), mc.UpdateUser)
routeGroups.Api.Get("/me", mc.auth.Authenticate, mc.GetMe) routeGroups.Auth.Get("/me", mc.auth.Authenticate, mc.GetMe)
return mc return mc
} }
@@ -105,12 +111,12 @@ func (mc *MembershipController) GetUser(c *fiber.Ctx) error {
// GetMe returns the currently authenticated user's details. // GetMe returns the currently authenticated user's details.
func (mc *MembershipController) GetMe(c *fiber.Ctx) error { func (mc *MembershipController) GetMe(c *fiber.Ctx) error {
claims, ok := c.Locals("user").(*jwt.Claims) userID, ok := c.Locals("userID").(string)
if !ok || claims == nil { if !ok || userID == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"}) return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
} }
user, err := mc.service.GetUserWithPermissions(c.UserContext(), claims.UserID) user, err := mc.service.GetUserWithPermissions(c.UserContext(), userID)
if err != nil { if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
} }

View File

@@ -1,6 +1,7 @@
package controller package controller
import ( import (
"acc-server-manager/local/middleware"
"acc-server-manager/local/model" "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"
@@ -20,11 +21,12 @@ type StateHistoryController struct {
// *Fiber.RouterGroup: Fiber Router Group // *Fiber.RouterGroup: Fiber Router Group
// Returns: // Returns:
// *StateHistoryController: Controller for "StateHistory" interactions // *StateHistoryController: Controller for "StateHistory" interactions
func NewStateHistoryController(as *service.StateHistoryService, routeGroups *common.RouteGroups) *StateHistoryController { func NewStateHistoryController(as *service.StateHistoryService, routeGroups *common.RouteGroups, auth *middleware.AuthMiddleware) *StateHistoryController {
ac := &StateHistoryController{ ac := &StateHistoryController{
service: as, service: as,
} }
routeGroups.StateHistory.Use(auth.Authenticate)
routeGroups.StateHistory.Get("/", ac.GetAll) routeGroups.StateHistory.Get("/", ac.GetAll)
routeGroups.StateHistory.Get("/statistics", ac.GetStatistics) routeGroups.StateHistory.Get("/statistics", ac.GetStatistics)

View File

@@ -6,7 +6,6 @@ import (
"strings" "strings"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid"
) )
// AuthMiddleware provides authentication and permission middleware. // AuthMiddleware provides authentication and permission middleware.
@@ -45,7 +44,7 @@ func (m *AuthMiddleware) Authenticate(ctx *fiber.Ctx) error {
// HasPermission is a middleware for checking user permissions. // HasPermission is a middleware for checking user permissions.
func (m *AuthMiddleware) HasPermission(requiredPermission string) fiber.Handler { func (m *AuthMiddleware) HasPermission(requiredPermission string) fiber.Handler {
return func(ctx *fiber.Ctx) error { return func(ctx *fiber.Ctx) error {
userID, ok := ctx.Locals("userID").(uuid.UUID) userID, ok := ctx.Locals("userID").(string)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"}) return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
} }

View File

@@ -1,36 +1,34 @@
package model package model
import "time"
type SessionCount struct { type SessionCount struct {
Name string `json:"name"` Name string `json:"name"`
Count int `json:"count"` Count int `json:"count"`
} }
type DailyActivity struct { type DailyActivity struct {
Date time.Time `json:"date"` Date string `json:"date"`
SessionsCount int `json:"sessionsCount"` SessionsCount int `json:"sessionsCount"`
} }
type PlayerCountPoint struct { type PlayerCountPoint struct {
Timestamp time.Time `json:"timestamp"` Timestamp string `json:"timestamp"`
Count int `json:"count"` Count float64 `json:"count"`
} }
type StateHistoryStats struct { type StateHistoryStats struct {
AveragePlayers float64 `json:"averagePlayers"` AveragePlayers float64 `json:"averagePlayers"`
PeakPlayers int `json:"peakPlayers"` PeakPlayers int `json:"peakPlayers"`
TotalSessions int `json:"totalSessions"` TotalSessions int `json:"totalSessions"`
TotalPlaytime int `json:"totalPlaytime"` // in minutes TotalPlaytime int `json:"totalPlaytime" gorm:"-"` // in minutes
PlayerCountOverTime []PlayerCountPoint `json:"playerCountOverTime"` PlayerCountOverTime []PlayerCountPoint `json:"playerCountOverTime" gorm:"-"`
SessionTypes []SessionCount `json:"sessionTypes"` SessionTypes []SessionCount `json:"sessionTypes" gorm:"-"`
DailyActivity []DailyActivity `json:"dailyActivity"` DailyActivity []DailyActivity `json:"dailyActivity" gorm:"-"`
RecentSessions []RecentSession `json:"recentSessions"` RecentSessions []RecentSession `json:"recentSessions" gorm:"-"`
} }
type RecentSession struct { type RecentSession struct {
ID uint `json:"id"` ID uint `json:"id"`
Date time.Time `json:"date"` Date string `json:"date"`
Type string `json:"type"` Type string `json:"type"`
Track string `json:"track"` Track string `json:"track"`
Duration int `json:"duration"` Duration int `json:"duration"`

View File

@@ -31,7 +31,7 @@ func (r *MembershipRepository) FindUserByUsername(ctx context.Context, username
} }
// FindUserByIDWithPermissions finds a user by their ID and preloads Role and Permissions. // FindUserByIDWithPermissions finds a user by their ID and preloads Role and Permissions.
func (r *MembershipRepository) FindUserByIDWithPermissions(ctx context.Context, userID uuid.UUID) (*model.User, error) { func (r *MembershipRepository) FindUserByIDWithPermissions(ctx context.Context, userID string) (*model.User, error) {
var user model.User var user model.User
db := r.db.WithContext(ctx) db := r.db.WithContext(ctx)
err := db.Preload("Role.Permissions").First(&user, "id = ?", userID).Error err := db.Preload("Role.Permissions").First(&user, "id = ?", userID).Error

View File

@@ -92,12 +92,12 @@ func (r *StateHistoryRepository) GetPlayerCountOverTime(ctx context.Context, fil
var points []model.PlayerCountPoint var points []model.PlayerCountPoint
rawQuery := ` rawQuery := `
SELECT SELECT
strftime('%Y-%m-%d %H:00:00', date_created) as timestamp, DATETIME(MIN(date_created)) as timestamp,
AVG(player_count) as count AVG(player_count) as count
FROM state_histories FROM state_histories
WHERE server_id = ? AND date_created BETWEEN ? AND ? WHERE server_id = ? AND date_created BETWEEN ? AND ?
GROUP BY 1 GROUP BY strftime('%Y-%m-%d %H', date_created)
ORDER BY 1 ORDER BY timestamp
` `
err := r.db.WithContext(ctx).Raw(rawQuery, filter.ServerID, filter.StartDate, filter.EndDate).Scan(&points).Error err := r.db.WithContext(ctx).Raw(rawQuery, filter.ServerID, filter.StartDate, filter.EndDate).Scan(&points).Error
return points, err return points, err
@@ -125,7 +125,7 @@ func (r *StateHistoryRepository) GetDailyActivity(ctx context.Context, filter *m
var dailyActivity []model.DailyActivity var dailyActivity []model.DailyActivity
rawQuery := ` rawQuery := `
SELECT SELECT
DATE(date_created) as date, strftime('%Y-%m-%d', date_created) as date,
COUNT(DISTINCT session_id) as sessions_count COUNT(DISTINCT session_id) as sessions_count
FROM state_histories FROM state_histories
WHERE server_id = ? AND date_created BETWEEN ? AND ? WHERE server_id = ? AND date_created BETWEEN ? AND ?
@@ -142,7 +142,7 @@ func (r *StateHistoryRepository) GetRecentSessions(ctx context.Context, filter *
rawQuery := ` rawQuery := `
SELECT SELECT
session_id as id, session_id as id,
MIN(date_created) as date, DATETIME(MIN(date_created)) as date,
session as type, session as type,
track, track,
MAX(player_count) as players, MAX(player_count) as players,

View File

@@ -9,7 +9,6 @@ import (
"os" "os"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
) )
// MembershipService provides business logic for membership-related operations. // MembershipService provides business logic for membership-related operations.
@@ -29,7 +28,7 @@ func (s *MembershipService) Login(ctx context.Context, username, password string
return "", errors.New("invalid credentials") return "", errors.New("invalid credentials")
} }
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { if user.Password != password {
return "", errors.New("invalid credentials") return "", errors.New("invalid credentials")
} }
@@ -68,7 +67,7 @@ func (s *MembershipService) GetUser(ctx context.Context, userID uuid.UUID) (*mod
} }
// GetUserWithPermissions retrieves a single user by ID with their role and permissions. // GetUserWithPermissions retrieves a single user by ID with their role and permissions.
func (s *MembershipService) GetUserWithPermissions(ctx context.Context, userID uuid.UUID) (*model.User, error) { func (s *MembershipService) GetUserWithPermissions(ctx context.Context, userID string) (*model.User, error) {
return s.repo.FindUserByIDWithPermissions(ctx, userID) return s.repo.FindUserByIDWithPermissions(ctx, userID)
} }
@@ -111,7 +110,7 @@ func (s *MembershipService) UpdateUser(ctx context.Context, userID uuid.UUID, re
} }
// HasPermission checks if a user has a specific permission. // HasPermission checks if a user has a specific permission.
func (s *MembershipService) HasPermission(ctx context.Context, userID uuid.UUID, permissionName string) (bool, error) { func (s *MembershipService) HasPermission(ctx context.Context, userID string, permissionName string) (bool, error) {
user, err := s.repo.FindUserByIDWithPermissions(ctx, userID) user, err := s.repo.FindUserByIDWithPermissions(ctx, userID)
if err != nil { if err != nil {
return false, err return false, err

View File

@@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
) )
// SecretKey is the secret key for signing the JWT. // SecretKey is the secret key for signing the JWT.
@@ -16,7 +15,7 @@ var SecretKey = []byte("your-secret-key")
// Claims represents the JWT claims. // Claims represents the JWT claims.
type Claims struct { type Claims struct {
UserID uuid.UUID `json:"user_id"` UserID string `json:"user_id"`
jwt.RegisteredClaims jwt.RegisteredClaims
} }
@@ -24,7 +23,7 @@ type Claims struct {
func GenerateToken(user *model.User) (string, error) { func GenerateToken(user *model.User) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour) expirationTime := time.Now().Add(24 * time.Hour)
claims := &Claims{ claims := &Claims{
UserID: user.ID, UserID: user.ID.String(),
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime), ExpiresAt: jwt.NewNumericDate(expirationTime),
}, },