From ef1577a212d1b5c6f908a59b943a512d33d312fe Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 8 Aug 2024 20:00:06 +0900 Subject: feat(backend/worker): enable `revive` in `golangci-lint` --- backend/.golangci.yml | 1 + backend/admin/handler.go | 265 ++++++++++++++++++++++++++++++++ backend/admin/handlers.go | 268 --------------------------------- backend/admin/renderer.go | 2 +- backend/api/handler.go | 147 ++++++++++++++++++ backend/api/handler_wrapper.go | 4 +- backend/api/handlers.go | 148 ------------------ backend/auth/jwt.go | 2 +- backend/game/http.go | 12 +- backend/game/hub.go | 24 +-- backend/game/ws.go | 1 + backend/gen/api/handler_wrapper_gen.go | 14 +- backend/main.go | 2 +- 13 files changed, 444 insertions(+), 446 deletions(-) create mode 100644 backend/admin/handler.go delete mode 100644 backend/admin/handlers.go create mode 100644 backend/api/handler.go delete mode 100644 backend/api/handlers.go (limited to 'backend') diff --git a/backend/.golangci.yml b/backend/.golangci.yml index a6494c0..d203223 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -2,4 +2,5 @@ linters: enable: - gofmt - goimports + - revive - stylecheck diff --git a/backend/admin/handler.go b/backend/admin/handler.go new file mode 100644 index 0000000..111476b --- /dev/null +++ b/backend/admin/handler.go @@ -0,0 +1,265 @@ +package admin + +import ( + "errors" + "net/http" + "strconv" + "time" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" + + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" +) + +var jst = time.FixedZone("Asia/Tokyo", 9*60*60) + +type Handler struct { + q *db.Queries + hubs GameHubsInterface +} + +type GameHubsInterface interface { + StartGame(gameID int) error +} + +func NewHandler(q *db.Queries, hubs GameHubsInterface) *Handler { + return &Handler{ + q: q, + hubs: hubs, + } +} + +func newAdminMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + jwt, err := c.Cookie("albatross_token") + if err != nil { + return c.Redirect(http.StatusSeeOther, "/login") + } + claims, err := auth.ParseJWT(jwt.Value) + if err != nil { + return c.Redirect(http.StatusSeeOther, "/login") + } + if !claims.IsAdmin { + return echo.NewHTTPError(http.StatusForbidden) + } + return next(c) + } + } +} + +func (h *Handler) RegisterHandlers(g *echo.Group) { + g.Use(newAssetsMiddleware()) + g.Use(newAdminMiddleware()) + + g.GET("/dashboard", h.getDashboard) + g.GET("/users", h.getUsers) + g.GET("/users/:userID", h.getUserEdit) + g.GET("/games", h.getGames) + g.GET("/games/:gameID", h.getGameEdit) + g.POST("/games/:gameID", h.postGameEdit) +} + +func (h *Handler) getDashboard(c echo.Context) error { + return c.Render(http.StatusOK, "dashboard", echo.Map{ + "Title": "Dashboard", + }) +} + +func (h *Handler) getUsers(c echo.Context) error { + rows, err := h.q.ListUsers(c.Request().Context()) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + users := make([]echo.Map, len(rows)) + for i, u := range rows { + users[i] = echo.Map{ + "UserID": u.UserID, + "Username": u.Username, + "DisplayName": u.DisplayName, + "IconPath": u.IconPath, + "IsAdmin": u.IsAdmin, + } + } + + return c.Render(http.StatusOK, "users", echo.Map{ + "Title": "Users", + "Users": users, + }) +} + +func (h *Handler) getUserEdit(c echo.Context) error { + userID, err := strconv.Atoi(c.Param("userID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid user id") + } + row, err := h.q.GetUserByID(c.Request().Context(), int32(userID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.Render(http.StatusOK, "user_edit", echo.Map{ + "Title": "User Edit", + "User": echo.Map{ + "UserID": row.UserID, + "Username": row.Username, + "DisplayName": row.DisplayName, + "IconPath": row.IconPath, + "IsAdmin": row.IsAdmin, + }, + }) +} + +func (h *Handler) getGames(c echo.Context) error { + rows, err := h.q.ListGames(c.Request().Context()) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + games := make([]echo.Map, len(rows)) + for i, g := range rows { + var startedAt string + if !g.StartedAt.Valid { + startedAt = g.StartedAt.Time.In(jst).Format("2006-01-02T15:04") + } + games[i] = echo.Map{ + "GameID": g.GameID, + "GameType": g.GameType, + "State": g.State, + "DisplayName": g.DisplayName, + "DurationSeconds": g.DurationSeconds, + "StartedAt": startedAt, + "ProblemID": g.ProblemID, + } + } + + return c.Render(http.StatusOK, "games", echo.Map{ + "Title": "Games", + "Games": games, + }) +} + +func (h *Handler) getGameEdit(c echo.Context) error { + gameID, err := strconv.Atoi(c.Param("gameID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid game id") + } + row, err := h.q.GetGameByID(c.Request().Context(), int32(gameID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + var startedAt string + if !row.StartedAt.Valid { + startedAt = row.StartedAt.Time.In(jst).Format("2006-01-02T15:04") + } + + return c.Render(http.StatusOK, "game_edit", echo.Map{ + "Title": "Game Edit", + "Game": echo.Map{ + "GameID": row.GameID, + "GameType": row.GameType, + "State": row.State, + "DisplayName": row.DisplayName, + "DurationSeconds": row.DurationSeconds, + "StartedAt": startedAt, + "ProblemID": row.ProblemID, + }, + }) +} + +func (h *Handler) postGameEdit(c echo.Context) error { + gameID, err := strconv.Atoi(c.Param("gameID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid game id") + } + row, err := h.q.GetGameByID(c.Request().Context(), int32(gameID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + gameType := c.FormValue("game_type") + state := c.FormValue("state") + displayName := c.FormValue("display_name") + durationSeconds, err := strconv.Atoi(c.FormValue("duration_seconds")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid duration_seconds") + } + var problemID *int + { + problemIDRaw := c.FormValue("problem_id") + if problemIDRaw != "" { + problemIDInt, err := strconv.Atoi(problemIDRaw) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem_id") + } + problemID = &problemIDInt + } + } + var startedAt *time.Time + { + startedAtRaw := c.FormValue("started_at") + if startedAtRaw != "" { + startedAtTime, err := time.Parse("2006-01-02T15:04", startedAtRaw) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid started_at") + } + startedAt = &startedAtTime + } + } + + var changedStartedAt pgtype.Timestamp + if startedAt == nil { + changedStartedAt = pgtype.Timestamp{ + Valid: false, + } + } else { + changedStartedAt = pgtype.Timestamp{ + Time: *startedAt, + Valid: true, + } + } + var changedProblemID *int32 + if problemID == nil { + changedProblemID = nil + } else { + changedProblemID = new(int32) + *changedProblemID = int32(*problemID) + } + + { + // TODO: + if state != row.State && state == "prepare" { + err := h.hubs.StartGame(int(gameID)) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + } + } + + err = h.q.UpdateGame(c.Request().Context(), db.UpdateGameParams{ + GameID: int32(gameID), + GameType: gameType, + State: state, + DisplayName: displayName, + DurationSeconds: int32(durationSeconds), + StartedAt: changedStartedAt, + ProblemID: changedProblemID, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.NoContent(http.StatusNoContent) +} diff --git a/backend/admin/handlers.go b/backend/admin/handlers.go deleted file mode 100644 index 2a678d3..0000000 --- a/backend/admin/handlers.go +++ /dev/null @@ -1,268 +0,0 @@ -package admin - -import ( - "errors" - "net/http" - "strconv" - "time" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgtype" - "github.com/labstack/echo/v4" - - "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" - "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" -) - -var jst = time.FixedZone("Asia/Tokyo", 9*60*60) - -type AdminHandler struct { - q *db.Queries - hubs GameHubsInterface -} - -type GameHubsInterface interface { - StartGame(gameID int) error -} - -func NewAdminHandler(q *db.Queries, hubs GameHubsInterface) *AdminHandler { - return &AdminHandler{ - q: q, - hubs: hubs, - } -} - -func newAdminMiddleware() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - jwt, err := c.Cookie("albatross_token") - if err != nil { - return c.Redirect(http.StatusSeeOther, "/login") - } - claims, err := auth.ParseJWT(jwt.Value) - if err != nil { - return c.Redirect(http.StatusSeeOther, "/login") - } - if !claims.IsAdmin { - return echo.NewHTTPError(http.StatusForbidden) - } - return next(c) - } - } -} - -func (h *AdminHandler) RegisterHandlers(g *echo.Group) { - g.Use(newAssetsMiddleware()) - g.Use(newAdminMiddleware()) - - g.GET("/dashboard", h.getDashboard) - g.GET("/users", h.getUsers) - g.GET("/users/:userID", h.getUserEdit) - g.GET("/games", h.getGames) - g.GET("/games/:gameID", h.getGameEdit) - g.POST("/games/:gameID", h.postGameEdit) -} - -func (h *AdminHandler) getDashboard(c echo.Context) error { - return c.Render(http.StatusOK, "dashboard", echo.Map{ - "Title": "Dashboard", - }) -} - -func (h *AdminHandler) getUsers(c echo.Context) error { - rows, err := h.q.ListUsers(c.Request().Context()) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - users := make([]echo.Map, len(rows)) - for i, u := range rows { - users[i] = echo.Map{ - "UserID": u.UserID, - "Username": u.Username, - "DisplayName": u.DisplayName, - "IconPath": u.IconPath, - "IsAdmin": u.IsAdmin, - } - } - - return c.Render(http.StatusOK, "users", echo.Map{ - "Title": "Users", - "Users": users, - }) -} - -func (h *AdminHandler) getUserEdit(c echo.Context) error { - userID, err := strconv.Atoi(c.Param("userID")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid user id") - } - row, err := h.q.GetUserByID(c.Request().Context(), int32(userID)) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return echo.NewHTTPError(http.StatusNotFound) - } else { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - - return c.Render(http.StatusOK, "user_edit", echo.Map{ - "Title": "User Edit", - "User": echo.Map{ - "UserID": row.UserID, - "Username": row.Username, - "DisplayName": row.DisplayName, - "IconPath": row.IconPath, - "IsAdmin": row.IsAdmin, - }, - }) -} - -func (h *AdminHandler) getGames(c echo.Context) error { - rows, err := h.q.ListGames(c.Request().Context()) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - games := make([]echo.Map, len(rows)) - for i, g := range rows { - var startedAt string - if !g.StartedAt.Valid { - startedAt = g.StartedAt.Time.In(jst).Format("2006-01-02T15:04") - } - games[i] = echo.Map{ - "GameID": g.GameID, - "GameType": g.GameType, - "State": g.State, - "DisplayName": g.DisplayName, - "DurationSeconds": g.DurationSeconds, - "StartedAt": startedAt, - "ProblemID": g.ProblemID, - } - } - - return c.Render(http.StatusOK, "games", echo.Map{ - "Title": "Games", - "Games": games, - }) -} - -func (h *AdminHandler) getGameEdit(c echo.Context) error { - gameID, err := strconv.Atoi(c.Param("gameID")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid game id") - } - row, err := h.q.GetGameByID(c.Request().Context(), int32(gameID)) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return echo.NewHTTPError(http.StatusNotFound) - } else { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - - var startedAt string - if !row.StartedAt.Valid { - startedAt = row.StartedAt.Time.In(jst).Format("2006-01-02T15:04") - } - - return c.Render(http.StatusOK, "game_edit", echo.Map{ - "Title": "Game Edit", - "Game": echo.Map{ - "GameID": row.GameID, - "GameType": row.GameType, - "State": row.State, - "DisplayName": row.DisplayName, - "DurationSeconds": row.DurationSeconds, - "StartedAt": startedAt, - "ProblemID": row.ProblemID, - }, - }) -} - -func (h *AdminHandler) postGameEdit(c echo.Context) error { - gameID, err := strconv.Atoi(c.Param("gameID")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid game id") - } - row, err := h.q.GetGameByID(c.Request().Context(), int32(gameID)) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return echo.NewHTTPError(http.StatusNotFound) - } else { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - - gameType := c.FormValue("game_type") - state := c.FormValue("state") - displayName := c.FormValue("display_name") - durationSeconds, err := strconv.Atoi(c.FormValue("duration_seconds")) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid duration_seconds") - } - var problemID *int - { - problemIDRaw := c.FormValue("problem_id") - if problemIDRaw != "" { - problemIDInt, err := strconv.Atoi(problemIDRaw) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem_id") - } - problemID = &problemIDInt - } - } - var startedAt *time.Time - { - startedAtRaw := c.FormValue("started_at") - if startedAtRaw != "" { - startedAtTime, err := time.Parse("2006-01-02T15:04", startedAtRaw) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid started_at") - } - startedAt = &startedAtTime - } - } - - var changedStartedAt pgtype.Timestamp - if startedAt == nil { - changedStartedAt = pgtype.Timestamp{ - Valid: false, - } - } else { - changedStartedAt = pgtype.Timestamp{ - Time: *startedAt, - Valid: true, - } - } - var changedProblemID *int32 - if problemID == nil { - changedProblemID = nil - } else { - changedProblemID = new(int32) - *changedProblemID = int32(*problemID) - } - - { - // TODO: - if state != row.State && state == "prepare" { - err := h.hubs.StartGame(int(gameID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - } - - err = h.q.UpdateGame(c.Request().Context(), db.UpdateGameParams{ - GameID: int32(gameID), - GameType: gameType, - State: state, - DisplayName: displayName, - DurationSeconds: int32(durationSeconds), - StartedAt: changedStartedAt, - ProblemID: changedProblemID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - - return c.NoContent(http.StatusNoContent) -} diff --git a/backend/admin/renderer.go b/backend/admin/renderer.go index 468677f..d38c701 100644 --- a/backend/admin/renderer.go +++ b/backend/admin/renderer.go @@ -27,7 +27,7 @@ func NewRenderer() *Renderer { } } -func (r *Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { +func (r *Renderer) Render(w io.Writer, name string, data interface{}, _ echo.Context) error { tmpl, ok := r.templates[name] if !ok { t, err := template.ParseFS(templatesFS, "templates/base.html", "templates/"+name+".html") diff --git a/backend/api/handler.go b/backend/api/handler.go new file mode 100644 index 0000000..57d7464 --- /dev/null +++ b/backend/api/handler.go @@ -0,0 +1,147 @@ +package api + +import ( + "context" + "errors" + "net/http" + + "github.com/jackc/pgx/v5" + "github.com/labstack/echo/v4" + + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" +) + +type Handler struct { + q *db.Queries + hubs GameHubsInterface +} + +type GameHubsInterface interface { + StartGame(gameID int) error +} + +func (h *Handler) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) { + username := request.Body.Username + password := request.Body.Password + userID, err := auth.Login(ctx, h.q, username, password) + if err != nil { + return PostLogin401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Invalid username or password", + }, + }, nil + } + + user, err := h.q.GetUserByID(ctx, int32(userID)) + if err != nil { + return PostLogin401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Invalid username or password", + }, + }, nil + } + + jwt, err := auth.NewJWT(&user) + if err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return PostLogin200JSONResponse{ + Token: jwt, + }, nil +} + +func (h *Handler) GetToken(_ context.Context, _ GetTokenRequestObject, user *auth.JWTClaims) (GetTokenResponseObject, error) { + newToken, err := auth.NewShortLivedJWT(user) + if err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + return GetToken200JSONResponse{ + Token: newToken, + }, nil +} + +func (h *Handler) GetGames(ctx context.Context, _ GetGamesRequestObject, user *auth.JWTClaims) (GetGamesResponseObject, error) { + gameRows, err := h.q.ListGamesForPlayer(ctx, int32(user.UserID)) + if err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + games := make([]Game, len(gameRows)) + for i, row := range gameRows { + var startedAt *int + if row.StartedAt.Valid { + startedAtTimestamp := int(row.StartedAt.Time.Unix()) + startedAt = &startedAtTimestamp + } + var problem *Problem + if row.ProblemID != nil { + if row.Title == nil || row.Description == nil { + panic("inconsistent data") + } + problem = &Problem{ + ProblemID: int(*row.ProblemID), + Title: *row.Title, + Description: *row.Description, + } + } + games[i] = Game{ + GameID: int(row.GameID), + GameType: GameGameType(row.GameType), + State: GameState(row.State), + DisplayName: row.DisplayName, + DurationSeconds: int(row.DurationSeconds), + StartedAt: startedAt, + Problem: problem, + } + } + return GetGames200JSONResponse{ + Games: games, + }, nil +} + +func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, user *auth.JWTClaims) (GetGameResponseObject, error) { + // TODO: check user permission + gameID := request.GameID + row, err := h.q.GetGameByID(ctx, int32(gameID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return GetGame404JSONResponse{ + NotFoundJSONResponse: NotFoundJSONResponse{ + Message: "Game not found", + }, + }, nil + } + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + var startedAt *int + if row.StartedAt.Valid { + startedAtTimestamp := int(row.StartedAt.Time.Unix()) + startedAt = &startedAtTimestamp + } + var problem *Problem + if row.ProblemID != nil { + if row.Title == nil || row.Description == nil { + panic("inconsistent data") + } + if user.IsAdmin || (GameState(row.State) != Closed && GameState(row.State) != WaitingEntries) { + problem = &Problem{ + ProblemID: int(*row.ProblemID), + Title: *row.Title, + Description: *row.Description, + } + } + } + game := Game{ + GameID: int(row.GameID), + GameType: GameGameType(row.GameType), + State: GameState(row.State), + DisplayName: row.DisplayName, + DurationSeconds: int(row.DurationSeconds), + StartedAt: startedAt, + Problem: problem, + } + return GetGame200JSONResponse{ + Game: game, + }, nil +} diff --git a/backend/api/handler_wrapper.go b/backend/api/handler_wrapper.go index 748af66..69b9baa 100644 --- a/backend/api/handler_wrapper.go +++ b/backend/api/handler_wrapper.go @@ -14,12 +14,12 @@ import ( var _ StrictServerInterface = (*ApiHandlerWrapper)(nil) type ApiHandlerWrapper struct { - innerHandler APIHandler + innerHandler Handler } func NewHandler(queries *db.Queries, hubs GameHubsInterface) *ApiHandlerWrapper { return &ApiHandlerWrapper{ - innerHandler: APIHandler{ + innerHandler: Handler{ q: queries, hubs: hubs, }, diff --git a/backend/api/handlers.go b/backend/api/handlers.go deleted file mode 100644 index 229246f..0000000 --- a/backend/api/handlers.go +++ /dev/null @@ -1,148 +0,0 @@ -package api - -import ( - "context" - "errors" - "net/http" - - "github.com/jackc/pgx/v5" - "github.com/labstack/echo/v4" - - "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" - "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" -) - -type APIHandler struct { - q *db.Queries - hubs GameHubsInterface -} - -type GameHubsInterface interface { - StartGame(gameID int) error -} - -func (h *APIHandler) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) { - username := request.Body.Username - password := request.Body.Password - userID, err := auth.Login(ctx, h.q, username, password) - if err != nil { - return PostLogin401JSONResponse{ - UnauthorizedJSONResponse: UnauthorizedJSONResponse{ - Message: "Invalid username or password", - }, - }, nil - } - - user, err := h.q.GetUserByID(ctx, int32(userID)) - if err != nil { - return PostLogin401JSONResponse{ - UnauthorizedJSONResponse: UnauthorizedJSONResponse{ - Message: "Invalid username or password", - }, - }, nil - } - - jwt, err := auth.NewJWT(&user) - if err != nil { - return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - - return PostLogin200JSONResponse{ - Token: jwt, - }, nil -} - -func (h *APIHandler) GetToken(ctx context.Context, request GetTokenRequestObject, user *auth.JWTClaims) (GetTokenResponseObject, error) { - newToken, err := auth.NewShortLivedJWT(user) - if err != nil { - return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - return GetToken200JSONResponse{ - Token: newToken, - }, nil -} - -func (h *APIHandler) GetGames(ctx context.Context, request GetGamesRequestObject, user *auth.JWTClaims) (GetGamesResponseObject, error) { - gameRows, err := h.q.ListGamesForPlayer(ctx, int32(user.UserID)) - if err != nil { - return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - games := make([]Game, len(gameRows)) - for i, row := range gameRows { - var startedAt *int - if row.StartedAt.Valid { - startedAtTimestamp := int(row.StartedAt.Time.Unix()) - startedAt = &startedAtTimestamp - } - var problem *Problem - if row.ProblemID != nil { - if row.Title == nil || row.Description == nil { - panic("inconsistent data") - } - problem = &Problem{ - ProblemID: int(*row.ProblemID), - Title: *row.Title, - Description: *row.Description, - } - } - games[i] = Game{ - GameID: int(row.GameID), - GameType: GameGameType(row.GameType), - State: GameState(row.State), - DisplayName: row.DisplayName, - DurationSeconds: int(row.DurationSeconds), - StartedAt: startedAt, - Problem: problem, - } - } - return GetGames200JSONResponse{ - Games: games, - }, nil -} - -func (h *APIHandler) GetGame(ctx context.Context, request GetGameRequestObject, user *auth.JWTClaims) (GetGameResponseObject, error) { - // TODO: check user permission - gameID := request.GameID - row, err := h.q.GetGameByID(ctx, int32(gameID)) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return GetGame404JSONResponse{ - NotFoundJSONResponse: NotFoundJSONResponse{ - Message: "Game not found", - }, - }, nil - } else { - return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - var startedAt *int - if row.StartedAt.Valid { - startedAtTimestamp := int(row.StartedAt.Time.Unix()) - startedAt = &startedAtTimestamp - } - var problem *Problem - if row.ProblemID != nil { - if row.Title == nil || row.Description == nil { - panic("inconsistent data") - } - if user.IsAdmin || (GameState(row.State) != Closed && GameState(row.State) != WaitingEntries) { - problem = &Problem{ - ProblemID: int(*row.ProblemID), - Title: *row.Title, - Description: *row.Description, - } - } - } - game := Game{ - GameID: int(row.GameID), - GameType: GameGameType(row.GameType), - State: GameState(row.State), - DisplayName: row.DisplayName, - DurationSeconds: int(row.DurationSeconds), - StartedAt: startedAt, - Problem: problem, - } - return GetGame200JSONResponse{ - Game: game, - }, nil -} diff --git a/backend/auth/jwt.go b/backend/auth/jwt.go index 966cc5f..e1852da 100644 --- a/backend/auth/jwt.go +++ b/backend/auth/jwt.go @@ -50,7 +50,7 @@ func NewShortLivedJWT(claims *JWTClaims) (string, error) { func ParseJWT(token string) (*JWTClaims, error) { claims := new(JWTClaims) - t, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) { + t, err := jwt.ParseWithClaims(token, claims, func(*jwt.Token) (interface{}, error) { return []byte("TODO"), nil }) if err != nil { diff --git a/backend/game/http.go b/backend/game/http.go index 865c724..d513593 100644 --- a/backend/game/http.go +++ b/backend/game/http.go @@ -9,17 +9,17 @@ import ( "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" ) -type sockHandler struct { - hubs *GameHubs +type SockHandler struct { + hubs *Hubs } -func newSockHandler(hubs *GameHubs) *sockHandler { - return &sockHandler{ +func newSockHandler(hubs *Hubs) *SockHandler { + return &SockHandler{ hubs: hubs, } } -func (h *sockHandler) HandleSockGolfPlay(c echo.Context) error { +func (h *SockHandler) HandleSockGolfPlay(c echo.Context) error { jwt := c.QueryParam("token") claims, err := auth.ParseJWT(jwt) if err != nil { @@ -38,7 +38,7 @@ func (h *sockHandler) HandleSockGolfPlay(c echo.Context) error { return servePlayerWs(hub, c.Response(), c.Request(), claims.UserID) } -func (h *sockHandler) HandleSockGolfWatch(c echo.Context) error { +func (h *SockHandler) HandleSockGolfWatch(c echo.Context) error { jwt := c.QueryParam("token") claims, err := auth.ParseJWT(jwt) if err != nil { diff --git a/backend/game/hub.go b/backend/game/hub.go index 11a466b..dc56f03 100644 --- a/backend/game/hub.go +++ b/backend/game/hub.go @@ -544,15 +544,15 @@ func (hub *gameHub) closeWatcherClient(watcher *watcherClient) { close(watcher.s2cMessages) } -type GameHubs struct { +type Hubs struct { hubs map[int]*gameHub q *db.Queries taskQueue *taskqueue.Queue taskResults chan taskqueue.TaskResult } -func NewGameHubs(q *db.Queries, taskQueue *taskqueue.Queue, taskResults chan taskqueue.TaskResult) *GameHubs { - return &GameHubs{ +func NewGameHubs(q *db.Queries, taskQueue *taskqueue.Queue, taskResults chan taskqueue.TaskResult) *Hubs { + return &Hubs{ hubs: make(map[int]*gameHub), q: q, taskQueue: taskQueue, @@ -560,18 +560,18 @@ func NewGameHubs(q *db.Queries, taskQueue *taskqueue.Queue, taskResults chan tas } } -func (hubs *GameHubs) Close() { +func (hubs *Hubs) Close() { log.Println("closing all game hubs") for _, hub := range hubs.hubs { hub.close() } } -func (hubs *GameHubs) getHub(gameID int) *gameHub { +func (hubs *Hubs) getHub(gameID int) *gameHub { return hubs.hubs[gameID] } -func (hubs *GameHubs) RestoreFromDB(ctx context.Context) error { +func (hubs *Hubs) RestoreFromDB(ctx context.Context) error { games, err := hubs.q.ListGames(ctx) if err != nil { return err @@ -581,12 +581,12 @@ func (hubs *GameHubs) RestoreFromDB(ctx context.Context) error { if row.StartedAt.Valid { startedAt = &row.StartedAt.Time } - var problem_ *problem + var pr *problem if row.ProblemID != nil { if row.Title == nil || row.Description == nil { panic("inconsistent data") } - problem_ = &problem{ + pr = &problem{ problemID: int(*row.ProblemID), title: *row.Title, description: *row.Description, @@ -603,14 +603,14 @@ func (hubs *GameHubs) RestoreFromDB(ctx context.Context) error { state: gameState(row.State), displayName: row.DisplayName, startedAt: startedAt, - problem: problem_, + problem: pr, playerCount: len(playerRows), }, hubs.q, hubs.taskQueue) } return nil } -func (hubs *GameHubs) Run() { +func (hubs *Hubs) Run() { for _, hub := range hubs.hubs { go hub.run() go hub.processTaskResults() @@ -626,11 +626,11 @@ func (hubs *GameHubs) Run() { } } -func (hubs *GameHubs) SockHandler() *sockHandler { +func (hubs *Hubs) SockHandler() *SockHandler { return newSockHandler(hubs) } -func (hubs *GameHubs) StartGame(gameID int) error { +func (hubs *Hubs) StartGame(gameID int) error { hub := hubs.getHub(gameID) if hub == nil { return errors.New("no such game") diff --git a/backend/game/ws.go b/backend/game/ws.go index 8e219d6..0dbd0ab 100644 --- a/backend/game/ws.go +++ b/backend/game/ws.go @@ -20,6 +20,7 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { // TODO: insecure! + _ = r return true }, } diff --git a/backend/gen/api/handler_wrapper_gen.go b/backend/gen/api/handler_wrapper_gen.go index 42fc5e8..1cd9770 100644 --- a/backend/gen/api/handler_wrapper_gen.go +++ b/backend/gen/api/handler_wrapper_gen.go @@ -111,15 +111,15 @@ import ( "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" ) -var _ StrictServerInterface = (*APIHandlerWrapper)(nil) +var _ StrictServerInterface = (*HandlerWrapper)(nil) -type APIHandlerWrapper struct { - impl APIHandler +type HandlerWrapper struct { + impl Handler } -func NewHandler(queries *db.Queries, hubs GameHubsInterface) *APIHandlerWrapper { - return &APIHandlerWrapper{ - impl: APIHandler{ +func NewHandler(queries *db.Queries, hubs GameHubsInterface) *HandlerWrapper { + return &HandlerWrapper{ + impl: Handler{ q: queries, hubs: hubs, }, @@ -140,7 +140,7 @@ func parseJWTClaimsFromAuthorizationHeader(authorization string) (*auth.JWTClaim } {{ range . }} - func (h *APIHandlerWrapper) {{ .Name }}(ctx context.Context, request {{ .Name }}RequestObject) ({{ .Name }}ResponseObject, error) { + func (h *HandlerWrapper) {{ .Name }}(ctx context.Context, request {{ .Name }}RequestObject) ({{ .Name }}ResponseObject, error) { {{ if .RequiresLogin -}} user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) if err != nil { diff --git a/backend/main.go b/backend/main.go index c8a5832..575c3f8 100644 --- a/backend/main.go +++ b/backend/main.go @@ -83,7 +83,7 @@ func main() { apiHandler := api.NewHandler(queries, gameHubs) api.RegisterHandlers(apiGroup, api.NewStrictHandler(apiHandler, nil)) - adminHandler := admin.NewAdminHandler(queries, gameHubs) + adminHandler := admin.NewHandler(queries, gameHubs) adminGroup := e.Group("/admin") adminHandler.RegisterHandlers(adminGroup) -- cgit v1.2.3-70-g09d2