Skip to content

Commit 3644a37

Browse files
committed
feat(api): Remove GameStatus resource
Replace GameStatus resource with new combined Game model and resource that takes data from GameServer informer and database
1 parent e2566aa commit 3644a37

10 files changed

Lines changed: 274 additions & 202 deletions

File tree

‎api/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.env
22
kubeconfig.yml
33
*.errors
4+
.vscode

‎api/cmd/migrations/20210613042045_init.up.sql‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS mc_accounts_mc_id_key ON mc_accounts (mc_id) W
4949
--gopg:split
5050

5151
CREATE TABLE IF NOT EXISTS games (
52-
id uuid PRIMARY KEY,
52+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
5353
user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE,
5454
name varchar(60) NOT NULL,
5555
motd varchar(59),

‎api/controllers/api/v1/game.go‎

Lines changed: 25 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -8,97 +8,51 @@ import (
88
"github.com/gin-gonic/gin"
99
"github.com/go-playground/validator/v10"
1010
"github.com/google/uuid"
11-
"gorm.io/gorm"
12-
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
1311

1412
v1Err "agones-minecraft/errors/v1"
1513
"agones-minecraft/middleware/session"
1614
gamev1Model "agones-minecraft/models/v1/game"
1715
apiErr "agones-minecraft/resources/api/v1/errors"
1816
gamev1Resource "agones-minecraft/resources/api/v1/game"
1917
gamev1Service "agones-minecraft/services/api/v1/game"
20-
"agones-minecraft/services/k8s/agones"
2118
)
2219

2320
var (
24-
ErrGameNotFound error = errors.New("game server not found")
21+
ErrGameNotFound error = errors.New("game server not found")
22+
ErrMissingRequestBody error = errors.New("missing request body")
2523
)
2624

2725
func ListGamesForUser(c *gin.Context) {
2826
v, _ := c.Get(session.SessionUserIDKey)
2927
userId := v.(uuid.UUID)
3028

31-
var games []*gamev1Model.Game
29+
games := []*gamev1Resource.Game{}
3230

3331
if err := gamev1Service.ListGamesForUser(&games, userId); err != nil {
3432
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrListingGames))
3533
return
3634
}
3735

38-
gameServers := []*gamev1Resource.Game{}
39-
40-
for _, game := range games {
41-
gameServers = append(gameServers, &gamev1Resource.Game{
42-
ID: game.ID,
43-
Name: game.Name,
44-
Address: game.Address,
45-
Edition: game.Edition,
46-
State: game.State,
47-
CreatedAt: game.CreatedAt,
48-
})
49-
}
50-
51-
c.JSON(http.StatusOK, gameServers)
36+
c.JSON(http.StatusOK, games)
5237
}
5338

5439
func GetGame(c *gin.Context) {
55-
name := c.Param("name")
56-
gameServer, err := agones.Client().Get(name)
57-
if err != nil {
58-
if k8sErrors.IsNotFound(err) {
59-
c.Error(apiErr.NewNotFoundError(ErrGameNotFound, v1Err.ErrGameNotFound))
60-
} else {
61-
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrRetrievingGameServer))
62-
}
63-
return
64-
}
65-
c.JSON(http.StatusOK, gameServer)
66-
}
40+
v, _ := c.Get(session.SessionUserIDKey)
41+
userId := v.(uuid.UUID)
6742

68-
func GetGameState(c *gin.Context) {
6943
name := c.Param("name")
7044

71-
var game gamev1Resource.GameStatus
72-
73-
if err := agones.GetGameStatusByName(&game, name); err != nil {
74-
if k8sErrors.IsNotFound(err) {
75-
if err := gamev1Service.GetGameStatusByName(&game, name); err != nil {
76-
if err == gorm.ErrRecordNotFound {
77-
c.Error(apiErr.NewNotFoundError(ErrGameNotFound, v1Err.ErrGameNotFound))
78-
} else {
79-
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrRetrievingGameServerFromDB))
80-
}
81-
return
82-
}
45+
var game gamev1Resource.Game
46+
47+
if err := gamev1Service.GetGameByNameAndUserId(&game, name, userId); err != nil {
48+
if err == gamev1Service.ErrGameServerNotFound {
49+
c.Error(apiErr.NewNotFoundError(ErrGameNotFound, v1Err.ErrGameNotFound))
8350
} else {
8451
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrRetrievingGameServer))
85-
return
86-
}
87-
} else {
88-
v, ok := c.Get(session.SessionUserIDKey)
89-
if ok {
90-
userId := v.(uuid.UUID).String()
91-
if userId == game.UserID.String() {
92-
c.JSON(http.StatusOK, game)
93-
return
94-
}
9552
}
53+
return
9654
}
9755

98-
game.Address = nil
99-
game.Hostname = nil
100-
game.Port = nil
101-
10256
c.JSON(http.StatusOK, game)
10357
}
10458

@@ -107,25 +61,21 @@ func CreateJava(c *gin.Context) {
10761
userId := v.(uuid.UUID)
10862

10963
var body gamev1Resource.CreateGameBody
110-
if err := c.ShouldBindJSON(&body); err != nil && err != io.EOF {
64+
if err := c.ShouldBindJSON(&body); err != nil {
11165
var verrs validator.ValidationErrors
11266
if errors.As(err, &verrs) {
11367
c.Errors = append(c.Errors, apiErr.NewValidationError(verrs, v1Err.ErrCreateGameServerValidation)...)
68+
} else if err == io.EOF {
69+
c.Error(apiErr.NewBadRequestError(ErrMissingRequestBody, v1Err.ErrMissingRequestBody))
11470
} else {
11571
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrMalformedJSON))
11672
}
11773
return
11874
}
11975

120-
game := gamev1Model.Game{
121-
Address: *body.Address,
122-
UserID: userId,
123-
Edition: gamev1Model.JavaEdition,
124-
}
125-
gs := agones.NewJavaServer()
126-
agones.SetUserId(gs, userId) // Set userId label
76+
var game gamev1Resource.Game
12777

128-
if err := gamev1Service.CreateGame(&game, gs); err != nil {
78+
if err := gamev1Service.CreateGame(&game, gamev1Model.JavaEdition, body, userId); err != nil {
12979
if err == gamev1Service.ErrSubdomainTaken {
13080
c.Error(apiErr.NewBadRequestError(err, v1Err.ErrSubdomainTaken))
13181
} else {
@@ -134,61 +84,38 @@ func CreateJava(c *gin.Context) {
13484
return
13585
}
13686

137-
createdGame := gamev1Resource.Game{
138-
ID: game.ID,
139-
UserID: game.UserID,
140-
Name: game.Name,
141-
Address: agones.GetHostname(gs),
142-
Edition: game.Edition,
143-
State: game.State,
144-
CreatedAt: game.CreatedAt,
145-
}
146-
147-
c.JSON(http.StatusCreated, createdGame)
87+
c.JSON(http.StatusCreated, game)
14888
}
14989

15090
func CreateBedrock(c *gin.Context) {
15191
v, _ := c.Get(session.SessionUserIDKey)
15292
userId := v.(uuid.UUID)
15393

15494
var body gamev1Resource.CreateGameBody
155-
if err := c.ShouldBindJSON(&body); err != nil && err != io.EOF {
95+
if err := c.ShouldBindJSON(&body); err != nil {
15696
var verrs validator.ValidationErrors
15797
if errors.As(err, &verrs) {
15898
c.Errors = append(c.Errors, apiErr.NewValidationError(verrs, v1Err.ErrCreateGameServerValidation)...)
99+
} else if err == io.EOF {
100+
c.Error(apiErr.NewBadRequestError(ErrMissingRequestBody, v1Err.ErrMissingRequestBody))
159101
} else {
160102
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrMalformedJSON))
161103
}
162104
return
163105
}
164106

165-
game := gamev1Model.Game{
166-
Address: *body.Address,
167-
UserID: userId,
168-
Edition: gamev1Model.BedrockEdition,
169-
}
170-
gs := agones.NewBedrockServer()
171-
agones.SetUserId(gs, userId) // Set userId label
107+
var game gamev1Resource.Game
172108

173-
if err := gamev1Service.CreateGame(&game, gs); err != nil {
109+
if err := gamev1Service.CreateGame(&game, gamev1Model.BedrockEdition, body, userId); err != nil {
174110
if err == gamev1Service.ErrSubdomainTaken {
175111
c.Error(apiErr.NewBadRequestError(err, v1Err.ErrSubdomainTaken))
176112
} else {
177113
c.Error(apiErr.NewInternalServerError(err, v1Err.ErrCreatingGame))
178114
}
179115
return
180116
}
181-
createdGame := gamev1Resource.Game{
182-
ID: game.ID,
183-
UserID: game.UserID,
184-
Name: game.Name,
185-
Address: agones.GetHostname(gs),
186-
Edition: game.Edition,
187-
State: game.State,
188-
CreatedAt: game.CreatedAt,
189-
}
190117

191-
c.JSON(http.StatusCreated, createdGame)
118+
c.JSON(http.StatusCreated, game)
192119
}
193120

194121
func DeleteGame(c *gin.Context) {
@@ -197,9 +124,7 @@ func DeleteGame(c *gin.Context) {
197124

198125
name := c.Param("name")
199126

200-
var game gamev1Model.Game
201-
202-
if err := gamev1Service.DeleteGame(&game, userId, name); err != nil {
127+
if err := gamev1Service.DeleteGame(userId, name); err != nil {
203128
if err == gamev1Service.ErrGameServerNotFound {
204129
c.Error(apiErr.NewNotFoundError(err, v1Err.ErrGameNotFound))
205130
} else {

‎api/errors/v1/errors.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const (
77

88
ErrMalformedJSON ErrorID = "0001"
99

10+
ErrMissingRequestBody ErrorID = "0002"
11+
1012
// random bytes read io err
1113
ErrNewState ErrorID = "b7b3"
1214
// state cookie encryption or encoding err

‎api/models/v1/game/game.go‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package game
22

33
import (
4-
"agones-minecraft/models/v1/model"
4+
"fmt"
55

66
"github.com/google/uuid"
7+
8+
"agones-minecraft/models/v1/model"
79
)
810

911
type Edition string
@@ -25,5 +27,9 @@ type Game struct {
2527
Slots int `pg:"default:10"`
2628
Address string `pg:"type:varchar(63),notnull"`
2729
Edition Edition `pg:"type:varchar(25),notnull"`
28-
State GameState `pg:"type:carchar(25),default:off,notnull"`
30+
State GameState `pg:"type:varchar(25),default:off,notnull"`
31+
}
32+
33+
func (g *Game) GetResourceName() string {
34+
return fmt.Sprintf("%s.%s", g.UserID.String(), g.Name)
2935
}

‎api/resources/api/v1/game/game.go‎

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package game
33
import (
44
"time"
55

6+
agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
67
"github.com/google/uuid"
78

89
gamev1Model "agones-minecraft/models/v1/game"
10+
"agones-minecraft/services/k8s/agones"
911
)
1012

1113
type Status string
@@ -18,26 +20,39 @@ const (
1820
)
1921

2022
type CreateGameBody struct {
21-
Address *string `json:"customSubdomain" binding:"omitempty,hostname_rfc1123"`
23+
Name string `json:"name" binding:"required,min=3,max=25"`
24+
Subdomain string `json:"subdomain" binding:"required,hostname_rfc1123"`
2225
}
2326

2427
type Game struct {
25-
ID uuid.UUID `json:"id"`
26-
UserID uuid.UUID `json:"userId"`
27-
Name string `json:"name"`
28-
Address string `json:"address"`
29-
Edition gamev1Model.Edition `json:"edition"`
30-
State gamev1Model.GameState `json:"state"`
31-
CreatedAt time.Time `json:"createdAt"`
28+
ID uuid.UUID `json:"id"`
29+
UserID uuid.UUID `json:"-"`
30+
Name string `json:"name"`
31+
Edition gamev1Model.Edition `json:"edition"`
32+
State gamev1Model.GameState `json:"state,omitempty"`
33+
Status *agonesv1.GameServerState `json:"status"`
34+
Address string `json:"address"`
35+
Port *int32 `json:"port,omitempty"`
36+
CreatedAt time.Time `json:"createdAt"`
3237
}
3338

34-
type GameStatus struct {
35-
ID uuid.UUID `json:"id"`
36-
UserID uuid.UUID `json:"-"`
37-
Name string `json:"name"`
38-
Status Status `json:"status"`
39-
Edition gamev1Model.Edition `json:"edition"`
40-
Hostname *string `json:"hostname"`
41-
Address *string `json:"address"`
42-
Port *int32 `json:"port"`
39+
// Merge fields of a non-nil game model and a non-nil Agones GameServer resource
40+
// into a game api resource
41+
func (game *Game) MergeGame(gameModel *gamev1Model.Game, gs *agonesv1.GameServer) {
42+
if gameModel != nil {
43+
game.ID = gameModel.ID
44+
game.Name = gameModel.Name
45+
game.UserID = gameModel.UserID
46+
game.Address = gameModel.Address
47+
game.Edition = gameModel.Edition
48+
game.State = gameModel.State
49+
game.CreatedAt = gameModel.CreatedAt
50+
}
51+
52+
if gs != nil {
53+
game.Status = agones.GetStatus(gs)
54+
if game.Edition == gamev1Model.BedrockEdition {
55+
game.Port = agones.GetPort(gs)
56+
}
57+
}
4358
}

‎api/routers/routers.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@ func AddV1Router(r *gin.Engine) {
8585

8686
game := v1.Group("/game")
8787
{
88-
game.GET("", session.Authorizer(), v1Controllers.ListGamesForUser)
89-
game.GET("/:name/status", session.Authenticator(), v1Controllers.GetGameState)
88+
game.Use(session.Authorizer())
89+
game.GET("/list", v1Controllers.ListGamesForUser)
9090
game.GET("/:name", v1Controllers.GetGame)
9191

92-
game.Use(session.Authorizer())
9392
game.POST("/java", v1Controllers.CreateJava)
9493
game.POST("/bedrock", v1Controllers.CreateBedrock)
94+
9595
game.DELETE("/:name", v1Controllers.DeleteGame)
9696
}
9797
}

0 commit comments

Comments
 (0)