add membership

This commit is contained in:
Fran Jurmanović
2025-06-26 00:51:54 +02:00
parent 69733e4940
commit 74df36cd0d
24 changed files with 863 additions and 83 deletions

8
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
go.uber.org/dig v1.17.1 go.uber.org/dig v1.17.1
golang.org/x/sync v0.15.0 golang.org/x/sync v0.15.0
golang.org/x/text v0.16.0 golang.org/x/text v0.26.0
gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.11 gorm.io/gorm v1.25.11
) )
@@ -23,6 +23,7 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
@@ -37,7 +38,8 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/crypto v0.39.0 // indirect
golang.org/x/tools v0.23.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/tools v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

11
go.sum
View File

@@ -16,6 +16,8 @@ github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ= github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ=
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8= github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -65,18 +67,27 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -2,13 +2,14 @@ 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"
"os" "context"
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/basicauth"
"go.uber.org/dig" "go.uber.org/dig"
) )
@@ -18,25 +19,28 @@ 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
groups := app.Group(configs.Prefix) groups := app.Group(configs.Prefix)
basicAuthConfig := basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": os.Getenv("PASSWORD"),
},
})
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"),
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"),
} }
groups.Use(basicAuthConfig)
err := di.Provide(func() *common.RouteGroups { err := di.Provide(func() *common.RouteGroups {
return routeGroups return routeGroups
}) })

View File

@@ -27,9 +27,9 @@ func NewConfigController(as *service.ConfigService, routeGroups *common.RouteGro
apiService: as2, apiService: as2,
} }
routeGroups.Config.Put("/:file", ac.updateConfig) routeGroups.Config.Put("/:file", ac.UpdateConfig)
routeGroups.Config.Get("/:file", ac.getConfig) routeGroups.Config.Get("/:file", ac.GetConfig)
routeGroups.Config.Get("/", ac.getConfigs) routeGroups.Config.Get("/", ac.GetConfigs)
return ac return ac
} }
@@ -44,7 +44,7 @@ func NewConfigController(as *service.ConfigService, routeGroups *common.RouteGro
// @Tags Config // @Tags Config
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/server/{id}/config/{file} [put] // @Router /v1/server/{id}/config/{file} [put]
func (ac *ConfigController) updateConfig(c *fiber.Ctx) error { func (ac *ConfigController) UpdateConfig(c *fiber.Ctx) error {
restart := c.QueryBool("restart") restart := c.QueryBool("restart")
serverID, _ := c.ParamsInt("id") serverID, _ := c.ParamsInt("id")
c.Locals("serverId", serverID) c.Locals("serverId", serverID)
@@ -79,7 +79,7 @@ func (ac *ConfigController) updateConfig(c *fiber.Ctx) error {
// @Tags Config // @Tags Config
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/server/{id}/config/{file} [get] // @Router /v1/server/{id}/config/{file} [get]
func (ac *ConfigController) getConfig(c *fiber.Ctx) error { func (ac *ConfigController) GetConfig(c *fiber.Ctx) error {
Model, err := ac.service.GetConfig(c) Model, err := ac.service.GetConfig(c)
if err != nil { if err != nil {
logging.Error(err.Error()) logging.Error(err.Error())
@@ -96,7 +96,7 @@ func (ac *ConfigController) getConfig(c *fiber.Ctx) error {
// @Tags Config // @Tags Config
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/server/{id}/config [get] // @Router /v1/server/{id}/config [get]
func (ac *ConfigController) getConfigs(c *fiber.Ctx) error { func (ac *ConfigController) GetConfigs(c *fiber.Ctx) error {
Model, err := ac.service.GetConfigs(c) Model, err := ac.service.GetConfigs(c)
if err != nil { if err != nil {
logging.Error(err.Error()) logging.Error(err.Error())

View File

@@ -1,6 +1,7 @@
package controller package controller
import ( import (
"acc-server-manager/local/middleware"
"acc-server-manager/local/service" "acc-server-manager/local/service"
"acc-server-manager/local/utl/logging" "acc-server-manager/local/utl/logging"
@@ -15,6 +16,10 @@ import (
func InitializeControllers(c *dig.Container) { func InitializeControllers(c *dig.Container) {
service.InitializeServices(c) service.InitializeServices(c)
if err := c.Provide(middleware.NewAuthMiddleware); err != nil {
logging.Panic("unable to initialize auth middleware")
}
err := c.Invoke(NewApiController) err := c.Invoke(NewApiController)
if err != nil { if err != nil {
logging.Panic("unable to initialize api controller") logging.Panic("unable to initialize api controller")
@@ -39,4 +44,9 @@ func InitializeControllers(c *dig.Container) {
if err != nil { if err != nil {
logging.Panic("unable to initialize stateHistory controller") logging.Panic("unable to initialize stateHistory controller")
} }
err = c.Invoke(NewMembershipController)
if err != nil {
logging.Panic("unable to initialize membership controller")
}
} }

View File

@@ -23,11 +23,11 @@ func NewLookupController(as *service.LookupService, routeGroups *common.RouteGro
ac := &LookupController{ ac := &LookupController{
service: as, service: as,
} }
routeGroups.Lookup.Get("/tracks", ac.getTracks) routeGroups.Lookup.Get("/tracks", ac.GetTracks)
routeGroups.Lookup.Get("/car-models", ac.getCarModels) routeGroups.Lookup.Get("/car-models", ac.GetCarModels)
routeGroups.Lookup.Get("/driver-categories", ac.getDriverCategories) routeGroups.Lookup.Get("/driver-categories", ac.GetDriverCategories)
routeGroups.Lookup.Get("/cup-categories", ac.getCupCategories) routeGroups.Lookup.Get("/cup-categories", ac.GetCupCategories)
routeGroups.Lookup.Get("/session-types", ac.getSessionTypes) routeGroups.Lookup.Get("/session-types", ac.GetSessionTypes)
return ac return ac
} }
@@ -39,7 +39,7 @@ func NewLookupController(as *service.LookupService, routeGroups *common.RouteGro
// @Tags Lookup // @Tags Lookup
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/lookup/tracks [get] // @Router /v1/lookup/tracks [get]
func (ac *LookupController) getTracks(c *fiber.Ctx) error { func (ac *LookupController) GetTracks(c *fiber.Ctx) error {
result, err := ac.service.GetTracks(c) result, err := ac.service.GetTracks(c)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@@ -56,7 +56,7 @@ func (ac *LookupController) getTracks(c *fiber.Ctx) error {
// @Tags Lookup // @Tags Lookup
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/lookup/car-models [get] // @Router /v1/lookup/car-models [get]
func (ac *LookupController) getCarModels(c *fiber.Ctx) error { func (ac *LookupController) GetCarModels(c *fiber.Ctx) error {
result, err := ac.service.GetCarModels(c) result, err := ac.service.GetCarModels(c)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@@ -73,7 +73,7 @@ func (ac *LookupController) getCarModels(c *fiber.Ctx) error {
// @Tags Lookup // @Tags Lookup
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/lookup/driver-categories [get] // @Router /v1/lookup/driver-categories [get]
func (ac *LookupController) getDriverCategories(c *fiber.Ctx) error { func (ac *LookupController) GetDriverCategories(c *fiber.Ctx) error {
result, err := ac.service.GetDriverCategories(c) result, err := ac.service.GetDriverCategories(c)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@@ -90,7 +90,7 @@ func (ac *LookupController) getDriverCategories(c *fiber.Ctx) error {
// @Tags Lookup // @Tags Lookup
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/lookup/cup-categories [get] // @Router /v1/lookup/cup-categories [get]
func (ac *LookupController) getCupCategories(c *fiber.Ctx) error { func (ac *LookupController) GetCupCategories(c *fiber.Ctx) error {
result, err := ac.service.GetCupCategories(c) result, err := ac.service.GetCupCategories(c)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@@ -107,7 +107,7 @@ func (ac *LookupController) getCupCategories(c *fiber.Ctx) error {
// @Tags Lookup // @Tags Lookup
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/lookup/session-types [get] // @Router /v1/lookup/session-types [get]
func (ac *LookupController) getSessionTypes(c *fiber.Ctx) error { func (ac *LookupController) GetSessionTypes(c *fiber.Ctx) error {
result, err := ac.service.GetSessionTypes(c) result, err := ac.service.GetSessionTypes(c)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{

View File

@@ -0,0 +1,142 @@
package controller
import (
"acc-server-manager/local/middleware"
"acc-server-manager/local/model"
"acc-server-manager/local/service"
"acc-server-manager/local/utl/common"
"acc-server-manager/local/utl/jwt"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
// MembershipController handles API requests for membership.
type MembershipController struct {
service *service.MembershipService
auth *middleware.AuthMiddleware
}
// NewMembershipController creates a new MembershipController.
func NewMembershipController(service *service.MembershipService, auth *middleware.AuthMiddleware, routeGroups *common.RouteGroups) *MembershipController {
mc := &MembershipController{
service: service,
auth: auth,
}
routeGroups.Auth.Post("/login", mc.Login)
usersGroup := routeGroups.Api.Group("/users", mc.auth.Authenticate)
usersGroup.Post("/", mc.auth.HasPermission(model.MembershipCreate), mc.CreateUser)
usersGroup.Get("/", mc.auth.HasPermission(model.MembershipView), mc.ListUsers)
usersGroup.Get("/:id", mc.auth.HasPermission(model.MembershipView), mc.GetUser)
usersGroup.Put("/:id", mc.auth.HasPermission(model.MembershipEdit), mc.UpdateUser)
routeGroups.Api.Get("/me", mc.auth.Authenticate, mc.GetMe)
return mc
}
// Login handles user login.
func (c *MembershipController) Login(ctx *fiber.Ctx) error {
type request struct {
Username string `json:"username"`
Password string `json:"password"`
}
var req request
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
}
token, err := c.service.Login(ctx.UserContext(), req.Username, req.Password)
if err != nil {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": err.Error()})
}
return ctx.JSON(fiber.Map{"token": token})
}
// CreateUser creates a new user.
func (mc *MembershipController) CreateUser(c *fiber.Ctx) error {
type request struct {
Username string `json:"username"`
Password string `json:"password"`
Role string `json:"role"`
}
var req request
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
}
user, err := mc.service.CreateUser(c.UserContext(), req.Username, req.Password, req.Role)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(user)
}
// ListUsers lists all users.
func (mc *MembershipController) ListUsers(c *fiber.Ctx) error {
users, err := mc.service.ListUsers(c.UserContext())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(users)
}
// GetUser gets a single user by ID.
func (mc *MembershipController) GetUser(c *fiber.Ctx) error {
id, err := uuid.Parse(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid user ID"})
}
user, err := mc.service.GetUser(c.UserContext(), id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
return c.JSON(user)
}
// GetMe returns the currently authenticated user's details.
func (mc *MembershipController) GetMe(c *fiber.Ctx) error {
claims, ok := c.Locals("user").(*jwt.Claims)
if !ok || claims == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
}
user, err := mc.service.GetUserWithPermissions(c.UserContext(), claims.UserID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
// Sanitize the user object to not expose password
user.Password = ""
return c.JSON(user)
}
// UpdateUser updates a user.
func (mc *MembershipController) UpdateUser(c *fiber.Ctx) error {
id, err := uuid.Parse(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid user ID"})
}
var req service.UpdateUserRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
}
user, err := mc.service.UpdateUser(c.UserContext(), id, req)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(user)
}

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"
@@ -12,33 +13,25 @@ type ServerController struct {
service *service.ServerService service *service.ServerService
} }
// NewServerController // NewServerController initializes ServerController.
// Initializes ServerController. func NewServerController(ss *service.ServerService, routeGroups *common.RouteGroups, auth *middleware.AuthMiddleware) *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{ ac := &ServerController{
service: as, service: ss,
} }
routeGroups.Server.Get("/", ac.getAll) serverRoutes := routeGroups.Server
routeGroups.Server.Get("/:id", ac.getById) serverRoutes.Use(auth.Authenticate)
routeGroups.Server.Post("/", ac.createServer)
serverRoutes.Get("/", auth.HasPermission(model.ServerView), ac.GetAll)
serverRoutes.Get("/:id", auth.HasPermission(model.ServerView), ac.GetById)
serverRoutes.Post("/", auth.HasPermission(model.ServerCreate), ac.CreateServer)
serverRoutes.Put("/:id", auth.HasPermission(model.ServerUpdate), ac.UpdateServer)
serverRoutes.Delete("/:id", auth.HasPermission(model.ServerDelete), ac.DeleteServer)
return ac return ac
} }
// getAll returns Servers // GetAll returns Servers
// func (ac *ServerController) GetAll(c *fiber.Ctx) error {
// @Summary Return Servers
// @Description Return Servers
// @Tags Server
// @Success 200 {array} string
// @Router /v1/server [get]
func (ac *ServerController) getAll(c *fiber.Ctx) error {
var filter model.ServerFilter var filter model.ServerFilter
if err := common.ParseQueryFilter(c, &filter); err != nil { if err := common.ParseQueryFilter(c, &filter); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
@@ -52,14 +45,8 @@ func (ac *ServerController) getAll(c *fiber.Ctx) error {
return c.JSON(ServerModel) return c.JSON(ServerModel)
} }
// getById returns Servers // GetById returns a single server by its ID
// func (ac *ServerController) GetById(c *fiber.Ctx) error {
// @Summary Return Servers
// @Description Return Servers
// @Tags Server
// @Success 200 {array} string
// @Router /v1/server [get]
func (ac *ServerController) getById(c *fiber.Ctx) error {
serverID, _ := c.ParamsInt("id") serverID, _ := c.ParamsInt("id")
ServerModel, err := ac.service.GetById(c, serverID) ServerModel, err := ac.service.GetById(c, serverID)
if err != nil { if err != nil {
@@ -68,14 +55,8 @@ func (ac *ServerController) getById(c *fiber.Ctx) error {
return c.JSON(ServerModel) return c.JSON(ServerModel)
} }
// createServer creates a new server // CreateServer creates a new server
// func (ac *ServerController) CreateServer(c *fiber.Ctx) error {
// @Summary Create a new server
// @Description Create a new server
// @Tags Server
// @Success 200 {array} string
// @Router /v1/server [post]
func (ac *ServerController) createServer(c *fiber.Ctx) error {
server := new(model.Server) server := new(model.Server)
if err := c.BodyParser(server); err != nil { if err := c.BodyParser(server); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
@@ -90,3 +71,36 @@ func (ac *ServerController) createServer(c *fiber.Ctx) error {
} }
return c.JSON(server) return c.JSON(server)
} }
// UpdateServer updates an existing server
func (ac *ServerController) UpdateServer(c *fiber.Ctx) error {
serverID, _ := c.ParamsInt("id")
server := new(model.Server)
if err := c.BodyParser(server); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
server.ID = uint(serverID)
if err := ac.service.UpdateServer(c, server); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.JSON(server)
}
// DeleteServer deletes a server
func (ac *ServerController) DeleteServer(c *fiber.Ctx) error {
serverID, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid server ID"})
}
if err := ac.service.DeleteServer(c, serverID); err != nil {
return c.Status(500).SendString(err.Error())
}
return c.SendStatus(204)
}

View File

@@ -25,20 +25,20 @@ func NewStateHistoryController(as *service.StateHistoryService, routeGroups *com
service: as, service: as,
} }
routeGroups.StateHistory.Get("/", ac.getAll) routeGroups.StateHistory.Get("/", ac.GetAll)
routeGroups.StateHistory.Get("/statistics", ac.getStatistics) routeGroups.StateHistory.Get("/statistics", ac.GetStatistics)
return ac return ac
} }
// getAll returns StateHistorys // GetAll returns StateHistorys
// //
// @Summary Return StateHistorys // @Summary Return StateHistorys
// @Description Return StateHistorys // @Description Return StateHistorys
// @Tags StateHistory // @Tags StateHistory
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/state-history [get] // @Router /v1/state-history [get]
func (ac *StateHistoryController) getAll(c *fiber.Ctx) error { func (ac *StateHistoryController) GetAll(c *fiber.Ctx) error {
var filter model.StateHistoryFilter var filter model.StateHistoryFilter
if err := common.ParseQueryFilter(c, &filter); err != nil { if err := common.ParseQueryFilter(c, &filter); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
@@ -63,7 +63,7 @@ func (ac *StateHistoryController) getAll(c *fiber.Ctx) error {
// @Tags StateHistory // @Tags StateHistory
// @Success 200 {array} string // @Success 200 {array} string
// @Router /v1/state-history/statistics [get] // @Router /v1/state-history/statistics [get]
func (ac *StateHistoryController) getStatistics(c *fiber.Ctx) error { func (ac *StateHistoryController) GetStatistics(c *fiber.Ctx) error {
var filter model.StateHistoryFilter var filter model.StateHistoryFilter
if err := common.ParseQueryFilter(c, &filter); err != nil { if err := common.ParseQueryFilter(c, &filter); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{

60
local/middleware/auth.go Normal file
View File

@@ -0,0 +1,60 @@
package middleware
import (
"acc-server-manager/local/service"
"acc-server-manager/local/utl/jwt"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
// AuthMiddleware provides authentication and permission middleware.
type AuthMiddleware struct {
membershipService *service.MembershipService
}
// NewAuthMiddleware creates a new AuthMiddleware.
func NewAuthMiddleware(ms *service.MembershipService) *AuthMiddleware {
return &AuthMiddleware{
membershipService: ms,
}
}
// Authenticate is a middleware for JWT authentication.
func (m *AuthMiddleware) Authenticate(ctx *fiber.Ctx) error {
authHeader := ctx.Get("Authorization")
if authHeader == "" {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing or malformed JWT"})
}
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing or malformed JWT"})
}
claims, err := jwt.ValidateToken(parts[1])
if err != nil {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired JWT"})
}
ctx.Locals("userID", claims.UserID)
return ctx.Next()
}
// HasPermission is a middleware for checking user permissions.
func (m *AuthMiddleware) HasPermission(requiredPermission string) fiber.Handler {
return func(ctx *fiber.Ctx) error {
userID, ok := ctx.Locals("userID").(uuid.UUID)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
}
has, err := m.membershipService.HasPermission(ctx.UserContext(), userID, requiredPermission)
if err != nil || !has {
return ctx.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Forbidden"})
}
return ctx.Next()
}
}

19
local/model/permission.go Normal file
View File

@@ -0,0 +1,19 @@
package model
import (
"github.com/google/uuid"
"gorm.io/gorm"
)
// Permission represents an action that can be performed in the system.
type Permission struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;"`
Name string `json:"name" gorm:"unique_index;not null"`
}
// BeforeCreate is a GORM hook that runs before creating new credentials
func (s *Permission) BeforeCreate(tx *gorm.DB) error {
s.ID = uuid.New()
return nil
}

View File

@@ -0,0 +1,53 @@
package model
// Permission constants
const (
ServerView = "server.view"
ServerCreate = "server.create"
ServerUpdate = "server.update"
ServerDelete = "server.delete"
ServerStart = "server.start"
ServerStop = "server.stop"
ConfigView = "config.view"
ConfigUpdate = "config.update"
UserView = "user.view"
UserCreate = "user.create"
UserUpdate = "user.update"
UserDelete = "user.delete"
RoleView = "role.view"
RoleCreate = "role.create"
RoleUpdate = "role.update"
RoleDelete = "role.delete"
MembershipCreate = "membership.create"
MembershipView = "membership.view"
MembershipEdit = "membership.edit"
)
// AllPermissions returns a slice of all permission strings.
func AllPermissions() []string {
return []string{
ServerView,
ServerCreate,
ServerUpdate,
ServerDelete,
ServerStart,
ServerStop,
ConfigView,
ConfigUpdate,
UserView,
UserCreate,
UserUpdate,
UserDelete,
RoleView,
RoleCreate,
RoleUpdate,
RoleDelete,
MembershipCreate,
MembershipView,
MembershipEdit,
}
}

20
local/model/role.go Normal file
View File

@@ -0,0 +1,20 @@
package model
import (
"github.com/google/uuid"
"gorm.io/gorm"
)
// Role represents a user role in the system.
type Role struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;"`
Name string `json:"name" gorm:"unique_index;not null"`
Permissions []Permission `json:"permissions" gorm:"many2many:role_permissions;"`
}
// BeforeCreate is a GORM hook that runs before creating new credentials
func (s *Role) BeforeCreate(tx *gorm.DB) error {
s.ID = uuid.New()
return nil
}

View File

@@ -24,7 +24,7 @@ type Server struct {
Port int `gorm:"not null" json:"-"` Port int `gorm:"not null" json:"-"`
Path string `gorm:"not null" json:"path"` // e.g. "/acc/servers/server1/" Path string `gorm:"not null" json:"path"` // e.g. "/acc/servers/server1/"
ServiceName string `gorm:"not null" json:"serviceName"` // Windows service name ServiceName string `gorm:"not null" json:"serviceName"` // Windows service name
State ServerState `gorm:"-" json:"state"` State *ServerState `gorm:"-" json:"state"`
DateCreated time.Time `json:"dateCreated"` DateCreated time.Time `json:"dateCreated"`
FromSteamCMD bool `gorm:"not null; default:true" json:"-"` FromSteamCMD bool `gorm:"not null; default:true" json:"-"`
} }

70
local/model/user.go Normal file
View File

@@ -0,0 +1,70 @@
package model
import (
"errors"
"github.com/google/uuid"
"gorm.io/gorm"
)
// User represents a user account in the system.
type User struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;"`
Username string `json:"username" gorm:"unique_index;not null"`
Password string `json:"password" gorm:"not null"`
RoleID uuid.UUID `json:"role_id" gorm:"type:uuid"`
Role Role `json:"role"`
}
// BeforeCreate is a GORM hook that runs before creating new credentials
func (s *User) BeforeCreate(tx *gorm.DB) error {
s.ID = uuid.New()
// Encrypt password before saving
encrypted, err := EncryptPassword(s.Password)
if err != nil {
return err
}
s.Password = encrypted
return nil
}
// BeforeUpdate is a GORM hook that runs before updating credentials
func (s *User) BeforeUpdate(tx *gorm.DB) error {
// Only encrypt if password field is being updated
if tx.Statement.Changed("Password") {
encrypted, err := EncryptPassword(s.Password)
if err != nil {
return err
}
s.Password = encrypted
}
return nil
}
// AfterFind is a GORM hook that runs after fetching credentials
func (s *User) AfterFind(tx *gorm.DB) error {
// Decrypt password after fetching
if s.Password != "" {
decrypted, err := DecryptPassword(s.Password)
if err != nil {
return err
}
s.Password = decrypted
}
return nil
}
// Validate checks if the credentials are valid
func (s *User) Validate() error {
if s.Username == "" {
return errors.New("username is required")
}
if s.Password == "" {
return errors.New("password is required")
}
return nil
}

View File

@@ -0,0 +1,142 @@
package repository
import (
"acc-server-manager/local/model"
"context"
"github.com/google/uuid"
"gorm.io/gorm"
)
// MembershipRepository handles database operations for users, roles, and permissions.
type MembershipRepository struct {
db *gorm.DB
}
// NewMembershipRepository creates a new MembershipRepository.
func NewMembershipRepository(db *gorm.DB) *MembershipRepository {
return &MembershipRepository{db: db}
}
// FindUserByUsername finds a user by their username.
// It preloads the user's role and the role's permissions.
func (r *MembershipRepository) FindUserByUsername(ctx context.Context, username string) (*model.User, error) {
var user model.User
db := r.db.WithContext(ctx)
err := db.Preload("Role.Permissions").Where("username = ?", username).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// 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) {
var user model.User
db := r.db.WithContext(ctx)
err := db.Preload("Role.Permissions").First(&user, "id = ?", userID).Error
if err != nil {
return nil, err
}
return &user, nil
}
// CreateUser creates a new user.
func (r *MembershipRepository) CreateUser(ctx context.Context, user *model.User) error {
db := r.db.WithContext(ctx)
return db.Create(user).Error
}
// FindRoleByName finds a role by its name.
func (r *MembershipRepository) FindRoleByName(ctx context.Context, name string) (*model.Role, error) {
var role model.Role
db := r.db.WithContext(ctx)
err := db.Where("name = ?", name).First(&role).Error
if err != nil {
return nil, err
}
return &role, nil
}
// CreateRole creates a new role.
func (r *MembershipRepository) CreateRole(ctx context.Context, role *model.Role) error {
db := r.db.WithContext(ctx)
return db.Create(role).Error
}
// FindPermissionByName finds a permission by its name.
func (r *MembershipRepository) FindPermissionByName(ctx context.Context, name string) (*model.Permission, error) {
var permission model.Permission
db := r.db.WithContext(ctx)
err := db.Where("name = ?", name).First(&permission).Error
if err != nil {
return nil, err
}
return &permission, nil
}
// CreatePermission creates a new permission.
func (r *MembershipRepository) CreatePermission(ctx context.Context, permission *model.Permission) error {
db := r.db.WithContext(ctx)
return db.Create(permission).Error
}
// AssignPermissionsToRole assigns a set of permissions to a role.
func (r *MembershipRepository) AssignPermissionsToRole(ctx context.Context, role *model.Role, permissions []model.Permission) error {
db := r.db.WithContext(ctx)
return db.Model(role).Association("Permissions").Replace(permissions)
}
// GetUserPermissions retrieves all permissions for a given user ID.
func (r *MembershipRepository) GetUserPermissions(ctx context.Context, userID uuid.UUID) ([]string, error) {
var user model.User
db := r.db.WithContext(ctx)
if err := db.Preload("Role.Permissions").First(&user, "id = ?", userID).Error; err != nil {
return nil, err
}
permissions := make([]string, len(user.Role.Permissions))
for i, p := range user.Role.Permissions {
permissions[i] = p.Name
}
return permissions, nil
}
// ListUsers retrieves all users.
func (r *MembershipRepository) ListUsers(ctx context.Context) ([]*model.User, error) {
var users []*model.User
db := r.db.WithContext(ctx)
err := db.Preload("Role").Find(&users).Error
return users, err
}
// FindUserByID finds a user by their ID.
func (r *MembershipRepository) FindUserByID(ctx context.Context, userID uuid.UUID) (*model.User, error) {
var user model.User
db := r.db.WithContext(ctx)
err := db.Preload("Role").First(&user, "id = ?", userID).Error
if err != nil {
return nil, err
}
return &user, nil
}
// UpdateUser updates a user's details in the database.
func (r *MembershipRepository) UpdateUser(ctx context.Context, user *model.User) error {
db := r.db.WithContext(ctx)
return db.Save(user).Error
}
// FindRoleByID finds a role by its ID.
func (r *MembershipRepository) FindRoleByID(ctx context.Context, roleID uuid.UUID) (*model.Role, error) {
var role model.Role
db := r.db.WithContext(ctx)
err := db.First(&role, "id = ?", roleID).Error
if err != nil {
return nil, err
}
return &role, nil
}

View File

@@ -17,4 +17,5 @@ func InitializeRepositories(c *dig.Container) {
c.Provide(NewLookupRepository) c.Provide(NewLookupRepository)
c.Provide(NewSteamCredentialsRepository) c.Provide(NewSteamCredentialsRepository)
c.Provide(NewSystemConfigRepository) c.Provide(NewSystemConfigRepository)
c.Provide(NewMembershipRepository)
} }

173
local/service/membership.go Normal file
View File

@@ -0,0 +1,173 @@
package service
import (
"acc-server-manager/local/model"
"acc-server-manager/local/repository"
"acc-server-manager/local/utl/jwt"
"context"
"errors"
"os"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
// MembershipService provides business logic for membership-related operations.
type MembershipService struct {
repo *repository.MembershipRepository
}
// NewMembershipService creates a new MembershipService.
func NewMembershipService(repo *repository.MembershipRepository) *MembershipService {
return &MembershipService{repo: repo}
}
// Login authenticates a user and returns a JWT.
func (s *MembershipService) Login(ctx context.Context, username, password string) (string, error) {
user, err := s.repo.FindUserByUsername(ctx, username)
if err != nil {
return "", errors.New("invalid credentials")
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return "", errors.New("invalid credentials")
}
return jwt.GenerateToken(user)
}
// CreateUser creates a new user.
func (s *MembershipService) CreateUser(ctx context.Context, username, password, roleName string) (*model.User, error) {
role, err := s.repo.FindRoleByName(ctx, roleName)
if err != nil {
return nil, errors.New("role not found")
}
user := &model.User{
Username: username,
Password: password,
RoleID: role.ID,
}
if err := s.repo.CreateUser(ctx, user); err != nil {
return nil, err
}
return user, nil
}
// ListUsers retrieves all users.
func (s *MembershipService) ListUsers(ctx context.Context) ([]*model.User, error) {
return s.repo.ListUsers(ctx)
}
// GetUser retrieves a single user by ID.
func (s *MembershipService) GetUser(ctx context.Context, userID uuid.UUID) (*model.User, error) {
return s.repo.FindUserByID(ctx, userID)
}
// 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) {
return s.repo.FindUserByIDWithPermissions(ctx, userID)
}
// UpdateUserRequest defines the request body for updating a user.
type UpdateUserRequest struct {
Username *string `json:"username"`
Password *string `json:"password"`
RoleID *uuid.UUID `json:"roleId"`
}
// UpdateUser updates a user's details.
func (s *MembershipService) UpdateUser(ctx context.Context, userID uuid.UUID, req UpdateUserRequest) (*model.User, error) {
user, err := s.repo.FindUserByID(ctx, userID)
if err != nil {
return nil, errors.New("user not found")
}
if req.Username != nil {
user.Username = *req.Username
}
if req.Password != nil && *req.Password != "" {
user.Password = *req.Password
}
if req.RoleID != nil {
// Check if role exists
_, err := s.repo.FindRoleByID(ctx, *req.RoleID)
if err != nil {
return nil, errors.New("role not found")
}
user.RoleID = *req.RoleID
}
if err := s.repo.UpdateUser(ctx, user); err != nil {
return nil, err
}
return user, nil
}
// HasPermission checks if a user has a specific permission.
func (s *MembershipService) HasPermission(ctx context.Context, userID uuid.UUID, permissionName string) (bool, error) {
user, err := s.repo.FindUserByIDWithPermissions(ctx, userID)
if err != nil {
return false, err
}
// Super admin has all permissions
if user.Role.Name == "Super Admin" {
return true, nil
}
for _, p := range user.Role.Permissions {
if p.Name == permissionName {
return true, nil
}
}
return false, nil
}
// SetupInitialData creates the initial roles and permissions.
func (s *MembershipService) SetupInitialData(ctx context.Context) error {
// Define all permissions
permissions := model.AllPermissions()
createdPermissions := make([]model.Permission, 0)
for _, pName := range permissions {
perm, err := s.repo.FindPermissionByName(ctx, pName)
if err != nil { // Assuming error means not found
perm = &model.Permission{Name: pName}
if err := s.repo.CreatePermission(ctx, perm); err != nil {
return err
}
}
createdPermissions = append(createdPermissions, *perm)
}
// Create Super Admin role with all permissions
superAdminRole, err := s.repo.FindRoleByName(ctx, "Super Admin")
if err != nil {
superAdminRole = &model.Role{Name: "Super Admin"}
if err := s.repo.CreateRole(ctx, superAdminRole); err != nil {
return err
}
}
if err := s.repo.AssignPermissionsToRole(ctx, superAdminRole, createdPermissions); err != nil {
return err
}
// Create a default admin user if one doesn't exist
_, err = s.repo.FindUserByUsername(ctx, "admin")
if err != nil {
_, err = s.CreateUser(ctx, "admin", os.Getenv("PASSWORD"), "Super Admin") // Default password, should be changed
if err != nil {
return err
}
}
return nil
}

View File

@@ -294,7 +294,7 @@ func (s *ServerService) GetAll(ctx *fiber.Ctx, filter *model.ServerFilter) (*[]m
} else { } else {
serverInstance := instance.(*tracking.AccServerInstance) serverInstance := instance.(*tracking.AccServerInstance)
if serverInstance.State != nil { if serverInstance.State != nil {
(*server).State = *serverInstance.State server.State = serverInstance.State
} }
} }
} }
@@ -325,7 +325,7 @@ func (as *ServerService) GetById(ctx *fiber.Ctx, serverID int) (*model.Server, e
} else { } else {
serverInstance := instance.(*tracking.AccServerInstance) serverInstance := instance.(*tracking.AccServerInstance)
if (serverInstance.State != nil) { if (serverInstance.State != nil) {
(*server).State = *serverInstance.State server.State = serverInstance.State
} }
} }

View File

@@ -27,6 +27,7 @@ func InitializeServices(c *dig.Container) {
c.Provide(NewSteamService) c.Provide(NewSteamService)
c.Provide(NewWindowsService) c.Provide(NewWindowsService)
c.Provide(NewFirewallService) c.Provide(NewFirewallService)
c.Provide(NewMembershipService)
logging.Debug("Initializing service dependencies") logging.Debug("Initializing service dependencies")
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, systemConfig *SystemConfigService) { err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService, systemConfig *SystemConfigService) {

View File

@@ -3,7 +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"
"acc-server-manager/pkg/logging" "acc-server-manager/local/utl/logging"
"sync" "sync"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"

View File

@@ -18,6 +18,7 @@ import (
type RouteGroups struct { type RouteGroups struct {
Api fiber.Router Api fiber.Router
Auth fiber.Router
Server fiber.Router Server fiber.Router
Config fiber.Router Config fiber.Router
Lookup fiber.Router Lookup fiber.Router

View File

@@ -44,6 +44,9 @@ func Migrate(db *gorm.DB) {
&model.StateHistory{}, &model.StateHistory{},
&model.SteamCredentials{}, &model.SteamCredentials{},
&model.SystemConfig{}, &model.SystemConfig{},
&model.User{},
&model.Role{},
&model.Permission{},
) )
if err != nil { if err != nil {

54
local/utl/jwt/jwt.go Normal file
View File

@@ -0,0 +1,54 @@
package jwt
import (
"acc-server-manager/local/model"
"errors"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
)
// SecretKey is the secret key for signing the JWT.
// It is recommended to use a long, complex string for this.
// In a production environment, this should be loaded from a secure configuration source.
var SecretKey = []byte("your-secret-key")
// Claims represents the JWT claims.
type Claims struct {
UserID uuid.UUID `json:"user_id"`
jwt.RegisteredClaims
}
// GenerateToken generates a new JWT for a given user.
func GenerateToken(user *model.User) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &Claims{
UserID: user.ID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(SecretKey)
}
// ValidateToken validates a JWT and returns the claims if the token is valid.
func ValidateToken(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return SecretKey, nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}