diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-08-01 21:35:25 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-08-01 21:35:25 +0900 |
| commit | f6443042fbec1bd394439904f3c69e23709f7e6a (patch) | |
| tree | 46872e86ed7e42962895e0bfe04064387487523f | |
| parent | 10b9be2c2a46b204f83be7d152ca62bf69e8843e (diff) | |
| parent | 94d5d89aa59b6d1e53dab280c26e3a8fcb22b7e4 (diff) | |
| download | iosdc-japan-2024-albatross-f6443042fbec1bd394439904f3c69e23709f7e6a.tar.gz iosdc-japan-2024-albatross-f6443042fbec1bd394439904f3c69e23709f7e6a.tar.zst iosdc-japan-2024-albatross-f6443042fbec1bd394439904f3c69e23709f7e6a.zip | |
Merge branch 'refactor/api'
| -rw-r--r-- | Makefile | 18 | ||||
| -rw-r--r-- | backend/Makefile | 10 | ||||
| -rw-r--r-- | backend/api/generated.go | 504 | ||||
| -rw-r--r-- | backend/api/handler_wrapper.go | 156 | ||||
| -rw-r--r-- | backend/api/handlers.go | 246 | ||||
| -rw-r--r-- | backend/gen/api_handler_wrapper_gen.go | 168 | ||||
| -rw-r--r-- | backend/gen/gen.go | 5 | ||||
| -rw-r--r-- | backend/gen/oapi-codegen.yaml (renamed from backend/oapi-codegen.yaml) | 2 | ||||
| -rw-r--r-- | backend/gen/sqlc.yaml (renamed from backend/sqlc.yaml) | 6 | ||||
| -rw-r--r-- | backend/main.go | 4 | ||||
| -rw-r--r-- | frontend/Makefile | 3 | ||||
| -rw-r--r-- | frontend/app/.server/api/client.ts | 90 | ||||
| -rw-r--r-- | frontend/app/.server/api/schema.d.ts | 669 | ||||
| -rw-r--r-- | frontend/app/.server/auth.ts | 13 | ||||
| -rw-r--r-- | frontend/app/routes/admin.games.tsx | 15 | ||||
| -rw-r--r-- | frontend/app/routes/admin.games_.$gameId.tsx | 35 | ||||
| -rw-r--r-- | frontend/app/routes/admin.users.tsx | 15 | ||||
| -rw-r--r-- | frontend/app/routes/dashboard.tsx | 18 | ||||
| -rw-r--r-- | frontend/app/routes/golf.$gameId.play.tsx | 30 | ||||
| -rw-r--r-- | frontend/app/routes/golf.$gameId.watch.tsx | 30 | ||||
| -rw-r--r-- | frontend/package.json | 1 | ||||
| -rw-r--r-- | openapi.yaml | 277 |
22 files changed, 1184 insertions, 1131 deletions
@@ -52,17 +52,7 @@ initdb: make psql-query < ./backend/schema.sql make psql-query < ./backend/fixtures/dev.sql -.PHONY: openapi -openapi: oapi-codegen openapi-typescript - -.PHONY: oapi-codegen -oapi-codegen: - cd backend; make oapi-codegen - -.PHONY: openapi-typescript -openapi-typescript: - cd frontend; make openapi-typescript - -.PHONY: sqlc -sqlc: - cd backend; make sqlc +.PHONY: gen +gen: + cd backend; make gen + cd frontend; npm run openapi-typescript diff --git a/backend/Makefile b/backend/Makefile index 3b26e00..72d3314 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -10,10 +10,6 @@ check: lint: go vet ./... -.PHONY: oapi-codegen -oapi-codegen: - go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config oapi-codegen.yaml ../openapi.yaml - -.PHONY: sqlc -sqlc: - go run github.com/sqlc-dev/sqlc/cmd/sqlc generate +.PHONY: gen +gen: + go generate ./... diff --git a/backend/api/generated.go b/backend/api/generated.go index a2fa329..a643833 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -43,17 +43,22 @@ const ( GameWatcherMessageS2CExecResultPayloadStatusSuccess GameWatcherMessageS2CExecResultPayloadStatus = "success" ) -// Defines values for PutAdminGamesGameIdJSONBodyState. +// Defines values for AdminPutGameJSONBodyState. const ( - PutAdminGamesGameIdJSONBodyStateClosed PutAdminGamesGameIdJSONBodyState = "closed" - PutAdminGamesGameIdJSONBodyStateFinished PutAdminGamesGameIdJSONBodyState = "finished" - PutAdminGamesGameIdJSONBodyStateGaming PutAdminGamesGameIdJSONBodyState = "gaming" - PutAdminGamesGameIdJSONBodyStatePrepare PutAdminGamesGameIdJSONBodyState = "prepare" - PutAdminGamesGameIdJSONBodyStateStarting PutAdminGamesGameIdJSONBodyState = "starting" - PutAdminGamesGameIdJSONBodyStateWaitingEntries PutAdminGamesGameIdJSONBodyState = "waiting_entries" - PutAdminGamesGameIdJSONBodyStateWaitingStart PutAdminGamesGameIdJSONBodyState = "waiting_start" + AdminPutGameJSONBodyStateClosed AdminPutGameJSONBodyState = "closed" + AdminPutGameJSONBodyStateFinished AdminPutGameJSONBodyState = "finished" + AdminPutGameJSONBodyStateGaming AdminPutGameJSONBodyState = "gaming" + AdminPutGameJSONBodyStatePrepare AdminPutGameJSONBodyState = "prepare" + AdminPutGameJSONBodyStateStarting AdminPutGameJSONBodyState = "starting" + AdminPutGameJSONBodyStateWaitingEntries AdminPutGameJSONBodyState = "waiting_entries" + AdminPutGameJSONBodyStateWaitingStart AdminPutGameJSONBodyState = "waiting_start" ) +// Error defines model for Error. +type Error struct { + Message string `json:"message"` +} + // Game defines model for Game. type Game struct { DisplayName string `json:"display_name"` @@ -207,47 +212,64 @@ type User struct { Username string `json:"username"` } -// GetAdminGamesParams defines parameters for GetAdminGames. -type GetAdminGamesParams struct { - Authorization string `json:"Authorization"` +// HeaderAuthorization defines model for header_authorization. +type HeaderAuthorization = string + +// PathGameId defines model for path_game_id. +type PathGameId = int + +// BadRequest defines model for BadRequest. +type BadRequest = Error + +// Forbidden defines model for Forbidden. +type Forbidden = Error + +// NotFound defines model for NotFound. +type NotFound = Error + +// Unauthorized defines model for Unauthorized. +type Unauthorized = Error + +// AdminGetGamesParams defines parameters for AdminGetGames. +type AdminGetGamesParams struct { + Authorization HeaderAuthorization `json:"Authorization"` } -// GetAdminGamesGameIdParams defines parameters for GetAdminGamesGameId. -type GetAdminGamesGameIdParams struct { - Authorization string `json:"Authorization"` +// AdminGetGameParams defines parameters for AdminGetGame. +type AdminGetGameParams struct { + Authorization HeaderAuthorization `json:"Authorization"` } -// PutAdminGamesGameIdJSONBody defines parameters for PutAdminGamesGameId. -type PutAdminGamesGameIdJSONBody struct { - DisplayName *string `json:"display_name,omitempty"` - DurationSeconds *int `json:"duration_seconds,omitempty"` - ProblemId nullable.Nullable[int] `json:"problem_id,omitempty"` - StartedAt nullable.Nullable[int] `json:"started_at,omitempty"` - State *PutAdminGamesGameIdJSONBodyState `json:"state,omitempty"` +// AdminPutGameJSONBody defines parameters for AdminPutGame. +type AdminPutGameJSONBody struct { + DisplayName *string `json:"display_name,omitempty"` + DurationSeconds *int `json:"duration_seconds,omitempty"` + ProblemId nullable.Nullable[int] `json:"problem_id,omitempty"` + StartedAt nullable.Nullable[int] `json:"started_at,omitempty"` + State *AdminPutGameJSONBodyState `json:"state,omitempty"` } -// PutAdminGamesGameIdParams defines parameters for PutAdminGamesGameId. -type PutAdminGamesGameIdParams struct { - Authorization string `json:"Authorization"` +// AdminPutGameParams defines parameters for AdminPutGame. +type AdminPutGameParams struct { + Authorization HeaderAuthorization `json:"Authorization"` } -// PutAdminGamesGameIdJSONBodyState defines parameters for PutAdminGamesGameId. -type PutAdminGamesGameIdJSONBodyState string +// AdminPutGameJSONBodyState defines parameters for AdminPutGame. +type AdminPutGameJSONBodyState string -// GetAdminUsersParams defines parameters for GetAdminUsers. -type GetAdminUsersParams struct { - Authorization string `json:"Authorization"` +// AdminGetUsersParams defines parameters for AdminGetUsers. +type AdminGetUsersParams struct { + Authorization HeaderAuthorization `json:"Authorization"` } // GetGamesParams defines parameters for GetGames. type GetGamesParams struct { - PlayerId *int `form:"player_id,omitempty" json:"player_id,omitempty"` - Authorization string `json:"Authorization"` + Authorization HeaderAuthorization `json:"Authorization"` } -// GetGamesGameIdParams defines parameters for GetGamesGameId. -type GetGamesGameIdParams struct { - Authorization string `json:"Authorization"` +// GetGameParams defines parameters for GetGame. +type GetGameParams struct { + Authorization HeaderAuthorization `json:"Authorization"` } // PostLoginJSONBody defines parameters for PostLogin. @@ -258,11 +280,11 @@ type PostLoginJSONBody struct { // GetTokenParams defines parameters for GetToken. type GetTokenParams struct { - Authorization string `json:"Authorization"` + Authorization HeaderAuthorization `json:"Authorization"` } -// PutAdminGamesGameIdJSONRequestBody defines body for PutAdminGamesGameId for application/json ContentType. -type PutAdminGamesGameIdJSONRequestBody PutAdminGamesGameIdJSONBody +// AdminPutGameJSONRequestBody defines body for AdminPutGame for application/json ContentType. +type AdminPutGameJSONRequestBody AdminPutGameJSONBody // PostLoginJSONRequestBody defines body for PostLogin for application/json ContentType. type PostLoginJSONRequestBody PostLoginJSONBody @@ -633,22 +655,22 @@ func (t *GameWatcherMessageS2C) UnmarshalJSON(b []byte) error { type ServerInterface interface { // List games // (GET /admin/games) - GetAdminGames(ctx echo.Context, params GetAdminGamesParams) error + AdminGetGames(ctx echo.Context, params AdminGetGamesParams) error // Get a game // (GET /admin/games/{game_id}) - GetAdminGamesGameId(ctx echo.Context, gameId int, params GetAdminGamesGameIdParams) error + AdminGetGame(ctx echo.Context, gameId PathGameId, params AdminGetGameParams) error // Update a game // (PUT /admin/games/{game_id}) - PutAdminGamesGameId(ctx echo.Context, gameId int, params PutAdminGamesGameIdParams) error + AdminPutGame(ctx echo.Context, gameId PathGameId, params AdminPutGameParams) error // List all users // (GET /admin/users) - GetAdminUsers(ctx echo.Context, params GetAdminUsersParams) error + AdminGetUsers(ctx echo.Context, params AdminGetUsersParams) error // List games // (GET /games) GetGames(ctx echo.Context, params GetGamesParams) error // Get a game // (GET /games/{game_id}) - GetGamesGameId(ctx echo.Context, gameId int, params GetGamesGameIdParams) error + GetGame(ctx echo.Context, gameId PathGameId, params GetGameParams) error // User login // (POST /login) PostLogin(ctx echo.Context) error @@ -662,17 +684,17 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } -// GetAdminGames converts echo context to params. -func (w *ServerInterfaceWrapper) GetAdminGames(ctx echo.Context) error { +// AdminGetGames converts echo context to params. +func (w *ServerInterfaceWrapper) AdminGetGames(ctx echo.Context) error { var err error // Parameter object where we will unmarshal all parameters from the context - var params GetAdminGamesParams + var params AdminGetGamesParams headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -689,15 +711,15 @@ func (w *ServerInterfaceWrapper) GetAdminGames(ctx echo.Context) error { } // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetAdminGames(ctx, params) + err = w.Handler.AdminGetGames(ctx, params) return err } -// GetAdminGamesGameId converts echo context to params. -func (w *ServerInterfaceWrapper) GetAdminGamesGameId(ctx echo.Context) error { +// AdminGetGame converts echo context to params. +func (w *ServerInterfaceWrapper) AdminGetGame(ctx echo.Context) error { var err error // ------------- Path parameter "game_id" ------------- - var gameId int + var gameId PathGameId err = runtime.BindStyledParameterWithOptions("simple", "game_id", ctx.Param("game_id"), &gameId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { @@ -705,12 +727,12 @@ func (w *ServerInterfaceWrapper) GetAdminGamesGameId(ctx echo.Context) error { } // Parameter object where we will unmarshal all parameters from the context - var params GetAdminGamesGameIdParams + var params AdminGetGameParams headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -727,15 +749,15 @@ func (w *ServerInterfaceWrapper) GetAdminGamesGameId(ctx echo.Context) error { } // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetAdminGamesGameId(ctx, gameId, params) + err = w.Handler.AdminGetGame(ctx, gameId, params) return err } -// PutAdminGamesGameId converts echo context to params. -func (w *ServerInterfaceWrapper) PutAdminGamesGameId(ctx echo.Context) error { +// AdminPutGame converts echo context to params. +func (w *ServerInterfaceWrapper) AdminPutGame(ctx echo.Context) error { var err error // ------------- Path parameter "game_id" ------------- - var gameId int + var gameId PathGameId err = runtime.BindStyledParameterWithOptions("simple", "game_id", ctx.Param("game_id"), &gameId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { @@ -743,12 +765,12 @@ func (w *ServerInterfaceWrapper) PutAdminGamesGameId(ctx echo.Context) error { } // Parameter object where we will unmarshal all parameters from the context - var params PutAdminGamesGameIdParams + var params AdminPutGameParams headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -765,21 +787,21 @@ func (w *ServerInterfaceWrapper) PutAdminGamesGameId(ctx echo.Context) error { } // Invoke the callback with all the unmarshaled arguments - err = w.Handler.PutAdminGamesGameId(ctx, gameId, params) + err = w.Handler.AdminPutGame(ctx, gameId, params) return err } -// GetAdminUsers converts echo context to params. -func (w *ServerInterfaceWrapper) GetAdminUsers(ctx echo.Context) error { +// AdminGetUsers converts echo context to params. +func (w *ServerInterfaceWrapper) AdminGetUsers(ctx echo.Context) error { var err error // Parameter object where we will unmarshal all parameters from the context - var params GetAdminUsersParams + var params AdminGetUsersParams headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -796,7 +818,7 @@ func (w *ServerInterfaceWrapper) GetAdminUsers(ctx echo.Context) error { } // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetAdminUsers(ctx, params) + err = w.Handler.AdminGetUsers(ctx, params) return err } @@ -806,17 +828,11 @@ func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error { // Parameter object where we will unmarshal all parameters from the context var params GetGamesParams - // ------------- Optional query parameter "player_id" ------------- - - err = runtime.BindQueryParameter("form", true, false, "player_id", ctx.QueryParams(), ¶ms.PlayerId) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter player_id: %s", err)) - } headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -837,11 +853,11 @@ func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error { return err } -// GetGamesGameId converts echo context to params. -func (w *ServerInterfaceWrapper) GetGamesGameId(ctx echo.Context) error { +// GetGame converts echo context to params. +func (w *ServerInterfaceWrapper) GetGame(ctx echo.Context) error { var err error // ------------- Path parameter "game_id" ------------- - var gameId int + var gameId PathGameId err = runtime.BindStyledParameterWithOptions("simple", "game_id", ctx.Param("game_id"), &gameId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { @@ -849,12 +865,12 @@ func (w *ServerInterfaceWrapper) GetGamesGameId(ctx echo.Context) error { } // Parameter object where we will unmarshal all parameters from the context - var params GetGamesGameIdParams + var params GetGameParams headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -871,7 +887,7 @@ func (w *ServerInterfaceWrapper) GetGamesGameId(ctx echo.Context) error { } // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetGamesGameId(ctx, gameId, params) + err = w.Handler.GetGame(ctx, gameId, params) return err } @@ -894,7 +910,7 @@ func (w *ServerInterfaceWrapper) GetToken(ctx echo.Context) error { headers := ctx.Request().Header // ------------- Required header parameter "Authorization" ------------- if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization string + var Authorization HeaderAuthorization n := len(valueList) if n != 1 { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) @@ -943,164 +959,194 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } - router.GET(baseURL+"/admin/games", wrapper.GetAdminGames) - router.GET(baseURL+"/admin/games/:game_id", wrapper.GetAdminGamesGameId) - router.PUT(baseURL+"/admin/games/:game_id", wrapper.PutAdminGamesGameId) - router.GET(baseURL+"/admin/users", wrapper.GetAdminUsers) + router.GET(baseURL+"/admin/games", wrapper.AdminGetGames) + router.GET(baseURL+"/admin/games/:game_id", wrapper.AdminGetGame) + router.PUT(baseURL+"/admin/games/:game_id", wrapper.AdminPutGame) + router.GET(baseURL+"/admin/users", wrapper.AdminGetUsers) router.GET(baseURL+"/games", wrapper.GetGames) - router.GET(baseURL+"/games/:game_id", wrapper.GetGamesGameId) + router.GET(baseURL+"/games/:game_id", wrapper.GetGame) router.POST(baseURL+"/login", wrapper.PostLogin) router.GET(baseURL+"/token", wrapper.GetToken) } -type GetAdminGamesRequestObject struct { - Params GetAdminGamesParams +type BadRequestJSONResponse Error + +type ForbiddenJSONResponse Error + +type NotFoundJSONResponse Error + +type UnauthorizedJSONResponse Error + +type AdminGetGamesRequestObject struct { + Params AdminGetGamesParams } -type GetAdminGamesResponseObject interface { - VisitGetAdminGamesResponse(w http.ResponseWriter) error +type AdminGetGamesResponseObject interface { + VisitAdminGetGamesResponse(w http.ResponseWriter) error } -type GetAdminGames200JSONResponse struct { +type AdminGetGames200JSONResponse struct { Games []Game `json:"games"` } -func (response GetAdminGames200JSONResponse) VisitGetAdminGamesResponse(w http.ResponseWriter) error { +func (response AdminGetGames200JSONResponse) VisitAdminGetGamesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetAdminGames403JSONResponse struct { - Message string `json:"message"` +type AdminGetGames401JSONResponse struct{ UnauthorizedJSONResponse } + +func (response AdminGetGames401JSONResponse) VisitAdminGetGamesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } -func (response GetAdminGames403JSONResponse) VisitGetAdminGamesResponse(w http.ResponseWriter) error { +type AdminGetGames403JSONResponse struct{ ForbiddenJSONResponse } + +func (response AdminGetGames403JSONResponse) VisitAdminGetGamesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(403) return json.NewEncoder(w).Encode(response) } -type GetAdminGamesGameIdRequestObject struct { - GameId int `json:"game_id"` - Params GetAdminGamesGameIdParams +type AdminGetGameRequestObject struct { + GameId PathGameId `json:"game_id"` + Params AdminGetGameParams } -type GetAdminGamesGameIdResponseObject interface { - VisitGetAdminGamesGameIdResponse(w http.ResponseWriter) error +type AdminGetGameResponseObject interface { + VisitAdminGetGameResponse(w http.ResponseWriter) error } -type GetAdminGamesGameId200JSONResponse struct { +type AdminGetGame200JSONResponse struct { Game Game `json:"game"` } -func (response GetAdminGamesGameId200JSONResponse) VisitGetAdminGamesGameIdResponse(w http.ResponseWriter) error { +func (response AdminGetGame200JSONResponse) VisitAdminGetGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetAdminGamesGameId403JSONResponse struct { - Message string `json:"message"` +type AdminGetGame401JSONResponse struct{ UnauthorizedJSONResponse } + +func (response AdminGetGame401JSONResponse) VisitAdminGetGameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } -func (response GetAdminGamesGameId403JSONResponse) VisitGetAdminGamesGameIdResponse(w http.ResponseWriter) error { +type AdminGetGame403JSONResponse struct{ ForbiddenJSONResponse } + +func (response AdminGetGame403JSONResponse) VisitAdminGetGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(403) return json.NewEncoder(w).Encode(response) } -type GetAdminGamesGameId404JSONResponse struct { - Message string `json:"message"` -} +type AdminGetGame404JSONResponse struct{ NotFoundJSONResponse } -func (response GetAdminGamesGameId404JSONResponse) VisitGetAdminGamesGameIdResponse(w http.ResponseWriter) error { +func (response AdminGetGame404JSONResponse) VisitAdminGetGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type PutAdminGamesGameIdRequestObject struct { - GameId int `json:"game_id"` - Params PutAdminGamesGameIdParams - Body *PutAdminGamesGameIdJSONRequestBody +type AdminPutGameRequestObject struct { + GameId PathGameId `json:"game_id"` + Params AdminPutGameParams + Body *AdminPutGameJSONRequestBody } -type PutAdminGamesGameIdResponseObject interface { - VisitPutAdminGamesGameIdResponse(w http.ResponseWriter) error +type AdminPutGameResponseObject interface { + VisitAdminPutGameResponse(w http.ResponseWriter) error } -type PutAdminGamesGameId204Response struct { +type AdminPutGame204Response struct { } -func (response PutAdminGamesGameId204Response) VisitPutAdminGamesGameIdResponse(w http.ResponseWriter) error { +func (response AdminPutGame204Response) VisitAdminPutGameResponse(w http.ResponseWriter) error { w.WriteHeader(204) return nil } -type PutAdminGamesGameId400JSONResponse struct { - Message string `json:"message"` -} +type AdminPutGame400JSONResponse struct{ BadRequestJSONResponse } -func (response PutAdminGamesGameId400JSONResponse) VisitPutAdminGamesGameIdResponse(w http.ResponseWriter) error { +func (response AdminPutGame400JSONResponse) VisitAdminPutGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type PutAdminGamesGameId403JSONResponse struct { - Message string `json:"message"` +type AdminPutGame401JSONResponse struct{ UnauthorizedJSONResponse } + +func (response AdminPutGame401JSONResponse) VisitAdminPutGameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } -func (response PutAdminGamesGameId403JSONResponse) VisitPutAdminGamesGameIdResponse(w http.ResponseWriter) error { +type AdminPutGame403JSONResponse struct{ ForbiddenJSONResponse } + +func (response AdminPutGame403JSONResponse) VisitAdminPutGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(403) return json.NewEncoder(w).Encode(response) } -type PutAdminGamesGameId404JSONResponse struct { - Message string `json:"message"` -} +type AdminPutGame404JSONResponse struct{ NotFoundJSONResponse } -func (response PutAdminGamesGameId404JSONResponse) VisitPutAdminGamesGameIdResponse(w http.ResponseWriter) error { +func (response AdminPutGame404JSONResponse) VisitAdminPutGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type GetAdminUsersRequestObject struct { - Params GetAdminUsersParams +type AdminGetUsersRequestObject struct { + Params AdminGetUsersParams } -type GetAdminUsersResponseObject interface { - VisitGetAdminUsersResponse(w http.ResponseWriter) error +type AdminGetUsersResponseObject interface { + VisitAdminGetUsersResponse(w http.ResponseWriter) error } -type GetAdminUsers200JSONResponse struct { +type AdminGetUsers200JSONResponse struct { Users []User `json:"users"` } -func (response GetAdminUsers200JSONResponse) VisitGetAdminUsersResponse(w http.ResponseWriter) error { +func (response AdminGetUsers200JSONResponse) VisitAdminGetUsersResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetAdminUsers403JSONResponse struct { - Message string `json:"message"` +type AdminGetUsers401JSONResponse struct{ UnauthorizedJSONResponse } + +func (response AdminGetUsers401JSONResponse) VisitAdminGetUsersResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } -func (response GetAdminUsers403JSONResponse) VisitGetAdminUsersResponse(w http.ResponseWriter) error { +type AdminGetUsers403JSONResponse struct{ ForbiddenJSONResponse } + +func (response AdminGetUsers403JSONResponse) VisitAdminGetUsersResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(403) @@ -1126,10 +1172,17 @@ func (response GetGames200JSONResponse) VisitGetGamesResponse(w http.ResponseWri return json.NewEncoder(w).Encode(response) } -type GetGames403JSONResponse struct { - Message string `json:"message"` +type GetGames401JSONResponse struct{ UnauthorizedJSONResponse } + +func (response GetGames401JSONResponse) VisitGetGamesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } +type GetGames403JSONResponse struct{ ForbiddenJSONResponse } + func (response GetGames403JSONResponse) VisitGetGamesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(403) @@ -1137,42 +1190,47 @@ func (response GetGames403JSONResponse) VisitGetGamesResponse(w http.ResponseWri return json.NewEncoder(w).Encode(response) } -type GetGamesGameIdRequestObject struct { - GameId int `json:"game_id"` - Params GetGamesGameIdParams +type GetGameRequestObject struct { + GameId PathGameId `json:"game_id"` + Params GetGameParams } -type GetGamesGameIdResponseObject interface { - VisitGetGamesGameIdResponse(w http.ResponseWriter) error +type GetGameResponseObject interface { + VisitGetGameResponse(w http.ResponseWriter) error } -type GetGamesGameId200JSONResponse struct { +type GetGame200JSONResponse struct { Game Game `json:"game"` } -func (response GetGamesGameId200JSONResponse) VisitGetGamesGameIdResponse(w http.ResponseWriter) error { +func (response GetGame200JSONResponse) VisitGetGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetGamesGameId403JSONResponse struct { - Message string `json:"message"` +type GetGame401JSONResponse struct{ UnauthorizedJSONResponse } + +func (response GetGame401JSONResponse) VisitGetGameResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } -func (response GetGamesGameId403JSONResponse) VisitGetGamesGameIdResponse(w http.ResponseWriter) error { +type GetGame403JSONResponse struct{ ForbiddenJSONResponse } + +func (response GetGame403JSONResponse) VisitGetGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(403) return json.NewEncoder(w).Encode(response) } -type GetGamesGameId404JSONResponse struct { - Message string `json:"message"` -} +type GetGame404JSONResponse struct{ NotFoundJSONResponse } -func (response GetGamesGameId404JSONResponse) VisitGetGamesGameIdResponse(w http.ResponseWriter) error { +func (response GetGame404JSONResponse) VisitGetGameResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(404) @@ -1198,9 +1256,7 @@ func (response PostLogin200JSONResponse) VisitPostLoginResponse(w http.ResponseW return json.NewEncoder(w).Encode(response) } -type PostLogin401JSONResponse struct { - Message string `json:"message"` -} +type PostLogin401JSONResponse struct{ UnauthorizedJSONResponse } func (response PostLogin401JSONResponse) VisitPostLoginResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -1228,13 +1284,11 @@ func (response GetToken200JSONResponse) VisitGetTokenResponse(w http.ResponseWri return json.NewEncoder(w).Encode(response) } -type GetToken403JSONResponse struct { - Message string `json:"message"` -} +type GetToken401JSONResponse struct{ UnauthorizedJSONResponse } -func (response GetToken403JSONResponse) VisitGetTokenResponse(w http.ResponseWriter) error { +func (response GetToken401JSONResponse) VisitGetTokenResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(403) + w.WriteHeader(401) return json.NewEncoder(w).Encode(response) } @@ -1243,22 +1297,22 @@ func (response GetToken403JSONResponse) VisitGetTokenResponse(w http.ResponseWri type StrictServerInterface interface { // List games // (GET /admin/games) - GetAdminGames(ctx context.Context, request GetAdminGamesRequestObject) (GetAdminGamesResponseObject, error) + AdminGetGames(ctx context.Context, request AdminGetGamesRequestObject) (AdminGetGamesResponseObject, error) // Get a game // (GET /admin/games/{game_id}) - GetAdminGamesGameId(ctx context.Context, request GetAdminGamesGameIdRequestObject) (GetAdminGamesGameIdResponseObject, error) + AdminGetGame(ctx context.Context, request AdminGetGameRequestObject) (AdminGetGameResponseObject, error) // Update a game // (PUT /admin/games/{game_id}) - PutAdminGamesGameId(ctx context.Context, request PutAdminGamesGameIdRequestObject) (PutAdminGamesGameIdResponseObject, error) + AdminPutGame(ctx context.Context, request AdminPutGameRequestObject) (AdminPutGameResponseObject, error) // List all users // (GET /admin/users) - GetAdminUsers(ctx context.Context, request GetAdminUsersRequestObject) (GetAdminUsersResponseObject, error) + AdminGetUsers(ctx context.Context, request AdminGetUsersRequestObject) (AdminGetUsersResponseObject, error) // List games // (GET /games) GetGames(ctx context.Context, request GetGamesRequestObject) (GetGamesResponseObject, error) // Get a game // (GET /games/{game_id}) - GetGamesGameId(ctx context.Context, request GetGamesGameIdRequestObject) (GetGamesGameIdResponseObject, error) + GetGame(ctx context.Context, request GetGameRequestObject) (GetGameResponseObject, error) // User login // (POST /login) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) @@ -1279,108 +1333,108 @@ type strictHandler struct { middlewares []StrictMiddlewareFunc } -// GetAdminGames operation middleware -func (sh *strictHandler) GetAdminGames(ctx echo.Context, params GetAdminGamesParams) error { - var request GetAdminGamesRequestObject +// AdminGetGames operation middleware +func (sh *strictHandler) AdminGetGames(ctx echo.Context, params AdminGetGamesParams) error { + var request AdminGetGamesRequestObject request.Params = params handler := func(ctx echo.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetAdminGames(ctx.Request().Context(), request.(GetAdminGamesRequestObject)) + return sh.ssi.AdminGetGames(ctx.Request().Context(), request.(AdminGetGamesRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetAdminGames") + handler = middleware(handler, "AdminGetGames") } response, err := handler(ctx, request) if err != nil { return err - } else if validResponse, ok := response.(GetAdminGamesResponseObject); ok { - return validResponse.VisitGetAdminGamesResponse(ctx.Response()) + } else if validResponse, ok := response.(AdminGetGamesResponseObject); ok { + return validResponse.VisitAdminGetGamesResponse(ctx.Response()) } else if response != nil { return fmt.Errorf("unexpected response type: %T", response) } return nil } -// GetAdminGamesGameId operation middleware -func (sh *strictHandler) GetAdminGamesGameId(ctx echo.Context, gameId int, params GetAdminGamesGameIdParams) error { - var request GetAdminGamesGameIdRequestObject +// AdminGetGame operation middleware +func (sh *strictHandler) AdminGetGame(ctx echo.Context, gameId PathGameId, params AdminGetGameParams) error { + var request AdminGetGameRequestObject request.GameId = gameId request.Params = params handler := func(ctx echo.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetAdminGamesGameId(ctx.Request().Context(), request.(GetAdminGamesGameIdRequestObject)) + return sh.ssi.AdminGetGame(ctx.Request().Context(), request.(AdminGetGameRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetAdminGamesGameId") + handler = middleware(handler, "AdminGetGame") } response, err := handler(ctx, request) if err != nil { return err - } else if validResponse, ok := response.(GetAdminGamesGameIdResponseObject); ok { - return validResponse.VisitGetAdminGamesGameIdResponse(ctx.Response()) + } else if validResponse, ok := response.(AdminGetGameResponseObject); ok { + return validResponse.VisitAdminGetGameResponse(ctx.Response()) } else if response != nil { return fmt.Errorf("unexpected response type: %T", response) } return nil } -// PutAdminGamesGameId operation middleware -func (sh *strictHandler) PutAdminGamesGameId(ctx echo.Context, gameId int, params PutAdminGamesGameIdParams) error { - var request PutAdminGamesGameIdRequestObject +// AdminPutGame operation middleware +func (sh *strictHandler) AdminPutGame(ctx echo.Context, gameId PathGameId, params AdminPutGameParams) error { + var request AdminPutGameRequestObject request.GameId = gameId request.Params = params - var body PutAdminGamesGameIdJSONRequestBody + var body AdminPutGameJSONRequestBody if err := ctx.Bind(&body); err != nil { return err } request.Body = &body handler := func(ctx echo.Context, request interface{}) (interface{}, error) { - return sh.ssi.PutAdminGamesGameId(ctx.Request().Context(), request.(PutAdminGamesGameIdRequestObject)) + return sh.ssi.AdminPutGame(ctx.Request().Context(), request.(AdminPutGameRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "PutAdminGamesGameId") + handler = middleware(handler, "AdminPutGame") } response, err := handler(ctx, request) if err != nil { return err - } else if validResponse, ok := response.(PutAdminGamesGameIdResponseObject); ok { - return validResponse.VisitPutAdminGamesGameIdResponse(ctx.Response()) + } else if validResponse, ok := response.(AdminPutGameResponseObject); ok { + return validResponse.VisitAdminPutGameResponse(ctx.Response()) } else if response != nil { return fmt.Errorf("unexpected response type: %T", response) } return nil } -// GetAdminUsers operation middleware -func (sh *strictHandler) GetAdminUsers(ctx echo.Context, params GetAdminUsersParams) error { - var request GetAdminUsersRequestObject +// AdminGetUsers operation middleware +func (sh *strictHandler) AdminGetUsers(ctx echo.Context, params AdminGetUsersParams) error { + var request AdminGetUsersRequestObject request.Params = params handler := func(ctx echo.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetAdminUsers(ctx.Request().Context(), request.(GetAdminUsersRequestObject)) + return sh.ssi.AdminGetUsers(ctx.Request().Context(), request.(AdminGetUsersRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetAdminUsers") + handler = middleware(handler, "AdminGetUsers") } response, err := handler(ctx, request) if err != nil { return err - } else if validResponse, ok := response.(GetAdminUsersResponseObject); ok { - return validResponse.VisitGetAdminUsersResponse(ctx.Response()) + } else if validResponse, ok := response.(AdminGetUsersResponseObject); ok { + return validResponse.VisitAdminGetUsersResponse(ctx.Response()) } else if response != nil { return fmt.Errorf("unexpected response type: %T", response) } @@ -1412,26 +1466,26 @@ func (sh *strictHandler) GetGames(ctx echo.Context, params GetGamesParams) error return nil } -// GetGamesGameId operation middleware -func (sh *strictHandler) GetGamesGameId(ctx echo.Context, gameId int, params GetGamesGameIdParams) error { - var request GetGamesGameIdRequestObject +// GetGame operation middleware +func (sh *strictHandler) GetGame(ctx echo.Context, gameId PathGameId, params GetGameParams) error { + var request GetGameRequestObject request.GameId = gameId request.Params = params handler := func(ctx echo.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetGamesGameId(ctx.Request().Context(), request.(GetGamesGameIdRequestObject)) + return sh.ssi.GetGame(ctx.Request().Context(), request.(GetGameRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetGamesGameId") + handler = middleware(handler, "GetGame") } response, err := handler(ctx, request) if err != nil { return err - } else if validResponse, ok := response.(GetGamesGameIdResponseObject); ok { - return validResponse.VisitGetGamesGameIdResponse(ctx.Response()) + } else if validResponse, ok := response.(GetGameResponseObject); ok { + return validResponse.VisitGetGameResponse(ctx.Response()) } else if response != nil { return fmt.Errorf("unexpected response type: %T", response) } @@ -1495,32 +1549,32 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "LMuy/wIAAP//vgYmOHMsAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/api/handler_wrapper.go b/backend/api/handler_wrapper.go new file mode 100644 index 0000000..939e37a --- /dev/null +++ b/backend/api/handler_wrapper.go @@ -0,0 +1,156 @@ +// Code generated by go generate; DO NOT EDIT. + +package api + +import ( + "context" + "errors" + "strings" + + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" +) + +var _ StrictServerInterface = (*ApiHandlerWrapper)(nil) + +type ApiHandlerWrapper struct { + innerHandler ApiHandler +} + +func NewHandler(queries *db.Queries, hubs GameHubsInterface) *ApiHandlerWrapper { + return &ApiHandlerWrapper{ + innerHandler: ApiHandler{ + q: queries, + hubs: hubs, + }, + } +} + +func parseJWTClaimsFromAuthorizationHeader(authorization string) (*auth.JWTClaims, error) { + const prefix = "Bearer " + if !strings.HasPrefix(authorization, prefix) { + return nil, errors.New("invalid authorization header") + } + token := authorization[len(prefix):] + claims, err := auth.ParseJWT(token) + if err != nil { + return nil, err + } + return claims, nil +} + +func (h *ApiHandlerWrapper) AdminGetGame(ctx context.Context, request AdminGetGameRequestObject) (AdminGetGameResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return AdminGetGame401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + if !user.IsAdmin { + return AdminGetGame403JSONResponse{ + ForbiddenJSONResponse: ForbiddenJSONResponse{ + Message: "Forbidden", + }, + }, nil + } + return h.innerHandler.AdminGetGame(ctx, request, user) +} + +func (h *ApiHandlerWrapper) AdminGetGames(ctx context.Context, request AdminGetGamesRequestObject) (AdminGetGamesResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return AdminGetGames401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + if !user.IsAdmin { + return AdminGetGames403JSONResponse{ + ForbiddenJSONResponse: ForbiddenJSONResponse{ + Message: "Forbidden", + }, + }, nil + } + return h.innerHandler.AdminGetGames(ctx, request, user) +} + +func (h *ApiHandlerWrapper) AdminGetUsers(ctx context.Context, request AdminGetUsersRequestObject) (AdminGetUsersResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return AdminGetUsers401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + if !user.IsAdmin { + return AdminGetUsers403JSONResponse{ + ForbiddenJSONResponse: ForbiddenJSONResponse{ + Message: "Forbidden", + }, + }, nil + } + return h.innerHandler.AdminGetUsers(ctx, request, user) +} + +func (h *ApiHandlerWrapper) AdminPutGame(ctx context.Context, request AdminPutGameRequestObject) (AdminPutGameResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return AdminPutGame401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + if !user.IsAdmin { + return AdminPutGame403JSONResponse{ + ForbiddenJSONResponse: ForbiddenJSONResponse{ + Message: "Forbidden", + }, + }, nil + } + return h.innerHandler.AdminPutGame(ctx, request, user) +} + +func (h *ApiHandlerWrapper) GetGame(ctx context.Context, request GetGameRequestObject) (GetGameResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return GetGame401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + return h.innerHandler.GetGame(ctx, request, user) +} + +func (h *ApiHandlerWrapper) GetGames(ctx context.Context, request GetGamesRequestObject) (GetGamesResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return GetGames401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + return h.innerHandler.GetGames(ctx, request, user) +} + +func (h *ApiHandlerWrapper) GetToken(ctx context.Context, request GetTokenRequestObject) (GetTokenResponseObject, error) { + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return GetToken401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + return h.innerHandler.GetToken(ctx, request, user) +} + +func (h *ApiHandlerWrapper) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) { + return h.innerHandler.PostLogin(ctx, request) +} diff --git a/backend/api/handlers.go b/backend/api/handlers.go index c96cd2a..35fc9f7 100644 --- a/backend/api/handlers.go +++ b/backend/api/handlers.go @@ -4,7 +4,6 @@ import ( "context" "errors" "net/http" - "strings" "time" "github.com/jackc/pgx/v5" @@ -15,8 +14,6 @@ import ( "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" ) -var _ StrictServerInterface = (*ApiHandler)(nil) - type ApiHandler struct { q *db.Queries hubs GameHubsInterface @@ -26,20 +23,7 @@ type GameHubsInterface interface { StartGame(gameID int) error } -func NewHandler(queries *db.Queries, hubs GameHubsInterface) *ApiHandler { - return &ApiHandler{ - q: queries, - hubs: hubs, - } -} - -func (h *ApiHandler) GetAdminGames(ctx context.Context, request GetAdminGamesRequestObject) (GetAdminGamesResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) - if !user.IsAdmin { - return GetAdminGames403JSONResponse{ - Message: "Forbidden", - }, nil - } +func (h *ApiHandler) AdminGetGames(ctx context.Context, request AdminGetGamesRequestObject, user *auth.JWTClaims) (AdminGetGamesResponseObject, error) { gameRows, err := h.q.ListGames(ctx) if err != nil { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -71,24 +55,20 @@ func (h *ApiHandler) GetAdminGames(ctx context.Context, request GetAdminGamesReq Problem: problem, } } - return GetAdminGames200JSONResponse{ + return AdminGetGames200JSONResponse{ Games: games, }, nil } -func (h *ApiHandler) GetAdminGamesGameId(ctx context.Context, request GetAdminGamesGameIdRequestObject) (GetAdminGamesGameIdResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) - if !user.IsAdmin { - return GetAdminGamesGameId403JSONResponse{ - Message: "Forbidden", - }, nil - } - gameId := request.GameId - row, err := h.q.GetGameById(ctx, int32(gameId)) +func (h *ApiHandler) AdminGetGame(ctx context.Context, request AdminGetGameRequestObject, user *auth.JWTClaims) (AdminGetGameResponseObject, error) { + gameID := request.GameId + row, err := h.q.GetGameById(ctx, int32(gameID)) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - return GetAdminGamesGameId404JSONResponse{ - Message: "Game not found", + return AdminGetGame404JSONResponse{ + NotFoundJSONResponse: NotFoundJSONResponse{ + Message: "Game not found", + }, }, nil } else { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -118,18 +98,12 @@ func (h *ApiHandler) GetAdminGamesGameId(ctx context.Context, request GetAdminGa StartedAt: startedAt, Problem: problem, } - return GetAdminGamesGameId200JSONResponse{ + return AdminGetGame200JSONResponse{ Game: game, }, nil } -func (h *ApiHandler) PutAdminGamesGameId(ctx context.Context, request PutAdminGamesGameIdRequestObject) (PutAdminGamesGameIdResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) - if !user.IsAdmin { - return PutAdminGamesGameId403JSONResponse{ - Message: "Forbidden", - }, nil - } +func (h *ApiHandler) AdminPutGame(ctx context.Context, request AdminPutGameRequestObject, user *auth.JWTClaims) (AdminPutGameResponseObject, error) { gameID := request.GameId displayName := request.Body.DisplayName durationSeconds := request.Body.DurationSeconds @@ -140,8 +114,10 @@ func (h *ApiHandler) PutAdminGamesGameId(ctx context.Context, request PutAdminGa game, err := h.q.GetGameById(ctx, int32(gameID)) if err != nil { if err == pgx.ErrNoRows { - return PutAdminGamesGameId404JSONResponse{ - Message: "Game not found", + return AdminPutGame404JSONResponse{ + NotFoundJSONResponse: NotFoundJSONResponse{ + Message: "Game not found", + }, }, nil } else { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -202,21 +178,17 @@ func (h *ApiHandler) PutAdminGamesGameId(ctx context.Context, request PutAdminGa ProblemID: changedProblemID, }) if err != nil { - return PutAdminGamesGameId400JSONResponse{ - Message: err.Error(), + return AdminPutGame400JSONResponse{ + BadRequestJSONResponse: BadRequestJSONResponse{ + Message: err.Error(), + }, }, nil } - return PutAdminGamesGameId204Response{}, nil + return AdminPutGame204Response{}, nil } -func (h *ApiHandler) GetAdminUsers(ctx context.Context, request GetAdminUsersRequestObject) (GetAdminUsersResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) - if !user.IsAdmin { - return GetAdminUsers403JSONResponse{ - Message: "Forbidden", - }, nil - } +func (h *ApiHandler) AdminGetUsers(ctx context.Context, request AdminGetUsersRequestObject, user *auth.JWTClaims) (AdminGetUsersResponseObject, error) { users, err := h.q.ListUsers(ctx) if err != nil { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -231,7 +203,7 @@ func (h *ApiHandler) GetAdminUsers(ctx context.Context, request GetAdminUsersReq IsAdmin: u.IsAdmin, } } - return GetAdminUsers200JSONResponse{ + return AdminGetUsers200JSONResponse{ Users: responseUsers, }, nil } @@ -239,17 +211,21 @@ func (h *ApiHandler) GetAdminUsers(ctx context.Context, request GetAdminUsersReq 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) + userID, err := auth.Login(ctx, h.q, username, password) if err != nil { return PostLogin401JSONResponse{ - Message: "Invalid username or password", + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Invalid username or password", + }, }, nil } - user, err := h.q.GetUserById(ctx, int32(userId)) + user, err := h.q.GetUserById(ctx, int32(userID)) if err != nil { return PostLogin401JSONResponse{ - Message: "Invalid username or password", + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Invalid username or password", + }, }, nil } @@ -263,8 +239,7 @@ func (h *ApiHandler) PostLogin(ctx context.Context, request PostLoginRequestObje }, nil } -func (h *ApiHandler) GetToken(ctx context.Context, request GetTokenRequestObject) (GetTokenResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) +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()) @@ -274,98 +249,53 @@ func (h *ApiHandler) GetToken(ctx context.Context, request GetTokenRequestObject }, nil } -func (h *ApiHandler) GetGames(ctx context.Context, request GetGamesRequestObject) (GetGamesResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) - playerId := request.Params.PlayerId - if !user.IsAdmin { - if playerId == nil || *playerId != user.UserID { - return GetGames403JSONResponse{ - Message: "Forbidden", - }, 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()) } - if playerId == nil { - gameRows, err := h.q.ListGames(ctx) - 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 } - 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, - } + var problem *Problem + if row.ProblemID != nil { + if row.Title == nil || row.Description == nil { + panic("inconsistent data") } - games[i] = Game{ - GameId: int(row.GameID), - State: GameState(row.State), - DisplayName: row.DisplayName, - DurationSeconds: int(row.DurationSeconds), - StartedAt: startedAt, - Problem: problem, + problem = &Problem{ + ProblemId: int(*row.ProblemID), + Title: *row.Title, + Description: *row.Description, } } - return GetGames200JSONResponse{ - Games: games, - }, nil - } else { - gameRows, err := h.q.ListGamesForPlayer(ctx, int32(*playerId)) - 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), - State: GameState(row.State), - DisplayName: row.DisplayName, - DurationSeconds: int(row.DurationSeconds), - StartedAt: startedAt, - Problem: problem, - } + games[i] = Game{ + GameId: int(row.GameID), + State: GameState(row.State), + DisplayName: row.DisplayName, + DurationSeconds: int(row.DurationSeconds), + StartedAt: startedAt, + Problem: problem, } - return GetGames200JSONResponse{ - Games: games, - }, nil } + return GetGames200JSONResponse{ + Games: games, + }, nil } -func (h *ApiHandler) GetGamesGameId(ctx context.Context, request GetGamesGameIdRequestObject) (GetGamesGameIdResponseObject, error) { - user := ctx.Value("user").(*auth.JWTClaims) +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)) + gameID := request.GameId + row, err := h.q.GetGameById(ctx, int32(gameID)) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - return GetGamesGameId404JSONResponse{ - Message: "Game not found", + return GetGame404JSONResponse{ + NotFoundJSONResponse: NotFoundJSONResponse{ + Message: "Game not found", + }, }, nil } else { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -397,49 +327,7 @@ func (h *ApiHandler) GetGamesGameId(ctx context.Context, request GetGamesGameIdR StartedAt: startedAt, Problem: problem, } - return GetGamesGameId200JSONResponse{ + return GetGame200JSONResponse{ Game: game, }, nil } - -func _assertUserResponseIsCompatibleWithJWTClaims() { - var c auth.JWTClaims - var u User - u.UserId = c.UserID - u.Username = c.Username - u.DisplayName = c.DisplayName - u.IconPath = c.IconPath - u.IsAdmin = c.IsAdmin - _ = u -} - -func setupJWTFromAuthorizationHeader(c echo.Context) error { - authorization := c.Request().Header.Get("Authorization") - const prefix = "Bearer " - if !strings.HasPrefix(authorization, prefix) { - return echo.NewHTTPError(http.StatusUnauthorized) - } - token := authorization[len(prefix):] - claims, err := auth.ParseJWT(token) - if err != nil { - return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) - } - c.SetRequest(c.Request().WithContext(context.WithValue(c.Request().Context(), "user", claims))) - return nil -} - -func NewJWTMiddleware() StrictMiddlewareFunc { - return func(handler StrictHandlerFunc, operationID string) StrictHandlerFunc { - if operationID == "PostLogin" { - return handler - } - - return func(c echo.Context, request interface{}) (interface{}, error) { - err := setupJWTFromAuthorizationHeader(c) - if err != nil { - return nil, echo.NewHTTPError(http.StatusUnauthorized, err.Error()) - } - return handler(c, request) - } - } -} diff --git a/backend/gen/api_handler_wrapper_gen.go b/backend/gen/api_handler_wrapper_gen.go new file mode 100644 index 0000000..7fd34b2 --- /dev/null +++ b/backend/gen/api_handler_wrapper_gen.go @@ -0,0 +1,168 @@ +package main + +import ( + "bytes" + "flag" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "slices" + "strings" + "text/template" +) + +func main() { + inputFile := flag.String("i", "", "input file") + outputFile := flag.String("o", "", "output file") + flag.Parse() + + if inputFile == nil || *inputFile == "" || outputFile == nil || *outputFile == "" { + flag.PrintDefaults() + os.Exit(1) + } + + // Parse the input file + fileSet := token.NewFileSet() + parsedFile, err := parser.ParseFile(fileSet, *inputFile, nil, parser.SkipObjectResolution) + if err != nil { + panic(err) + } + + // Find methods in StrictServerInterface + var methods []string + for _, decl := range parsedFile.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range genDecl.Specs { + typeSpec, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + if typeSpec.Name.Name != "StrictServerInterface" { + continue + } + interfaceType, ok := typeSpec.Type.(*ast.InterfaceType) + if !ok { + continue + } + for _, method := range interfaceType.Methods.List { + if len(method.Names) != 0 { + methods = append(methods, method.Names[0].Name) + } + } + } + } + if len(methods) == 0 { + panic("StrictServerInterface not found") + } + slices.Sort(methods) + + type TemplateParameter struct { + Name string + RequiresLogin bool + RequiresAdminRole bool + } + templateParameters := make([]TemplateParameter, len(methods)) + for i, method := range methods { + templateParameters[i] = TemplateParameter{ + Name: method, + RequiresLogin: method != "PostLogin", + RequiresAdminRole: strings.Contains(method, "Admin"), + } + } + + // Generate code. + tmpl, err := template.New("code").Parse(templateText) + if err != nil { + panic(err) + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, templateParameters) + if err != nil { + panic(err) + } + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + panic(err) + } + + err = os.WriteFile(*outputFile, formatted, 0644) + if err != nil { + panic(err) + } +} + +const templateText = `// Code generated by go generate; DO NOT EDIT. + +package api + +import ( + "context" + "errors" + "strings" + + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" +) + +var _ StrictServerInterface = (*ApiHandlerWrapper)(nil) + +type ApiHandlerWrapper struct { + innerHandler ApiHandler +} + +func NewHandler(queries *db.Queries, hubs GameHubsInterface) *ApiHandlerWrapper { + return &ApiHandlerWrapper{ + innerHandler: ApiHandler{ + q: queries, + hubs: hubs, + }, + } +} + +func parseJWTClaimsFromAuthorizationHeader(authorization string) (*auth.JWTClaims, error) { + const prefix = "Bearer " + if !strings.HasPrefix(authorization, prefix) { + return nil, errors.New("invalid authorization header") + } + token := authorization[len(prefix):] + claims, err := auth.ParseJWT(token) + if err != nil { + return nil, err + } + return claims, nil +} + +{{ range . }} + func (h *ApiHandlerWrapper) {{ .Name }}(ctx context.Context, request {{ .Name }}RequestObject) ({{ .Name }}ResponseObject, error) { + {{ if .RequiresLogin -}} + user, err := parseJWTClaimsFromAuthorizationHeader(request.Params.Authorization) + if err != nil { + return {{ .Name }}401JSONResponse{ + UnauthorizedJSONResponse: UnauthorizedJSONResponse{ + Message: "Unauthorized", + }, + }, nil + } + {{ if .RequiresAdminRole -}} + if !user.IsAdmin { + return {{ .Name }}403JSONResponse{ + ForbiddenJSONResponse: ForbiddenJSONResponse{ + Message: "Forbidden", + }, + }, nil + } + {{ end -}} + return h.innerHandler.{{ .Name }}(ctx, request, user) + {{ else -}} + return h.innerHandler.{{ .Name }}(ctx, request) + {{ end -}} + } +{{ end }} +` diff --git a/backend/gen/gen.go b/backend/gen/gen.go new file mode 100644 index 0000000..6fb430f --- /dev/null +++ b/backend/gen/gen.go @@ -0,0 +1,5 @@ +package main + +//go:generate go run github.com/sqlc-dev/sqlc/cmd/sqlc generate +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config oapi-codegen.yaml ../../openapi.yaml +//go:generate go run ./api_handler_wrapper_gen.go -i ../api/generated.go -o ../api/handler_wrapper.go diff --git a/backend/oapi-codegen.yaml b/backend/gen/oapi-codegen.yaml index 65cf079..d235096 100644 --- a/backend/oapi-codegen.yaml +++ b/backend/gen/oapi-codegen.yaml @@ -4,7 +4,7 @@ generate: echo-server: true strict-server: true embedded-spec: true -output: api/generated.go +output: ../api/generated.go output-options: skip-prune: true nullable-type: true diff --git a/backend/sqlc.yaml b/backend/gen/sqlc.yaml index 3b2d1d0..c56e44b 100644 --- a/backend/sqlc.yaml +++ b/backend/gen/sqlc.yaml @@ -1,11 +1,11 @@ version: "2" sql: - engine: "postgresql" - queries: "query.sql" - schema: "schema.sql" + queries: "../query.sql" + schema: "../schema.sql" gen: go: package: "db" - out: "db" + out: "../db" sql_package: "pgx/v5" emit_pointers_for_null_types: true diff --git a/backend/main.go b/backend/main.go index d636af7..0257113 100644 --- a/backend/main.go +++ b/backend/main.go @@ -75,9 +75,7 @@ func main() { apiGroup := e.Group("/api") apiGroup.Use(oapimiddleware.OapiRequestValidator(openApiSpec)) apiHandler := api.NewHandler(queries, gameHubs) - api.RegisterHandlers(apiGroup, api.NewStrictHandler(apiHandler, []api.StrictMiddlewareFunc{ - api.NewJWTMiddleware(), - })) + api.RegisterHandlers(apiGroup, api.NewStrictHandler(apiHandler, nil)) gameHubs.Run() diff --git a/frontend/Makefile b/frontend/Makefile deleted file mode 100644 index 17dd690..0000000 --- a/frontend/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -.PHONY: openapi-typescript -openapi-typescript: - npx --no-install openapi-typescript --path-params-as-types --output ./app/.server/api/schema.d.ts ../openapi.yaml diff --git a/frontend/app/.server/api/client.ts b/frontend/app/.server/api/client.ts index 8e50b7e..8f53b5b 100644 --- a/frontend/app/.server/api/client.ts +++ b/frontend/app/.server/api/client.ts @@ -1,9 +1,95 @@ import createClient from "openapi-fetch"; -import type { paths } from "./schema"; +import type { paths, operations } from "./schema"; -export const apiClient = createClient<paths>({ +const apiClient = createClient<paths>({ baseUrl: process.env.NODE_ENV === "development" ? "http://localhost:8002/api/" : "http://api-server/api/", }); + +export async function apiPostLogin(username: string, password: string) { + const { data, error } = await apiClient.POST("/login", { + body: { username, password }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGames(token: string) { + const { data, error } = await apiClient.GET("/games", { + params: { + header: { Authorization: `Bearer ${token}` }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGame(token: string, gameId: number) { + const { data, error } = await apiClient.GET("/games/{game_id}", { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetToken(token: string) { + const { data, error } = await apiClient.GET("/token", { + params: { + header: { Authorization: `Bearer ${token}` }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function adminApiGetUsers(token: string) { + const { data, error } = await apiClient.GET("/admin/users", { + params: { + header: { Authorization: `Bearer ${token}` }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function adminApiGetGames(token: string) { + const { data, error } = await apiClient.GET("/admin/games", { + params: { + header: { Authorization: `Bearer ${token}` }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function adminApiGetGame(token: string, gameId: number) { + const { data, error } = await apiClient.GET("/admin/games/{game_id}", { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function adminApiPutGame( + token: string, + gameId: number, + body: operations["adminPutGame"]["requestBody"]["content"]["application/json"], +) { + const { data, error } = await apiClient.PUT("/admin/games/{game_id}", { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + body, + }); + if (error) throw new Error(error.message); + return data; +} diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts index 000c876..705380d 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -14,50 +14,7 @@ export interface paths { get?: never; put?: never; /** User login */ - post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** @example john */ - username: string; - /** @example password123 */ - password: string; - }; - }; - }; - responses: { - /** @description Successfully authenticated */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example xxxxx.xxxxx.xxxxx */ - token: string; - }; - }; - }; - /** @description Invalid username or password */ - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Invalid credentials */ - message: string; - }; - }; - }; - }; - }; + post: operations["postLogin"]; delete?: never; options?: never; head?: never; @@ -72,43 +29,7 @@ export interface paths { cookie?: never; }; /** Get a short-lived access token */ - get: { - parameters: { - query?: never; - header: { - Authorization: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successfully authenticated */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example xxxxx.xxxxx.xxxxx */ - token: string; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - }; - }; + get: operations["getToken"]; put?: never; post?: never; delete?: never; @@ -125,44 +46,7 @@ export interface paths { cookie?: never; }; /** List games */ - get: { - parameters: { - query?: { - player_id?: number; - }; - header: { - Authorization: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of games */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - games: components["schemas"]["Game"][]; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - }; - }; + get: operations["getGames"]; put?: never; post?: never; delete?: never; @@ -171,7 +55,7 @@ export interface paths { patch?: never; trace?: never; }; - [path: `/games/${integer}`]: { + "/games/{game_id}": { parameters: { query?: never; header?: never; @@ -179,56 +63,7 @@ export interface paths { cookie?: never; }; /** Get a game */ - get: { - parameters: { - query?: never; - header: { - Authorization: string; - }; - path: { - game_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A game */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - game: components["schemas"]["Game"]; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - /** @description Not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Not found */ - message: string; - }; - }; - }; - }; - }; + get: operations["getGame"]; put?: never; post?: never; delete?: never; @@ -245,42 +80,7 @@ export interface paths { cookie?: never; }; /** List all users */ - get: { - parameters: { - query?: never; - header: { - Authorization: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of users */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - users: components["schemas"]["User"][]; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - }; - }; + get: operations["adminGetUsers"]; put?: never; post?: never; delete?: never; @@ -297,42 +97,7 @@ export interface paths { cookie?: never; }; /** List games */ - get: { - parameters: { - query?: never; - header: { - Authorization: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of games */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - games: components["schemas"]["Game"][]; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - }; - }; + get: operations["adminGetGames"]; put?: never; post?: never; delete?: never; @@ -341,7 +106,7 @@ export interface paths { patch?: never; trace?: never; }; - [path: `/admin/games/${integer}`]: { + "/admin/games/{game_id}": { parameters: { query?: never; header?: never; @@ -349,133 +114,9 @@ export interface paths { cookie?: never; }; /** Get a game */ - get: { - parameters: { - query?: never; - header: { - Authorization: string; - }; - path: { - game_id: number; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description A game */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - game: components["schemas"]["Game"]; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - /** @description Not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Not found */ - message: string; - }; - }; - }; - }; - }; + get: operations["adminGetGame"]; /** Update a game */ - put: { - parameters: { - query?: never; - header: { - Authorization: string; - }; - path: { - game_id: number; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** - * @example closed - * @enum {string} - */ - state?: "closed" | "waiting_entries" | "waiting_start" | "prepare" | "starting" | "gaming" | "finished"; - /** @example Game 1 */ - display_name?: string; - /** @example 360 */ - duration_seconds?: number; - /** @example 946684800 */ - started_at?: number | null; - /** @example 1 */ - problem_id?: number | null; - }; - }; - }; - responses: { - /** @description Successfully updated */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Invalid request */ - message: string; - }; - }; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Forbidden operation */ - message: string; - }; - }; - }; - /** @description Not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @example Not found */ - message: string; - }; - }; - }; - }; - }; + put: operations["adminPutGame"]; post?: never; delete?: never; options?: never; @@ -487,6 +128,10 @@ export interface paths { export type webhooks = Record<string, never>; export interface components { schemas: { + Error: { + /** @example Invalid request */ + message: string; + }; User: { /** @example 123 */ user_id: number; @@ -617,11 +262,291 @@ export interface components { stderr: string; }; }; - responses: never; - parameters: never; + responses: { + /** @description Bad request */ + BadRequest: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + Unauthorized: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + Forbidden: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + NotFound: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + parameters: { + header_authorization: string; + path_game_id: number; + }; requestBodies: never; headers: never; pathItems: never; } export type $defs = Record<string, never>; -export type operations = Record<string, never>; +export interface operations { + postLogin: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @example john */ + username: string; + /** @example password123 */ + password: string; + }; + }; + }; + responses: { + /** @description Successfully authenticated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example xxxxx.xxxxx.xxxxx */ + token: string; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + }; + }; + getToken: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successfully authenticated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example xxxxx.xxxxx.xxxxx */ + token: string; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + }; + }; + getGames: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of games */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + games: components["schemas"]["Game"][]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + }; + }; + getGame: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A game */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + game: components["schemas"]["Game"]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + adminGetUsers: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of users */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + users: components["schemas"]["User"][]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + }; + }; + adminGetGames: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of games */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + games: components["schemas"]["Game"][]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + }; + }; + adminGetGame: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A game */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + game: components["schemas"]["Game"]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + adminPutGame: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * @example closed + * @enum {string} + */ + state?: "closed" | "waiting_entries" | "waiting_start" | "prepare" | "starting" | "gaming" | "finished"; + /** @example Game 1 */ + display_name?: string; + /** @example 360 */ + duration_seconds?: number; + /** @example 946684800 */ + started_at?: number | null; + /** @example 1 */ + problem_id?: number | null; + }; + }; + }; + responses: { + /** @description Successfully updated */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; +} diff --git a/frontend/app/.server/auth.ts b/frontend/app/.server/auth.ts index b80166b..b7e1820 100644 --- a/frontend/app/.server/auth.ts +++ b/frontend/app/.server/auth.ts @@ -3,22 +3,13 @@ import { FormStrategy } from "remix-auth-form"; import { jwtDecode } from "jwt-decode"; import type { Session } from "@remix-run/server-runtime"; import { sessionStorage } from "./session"; -import { apiClient } from "./api/client"; +import { apiPostLogin } from "./api/client"; import { components } from "./api/schema"; export const authenticator = new Authenticator<string>(sessionStorage); async function login(username: string, password: string): Promise<string> { - const { data, error } = await apiClient.POST("/login", { - body: { - username, - password, - }, - }); - if (error) { - throw new Error(error.message); - } - return data.token; + return (await apiPostLogin(username, password)).token; } authenticator.use( diff --git a/frontend/app/routes/admin.games.tsx b/frontend/app/routes/admin.games.tsx index 085a97e..00a56d6 100644 --- a/frontend/app/routes/admin.games.tsx +++ b/frontend/app/routes/admin.games.tsx @@ -1,7 +1,7 @@ import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { useLoaderData, Link } from "@remix-run/react"; import { isAuthenticated } from "../.server/auth"; -import { apiClient } from "../.server/api/client"; +import { adminApiGetGames } from "../.server/api/client"; export const meta: MetaFunction = () => { return [{ title: "[Admin] Games | iOSDC Japan 2024 Albatross.swift" }]; @@ -14,17 +14,8 @@ export async function loader({ request }: LoaderFunctionArgs) { if (!user.is_admin) { throw new Error("Unauthorized"); } - const { data, error } = await apiClient.GET("/admin/games", { - params: { - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return { games: data.games }; + const { games } = await adminApiGetGames(token); + return { games }; } export default function AdminGames() { diff --git a/frontend/app/routes/admin.games_.$gameId.tsx b/frontend/app/routes/admin.games_.$gameId.tsx index ff8f136..1ad8a2a 100644 --- a/frontend/app/routes/admin.games_.$gameId.tsx +++ b/frontend/app/routes/admin.games_.$gameId.tsx @@ -5,7 +5,7 @@ import type { } from "@remix-run/node"; import { useLoaderData, Form } from "@remix-run/react"; import { isAuthenticated } from "../.server/auth"; -import { apiClient } from "../.server/api/client"; +import { adminApiPutGame, adminApiGetGame } from "../.server/api/client"; export const meta: MetaFunction<typeof loader> = ({ data }) => { return [ @@ -25,20 +25,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { throw new Error("Unauthorized"); } const { gameId } = params; - const { data, error } = await apiClient.GET("/admin/games/{game_id}", { - params: { - path: { - game_id: Number(gameId), - }, - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return { game: data.game }; + const { game } = await adminApiGetGame(token, Number(gameId)); + return { game }; } export async function action({ request, params }: ActionFunctionArgs) { @@ -63,22 +51,9 @@ export async function action({ request, params }: ActionFunctionArgs) { throw new Error("Invalid action"); } - const { error } = await apiClient.PUT("/admin/games/{game_id}", { - params: { - path: { - game_id: Number(gameId), - }, - header: { - Authorization: `Bearer ${token}`, - }, - }, - body: { - state: nextState, - }, + await adminApiPutGame(token, Number(gameId), { + state: nextState, }); - if (error) { - throw new Error(error.message); - } return null; } diff --git a/frontend/app/routes/admin.users.tsx b/frontend/app/routes/admin.users.tsx index 6c9b60d..c2e4cc7 100644 --- a/frontend/app/routes/admin.users.tsx +++ b/frontend/app/routes/admin.users.tsx @@ -1,7 +1,7 @@ import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { isAuthenticated } from "../.server/auth"; -import { apiClient } from "../.server/api/client"; +import { adminApiGetUsers } from "../.server/api/client"; export const meta: MetaFunction = () => { return [{ title: "[Admin] Users | iOSDC Japan 2024 Albatross.swift" }]; @@ -14,17 +14,8 @@ export async function loader({ request }: LoaderFunctionArgs) { if (!user.is_admin) { throw new Error("Unauthorized"); } - const { data, error } = await apiClient.GET("/admin/users", { - params: { - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return { users: data.users }; + const { users } = await adminApiGetUsers(token); + return { users }; } export default function AdminUsers() { diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx index 3cc8e13..b93e5d1 100644 --- a/frontend/app/routes/dashboard.tsx +++ b/frontend/app/routes/dashboard.tsx @@ -2,7 +2,7 @@ import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { redirect } from "@remix-run/node"; import { Link, useLoaderData, Form } from "@remix-run/react"; import { isAuthenticated } from "../.server/auth"; -import { apiClient } from "../.server/api/client"; +import { apiGetGames } from "../.server/api/client"; export const meta: MetaFunction = () => { return [{ title: "Dashboard | iOSDC Japan 2024 Albatross.swift" }]; @@ -15,22 +15,10 @@ export async function loader({ request }: LoaderFunctionArgs) { if (user.is_admin) { return redirect("/admin/dashboard"); } - const { data, error } = await apiClient.GET("/games", { - params: { - query: { - player_id: user.user_id, - }, - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } + const { games } = await apiGetGames(token); return { user, - games: data.games, + games, }; } diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx index 919d8df..248c4e4 100644 --- a/frontend/app/routes/golf.$gameId.play.tsx +++ b/frontend/app/routes/golf.$gameId.play.tsx @@ -2,7 +2,7 @@ import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { ClientOnly } from "remix-utils/client-only"; import { isAuthenticated } from "../.server/auth"; -import { apiClient } from "../.server/api/client"; +import { apiGetGame, apiGetToken } from "../.server/api/client"; import GolfPlayApp from "../components/GolfPlayApp.client"; import GolfPlayAppConnecting from "../components/GolfPlayApps/GolfPlayAppConnecting"; @@ -22,34 +22,10 @@ export async function loader({ params, request }: LoaderFunctionArgs) { }); const fetchGame = async () => { - const { data, error } = await apiClient.GET("/games/{game_id}", { - params: { - path: { - game_id: Number(params.gameId), - }, - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return data.game; + return (await apiGetGame(token, Number(params.gameId))).game; }; - const fetchSockToken = async () => { - const { data, error } = await apiClient.GET("/token", { - params: { - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return data.token; + return (await apiGetToken(token)).token; }; const [game, sockToken] = await Promise.all([fetchGame(), fetchSockToken()]); diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx index c2e269e..1a14e78 100644 --- a/frontend/app/routes/golf.$gameId.watch.tsx +++ b/frontend/app/routes/golf.$gameId.watch.tsx @@ -2,7 +2,7 @@ import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { ClientOnly } from "remix-utils/client-only"; import { isAuthenticated } from "../.server/auth"; -import { apiClient } from "../.server/api/client"; +import { apiGetGame, apiGetToken } from "../.server/api/client"; import GolfWatchApp from "../components/GolfWatchApp.client"; import GolfWatchAppConnecting from "../components/GolfWatchApps/GolfWatchAppConnecting"; @@ -22,34 +22,10 @@ export async function loader({ params, request }: LoaderFunctionArgs) { }); const fetchGame = async () => { - const { data, error } = await apiClient.GET("/games/{game_id}", { - params: { - path: { - game_id: Number(params.gameId), - }, - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return data.game; + return (await apiGetGame(token, Number(params.gameId))).game; }; - const fetchSockToken = async () => { - const { data, error } = await apiClient.GET("/token", { - params: { - header: { - Authorization: `Bearer ${token}`, - }, - }, - }); - if (error) { - throw new Error(error.message); - } - return data.token; + return (await apiGetToken(token)).token; }; const [game, sockToken] = await Promise.all([fetchGame(), fetchSockToken()]); diff --git a/frontend/package.json b/frontend/package.json index aefb693..12aa6ba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "build": "remix vite:build", "dev": "remix vite:dev", "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", + "openapi-typescript": "npx --no-install openapi-typescript --output ./app/.server/api/schema.d.ts ../openapi.yaml", "start": "remix-serve ./build/server/index.js", "typecheck": "tsc" }, diff --git a/openapi.yaml b/openapi.yaml index f28452b..683fadf 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5,6 +5,7 @@ info: paths: /login: post: + operationId: postLogin summary: User login requestBody: required: true @@ -36,26 +37,13 @@ paths: required: - token '401': - description: Invalid username or password - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Invalid credentials" - required: - - message + $ref: '#/components/responses/Unauthorized' /token: get: + operationId: getToken summary: Get a short-lived access token parameters: - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' responses: '200': description: Successfully authenticated @@ -69,32 +57,14 @@ paths: example: "xxxxx.xxxxx.xxxxx" required: - token - '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + '401': + $ref: '#/components/responses/Unauthorized' /games: get: + operationId: getGames summary: List games parameters: - - in: query - name: player_id - schema: - type: integer - required: false - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' responses: '200': description: List of games @@ -109,32 +79,17 @@ paths: $ref: '#/components/schemas/Game' required: - games + '401': + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + $ref: '#/components/responses/Forbidden' /games/{game_id}: get: + operationId: getGame summary: Get a game parameters: - - in: path - name: game_id - schema: - type: integer - required: true - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' + - $ref: '#/components/parameters/path_game_id' responses: '200': description: A game @@ -147,39 +102,18 @@ paths: $ref: '#/components/schemas/Game' required: - game + '401': + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + $ref: '#/components/responses/Forbidden' '404': - description: Not found - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Not found" - required: - - message + $ref: '#/components/responses/NotFound' /admin/users: get: + operationId: adminGetUsers summary: List all users parameters: - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' responses: '200': description: List of users @@ -194,27 +128,16 @@ paths: $ref: '#/components/schemas/User' required: - users + '401': + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + $ref: '#/components/responses/Forbidden' /admin/games: get: + operationId: adminGetGames summary: List games parameters: - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' responses: '200': description: List of games @@ -229,32 +152,17 @@ paths: $ref: '#/components/schemas/Game' required: - games + '401': + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + $ref: '#/components/responses/Forbidden' /admin/games/{game_id}: get: + operationId: adminGetGame summary: Get a game parameters: - - in: path - name: game_id - schema: - type: integer - required: true - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' + - $ref: '#/components/parameters/path_game_id' responses: '200': description: A game @@ -267,43 +175,18 @@ paths: $ref: '#/components/schemas/Game' required: - game + '401': + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + $ref: '#/components/responses/Forbidden' '404': - description: Not found - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Not found" - required: - - message + $ref: '#/components/responses/NotFound' put: + operationId: adminPutGame summary: Update a game parameters: - - in: path - name: game_id - schema: - type: integer - required: true - - in: header - name: Authorization - schema: - type: string - required: true + - $ref: '#/components/parameters/header_authorization' + - $ref: '#/components/parameters/path_game_id' requestBody: required: true content: @@ -340,43 +223,61 @@ paths: '204': description: Successfully updated '400': - description: Invalid request - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Invalid request" - required: - - message + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Forbidden operation" - required: - - message + $ref: '#/components/responses/Forbidden' '404': - description: Not found - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "Not found" - required: - - message + $ref: '#/components/responses/NotFound' components: + parameters: + header_authorization: + in: header + name: Authorization + schema: + type: string + required: true + path_game_id: + in: path + name: game_id + schema: + type: integer + required: true + responses: + BadRequest: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Forbidden: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + NotFound: + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' schemas: + Error: + type: object + properties: + message: + type: string + example: "Invalid request" + required: + - message User: type: object properties: |
