From dd3440009ae75ecc00e10e48b014ef23bb446964 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 4 Aug 2024 17:06:23 +0900 Subject: feat(frontend): remove admin pages --- frontend/app/.server/auth.ts | 10 --- frontend/app/routes/admin.dashboard.tsx | 29 --------- frontend/app/routes/admin.games.tsx | 35 ---------- frontend/app/routes/admin.games_.$gameId.tsx | 95 ---------------------------- frontend/app/routes/admin.tsx | 8 --- frontend/app/routes/admin.users.tsx | 34 ---------- 6 files changed, 211 deletions(-) delete mode 100644 frontend/app/routes/admin.dashboard.tsx delete mode 100644 frontend/app/routes/admin.games.tsx delete mode 100644 frontend/app/routes/admin.games_.$gameId.tsx delete mode 100644 frontend/app/routes/admin.tsx delete mode 100644 frontend/app/routes/admin.users.tsx (limited to 'frontend') diff --git a/frontend/app/.server/auth.ts b/frontend/app/.server/auth.ts index a4811e2..d5ffe0f 100644 --- a/frontend/app/.server/auth.ts +++ b/frontend/app/.server/auth.ts @@ -40,16 +40,6 @@ export async function ensureUserLoggedIn( return { user, token }; } -export async function ensureAdminUserLoggedIn( - request: Request | Session, -): Promise<{ user: User; token: string }> { - const { user, token } = await ensureUserLoggedIn(request); - if (!user.is_admin) { - throw new Error("Forbidden"); - } - return { user, token }; -} - export async function ensureUserNotLoggedIn( request: Request | Session, ): Promise { diff --git a/frontend/app/routes/admin.dashboard.tsx b/frontend/app/routes/admin.dashboard.tsx deleted file mode 100644 index 8a0c9a8..0000000 --- a/frontend/app/routes/admin.dashboard.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; -import { Form, Link } from "@remix-run/react"; -import { ensureAdminUserLoggedIn } from "../.server/auth"; - -export const meta: MetaFunction = () => [ - { title: "[Admin] Dashboard | iOSDC Japan 2024 Albatross.swift" }, -]; - -export async function loader({ request }: LoaderFunctionArgs) { - await ensureAdminUserLoggedIn(request); - return null; -} - -export default function AdminDashboard() { - return ( -
-

[Admin] Dashboard

-

- Users -

-

- Games -

-
- -
-
- ); -} diff --git a/frontend/app/routes/admin.games.tsx b/frontend/app/routes/admin.games.tsx deleted file mode 100644 index f9d15f7..0000000 --- a/frontend/app/routes/admin.games.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; -import { Link, useLoaderData } from "@remix-run/react"; -import { adminApiGetGames } from "../.server/api/client"; -import { ensureAdminUserLoggedIn } from "../.server/auth"; - -export const meta: MetaFunction = () => [ - { title: "[Admin] Games | iOSDC Japan 2024 Albatross.swift" }, -]; - -export async function loader({ request }: LoaderFunctionArgs) { - const { token } = await ensureAdminUserLoggedIn(request); - const { games } = await adminApiGetGames(token); - return { games }; -} - -export default function AdminGames() { - const { games } = useLoaderData()!; - - return ( -
-
-

[Admin] Games

-
    - {games.map((game) => ( -
  • - - {game.display_name} (id={game.game_id}) - -
  • - ))} -
-
-
- ); -} diff --git a/frontend/app/routes/admin.games_.$gameId.tsx b/frontend/app/routes/admin.games_.$gameId.tsx deleted file mode 100644 index c4d75c1..0000000 --- a/frontend/app/routes/admin.games_.$gameId.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import type { - ActionFunctionArgs, - LoaderFunctionArgs, - MetaFunction, -} from "@remix-run/node"; -import { Form, useLoaderData } from "@remix-run/react"; -import { adminApiGetGame, adminApiPutGame } from "../.server/api/client"; -import { ensureAdminUserLoggedIn } from "../.server/auth"; - -export const meta: MetaFunction = ({ data }) => [ - { - title: data - ? `[Admin] Game Edit ${data.game.display_name} | iOSDC Japan 2024 Albatross.swift` - : "[Admin] Game Edit | iOSDC Japan 2024 Albatross.swift", - }, -]; - -export async function loader({ request, params }: LoaderFunctionArgs) { - const { token } = await ensureAdminUserLoggedIn(request); - const { gameId } = params; - const { game } = await adminApiGetGame(token, Number(gameId)); - return { game }; -} - -export async function action({ request, params }: ActionFunctionArgs) { - const { token } = await ensureAdminUserLoggedIn(request); - const { gameId } = params; - - const formData = await request.formData(); - const action = formData.get("action"); - - const nextState = - action === "open" - ? "waiting_entries" - : action === "start" - ? "prepare" - : null; - if (!nextState) { - throw new Error("Invalid action"); - } - - await adminApiPutGame(token, Number(gameId), { - state: nextState, - }); - return null; -} - -export default function AdminGameEdit() { - const { game } = useLoaderData()!; - - return ( -
-
-

[Admin] Game Edit {game.display_name}

-
    -
  • ID: {game.game_id}
  • -
  • State: {game.state}
  • -
  • Display Name: {game.display_name}
  • -
  • Duration Seconds: {game.duration_seconds}
  • -
  • - Started At:{" "} - {game.started_at - ? new Date(game.started_at * 1000).toString() - : "-"} -
  • -
  • Problem ID: {game.problem ? game.problem.problem_id : "-"}
  • -
-
-
-
- -
-
- -
-
-
-
-
- ); -} diff --git a/frontend/app/routes/admin.tsx b/frontend/app/routes/admin.tsx deleted file mode 100644 index ceef37e..0000000 --- a/frontend/app/routes/admin.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { LinksFunction } from "@remix-run/node"; -import normalizeCss from "sakura.css/css/normalize.css?url"; -import sakuraCss from "sakura.css/css/sakura.css?url"; - -export const links: LinksFunction = () => [ - { rel: "stylesheet", href: normalizeCss }, - { rel: "stylesheet", href: sakuraCss }, -]; diff --git a/frontend/app/routes/admin.users.tsx b/frontend/app/routes/admin.users.tsx deleted file mode 100644 index c403285..0000000 --- a/frontend/app/routes/admin.users.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; -import { useLoaderData } from "@remix-run/react"; -import { adminApiGetUsers } from "../.server/api/client"; -import { ensureAdminUserLoggedIn } from "../.server/auth"; - -export const meta: MetaFunction = () => [ - { title: "[Admin] Users | iOSDC Japan 2024 Albatross.swift" }, -]; - -export async function loader({ request }: LoaderFunctionArgs) { - const { token } = await ensureAdminUserLoggedIn(request); - const { users } = await adminApiGetUsers(token); - return { users }; -} - -export default function AdminUsers() { - const { users } = useLoaderData()!; - - return ( -
-
-

[Admin] Users

-
    - {users.map((user) => ( -
  • - {user.display_name} (id={user.user_id} username={user.username}) - {user.is_admin && admin} -
  • - ))} -
-
-
- ); -} -- cgit v1.2.3-70-g09d2 From 2034d5efe124ffa8a9bb56821a9dfcfea27425ff Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 4 Aug 2024 17:09:39 +0900 Subject: feat(frontend): redirect to /admin/dashboard if logged-in user is admin --- frontend/app/routes/dashboard.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'frontend') diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx index 229375c..e23d7aa 100644 --- a/frontend/app/routes/dashboard.tsx +++ b/frontend/app/routes/dashboard.tsx @@ -11,7 +11,11 @@ export const meta: MetaFunction = () => [ export async function loader({ request }: LoaderFunctionArgs) { const { user, token } = await ensureUserLoggedIn(request); if (user.is_admin) { - return redirect("/admin/dashboard"); + return redirect( + process.env.NODE_ENV === "development" + ? "http://localhost:8002/admin/dashboard" + : "/admin/dashboard", + ); } const { games } = await apiGetGames(token); return { @@ -26,10 +30,7 @@ export default function Dashboard() { return (
-

- {user.username}{" "} - {user.is_admin && admin} -

+

{user.username}

User

    -- cgit v1.2.3-70-g09d2 From 0f0324b396f3eab53606c8f770d26337dd0e291a Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 4 Aug 2024 20:33:37 +0900 Subject: feat: authenticate users in admin pages --- backend/admin/handlers.go | 21 +++++++++++++++++++++ backend/main.go | 8 +++++--- frontend/app/.server/auth.ts | 35 +++++++++++++++++++++++++++++++---- frontend/app/.server/cookie.ts | 41 +++++++++++++++++++++++++++++++++++++++++ frontend/app/.server/session.ts | 16 ++++++++++------ frontend/package-lock.json | 1 + frontend/package.json | 1 + 7 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 frontend/app/.server/cookie.ts (limited to 'frontend') diff --git a/backend/admin/handlers.go b/backend/admin/handlers.go index f81856c..14523e6 100644 --- a/backend/admin/handlers.go +++ b/backend/admin/handlers.go @@ -10,6 +10,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" + "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" "github.com/nsfisis/iosdc-japan-2024-albatross/backend/db" ) @@ -31,8 +32,28 @@ func NewAdminHandler(q *db.Queries, hubs GameHubsInterface) *AdminHandler { } } +func newAdminMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + jwt, err := c.Cookie("albatross_token") + if err != nil { + return c.Redirect(http.StatusSeeOther, "/login") + } + claims, err := auth.ParseJWT(jwt.Value) + if err != nil { + return c.Redirect(http.StatusSeeOther, "/login") + } + if !claims.IsAdmin { + return echo.NewHTTPError(http.StatusForbidden) + } + return next(c) + } + } +} + func (h *AdminHandler) RegisterHandlers(g *echo.Group) { g.Use(newAssetsMiddleware()) + g.Use(newAdminMiddleware()) g.GET("/dashboard", h.getDashboard) g.GET("/users", h.getUsers) diff --git a/backend/main.go b/backend/main.go index 2d38ee5..e2e4bbd 100644 --- a/backend/main.go +++ b/backend/main.go @@ -83,9 +83,11 @@ func main() { adminGroup := e.Group("/admin") adminHandler.RegisterHandlers(adminGroup) - // For local dev: - // This is never used in production because the reverse proxy sends /logout - // to the app server. + // For local dev: This is never used in production because the reverse + // proxy sends /login and /logout to the app server. + e.GET("/login", func(c echo.Context) error { + return c.Redirect(http.StatusPermanentRedirect, "http://localhost:5173/login") + }) e.POST("/logout", func(c echo.Context) error { return c.Redirect(http.StatusPermanentRedirect, "http://localhost:5173/logout") }) diff --git a/frontend/app/.server/auth.ts b/frontend/app/.server/auth.ts index d5ffe0f..2c9d23c 100644 --- a/frontend/app/.server/auth.ts +++ b/frontend/app/.server/auth.ts @@ -1,10 +1,12 @@ +import { redirect } from "@remix-run/node"; import type { Session } from "@remix-run/server-runtime"; import { jwtDecode } from "jwt-decode"; import { Authenticator } from "remix-auth"; import { FormStrategy } from "remix-auth-form"; import { apiPostLogin } from "./api/client"; import { components } from "./api/schema"; -import { sessionStorage } from "./session"; +import { createUnstructuredCookie } from "./cookie"; +import { cookieOptions, sessionStorage } from "./session"; const authenticator = new Authenticator(sessionStorage); @@ -19,15 +21,40 @@ authenticator.use( export type User = components["schemas"]["User"]; +// This cookie is used to directly store the JWT for the API server. +// Remix's createCookie() returns "structured" cookies, which cannot be reused directly by non-Remix servers. +const tokenCookie = createUnstructuredCookie("albatross_token", cookieOptions); + export async function login(request: Request): Promise { - return await authenticator.authenticate("default", request, { - successRedirect: "/dashboard", + const jwt = await authenticator.authenticate("default", request, { failureRedirect: "/login", }); + + const session = await sessionStorage.getSession( + request.headers.get("cookie"), + ); + session.set(authenticator.sessionKey, jwt); + + throw redirect("/dashboard", { + headers: [ + ["Set-Cookie", await sessionStorage.commitSession(session)], + ["Set-Cookie", await tokenCookie.serialize(jwt)], + ], + }); } export async function logout(request: Request | Session): Promise { - return await authenticator.logout(request, { redirectTo: "/" }); + try { + return await authenticator.logout(request, { redirectTo: "/" }); + } catch (response) { + if (response instanceof Response) { + response.headers.append( + "Set-Cookie", + await tokenCookie.serialize("", { maxAge: 0, expires: new Date(0) }), + ); + } + throw response; + } } export async function ensureUserLoggedIn( diff --git a/frontend/app/.server/cookie.ts b/frontend/app/.server/cookie.ts new file mode 100644 index 0000000..cccbe78 --- /dev/null +++ b/frontend/app/.server/cookie.ts @@ -0,0 +1,41 @@ +import { Cookie, CookieOptions } from "@remix-run/server-runtime"; +import { parse, serialize } from "cookie"; + +// Remix's createCookie() returns "structured" cookies, which are cookies that hold a JSON-encoded object. +// This is not suitable for interoperation with other systems that expect a simple string value. +// This function creates an "unstructured" cookie, a simple plain text. +export function createUnstructuredCookie( + name: string, + cookieOptions?: CookieOptions, +): Cookie { + const { secrets = [], ...options } = { + path: "/", + sameSite: "lax" as const, + ...cookieOptions, + }; + + return { + get name() { + return name; + }, + get isSigned() { + return secrets.length > 0; + }, + get expires() { + return typeof options.maxAge !== "undefined" + ? new Date(Date.now() + options.maxAge * 1000) + : options.expires; + }, + async parse(cookieHeader, parseOptions) { + if (!cookieHeader) return null; + const cookies = parse(cookieHeader, { ...options, ...parseOptions }); + return name in cookies ? cookies[name] : null; + }, + async serialize(value, serializeOptions) { + return serialize(name, value, { + ...options, + ...serializeOptions, + }); + }, + }; +} diff --git a/frontend/app/.server/session.ts b/frontend/app/.server/session.ts index 79810f4..102bcd2 100644 --- a/frontend/app/.server/session.ts +++ b/frontend/app/.server/session.ts @@ -1,13 +1,17 @@ import { createCookieSessionStorage } from "@remix-run/node"; +export const cookieOptions = { + sameSite: "lax" as const, + path: "/", + httpOnly: true, + // secure: process.env.NODE_ENV === "production", + secure: false, // TODO + secrets: ["TODO"], +}; + export const sessionStorage = createCookieSessionStorage({ cookie: { name: "albatross_session", - sameSite: "lax", - path: "/", - httpOnly: true, - secrets: ["TODO"], - // secure: process.env.NODE_ENV === "production", - secure: false, // TODO + ...cookieOptions, }, }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a49235c..0e6f7bf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "@remix-run/node": "^2.10.3", "@remix-run/react": "^2.10.3", "@remix-run/serve": "^2.10.3", + "cookie": "^0.6.0", "isbot": "^5.1.13", "jwt-decode": "^4.0.0", "openapi-fetch": "^0.10.2", diff --git a/frontend/package.json b/frontend/package.json index 30d385a..e4eefac 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@remix-run/node": "^2.10.3", "@remix-run/react": "^2.10.3", "@remix-run/serve": "^2.10.3", + "cookie": "^0.6.0", "isbot": "^5.1.13", "jwt-decode": "^4.0.0", "openapi-fetch": "^0.10.2", -- cgit v1.2.3-70-g09d2 From 3963dedada8fdc43b18dfec1313c184d2f4fdc47 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 4 Aug 2024 20:34:54 +0900 Subject: chore(frontend): remove disused dependency --- frontend/package-lock.json | 6 ------ frontend/package.json | 1 - 2 files changed, 7 deletions(-) (limited to 'frontend') diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0e6f7bf..d5c12fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,7 +19,6 @@ "remix-auth": "^3.7.0", "remix-auth-form": "^1.5.0", "remix-utils": "^7.6.0", - "sakura.css": "^1.5.0", "use-debounce": "^10.0.1" }, "devDependencies": { @@ -9674,11 +9673,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/sakura.css": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sakura.css/-/sakura.css-1.5.0.tgz", - "integrity": "sha512-AcAZa9F4SCs2xaKLWcXQxJxKfeod2PN3sR31+R22MKuyoJxNChH1wBG4mQaY9gVpJ3VpNA1XHPOrOM9hFo9cSw==" - }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index e4eefac..44af089 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,6 @@ "remix-auth": "^3.7.0", "remix-auth-form": "^1.5.0", "remix-utils": "^7.6.0", - "sakura.css": "^1.5.0", "use-debounce": "^10.0.1" }, "devDependencies": { -- cgit v1.2.3-70-g09d2 From f4bae7f755ca25b2547dc98b2db2fdb255948bc5 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 4 Aug 2024 20:48:50 +0900 Subject: chore: remove admin APIs to communicate between app-server and api-server --- backend/api/generated.go | 561 ++--------------------------------- backend/api/handler_wrapper.go | 76 ----- backend/api/handlers.go | 189 +----------- backend/game/models.go | 14 +- frontend/app/.server/api/client.ts | 49 +-- frontend/app/.server/api/schema.d.ts | 177 ----------- openapi.yaml | 122 -------- 7 files changed, 41 insertions(+), 1147 deletions(-) (limited to 'frontend') diff --git a/backend/api/generated.go b/backend/api/generated.go index f7da9ee..ea1c315 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -24,13 +24,13 @@ import ( // Defines values for GameState. const ( - GameStateClosed GameState = "closed" - GameStateFinished GameState = "finished" - GameStateGaming GameState = "gaming" - GameStatePrepare GameState = "prepare" - GameStateStarting GameState = "starting" - GameStateWaitingEntries GameState = "waiting_entries" - GameStateWaitingStart GameState = "waiting_start" + Closed GameState = "closed" + Finished GameState = "finished" + Gaming GameState = "gaming" + Prepare GameState = "prepare" + Starting GameState = "starting" + WaitingEntries GameState = "waiting_entries" + WaitingStart GameState = "waiting_start" ) // Defines values for GamePlayerMessageS2CExecResultPayloadStatus. @@ -43,17 +43,6 @@ const ( GameWatcherMessageS2CExecResultPayloadStatusSuccess GameWatcherMessageS2CExecResultPayloadStatus = "success" ) -// Defines values for AdminPutGameJSONBodyState. -const ( - 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"` @@ -241,38 +230,6 @@ type NotFound = Error // Unauthorized defines model for Unauthorized. type Unauthorized = Error -// AdminGetGamesParams defines parameters for AdminGetGames. -type AdminGetGamesParams struct { - Authorization HeaderAuthorization `json:"Authorization"` -} - -// AdminGetGameParams defines parameters for AdminGetGame. -type AdminGetGameParams struct { - Authorization HeaderAuthorization `json:"Authorization"` -} - -// 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"` -} - -// AdminPutGameParams defines parameters for AdminPutGame. -type AdminPutGameParams struct { - Authorization HeaderAuthorization `json:"Authorization"` -} - -// AdminPutGameJSONBodyState defines parameters for AdminPutGame. -type AdminPutGameJSONBodyState string - -// AdminGetUsersParams defines parameters for AdminGetUsers. -type AdminGetUsersParams struct { - Authorization HeaderAuthorization `json:"Authorization"` -} - // GetGamesParams defines parameters for GetGames. type GetGamesParams struct { Authorization HeaderAuthorization `json:"Authorization"` @@ -294,9 +251,6 @@ type GetTokenParams struct { Authorization HeaderAuthorization `json:"Authorization"` } -// AdminPutGameJSONRequestBody defines body for AdminPutGame for application/json ContentType. -type AdminPutGameJSONRequestBody AdminPutGameJSONBody - // PostLoginJSONRequestBody defines body for PostLogin for application/json ContentType. type PostLoginJSONRequestBody PostLoginJSONBody @@ -690,18 +644,6 @@ func (t *GameWatcherMessageS2C) UnmarshalJSON(b []byte) error { // ServerInterface represents all server handlers. type ServerInterface interface { - // List games - // (GET /admin/games) - AdminGetGames(ctx echo.Context, params AdminGetGamesParams) error - // Get a game - // (GET /admin/games/{game_id}) - AdminGetGame(ctx echo.Context, gameID PathGameID, params AdminGetGameParams) error - // Update a game - // (PUT /admin/games/{game_id}) - AdminPutGame(ctx echo.Context, gameID PathGameID, params AdminPutGameParams) error - // List all users - // (GET /admin/users) - AdminGetUsers(ctx echo.Context, params AdminGetUsersParams) error // List games // (GET /games) GetGames(ctx echo.Context, params GetGamesParams) error @@ -721,144 +663,6 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } -// 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 AdminGetGamesParams - - headers := ctx.Request().Header - // ------------- Required header parameter "Authorization" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization HeaderAuthorization - n := len(valueList) - if n != 1 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) - } - - err = runtime.BindStyledParameterWithOptions("simple", "Authorization", valueList[0], &Authorization, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter Authorization: %s", err)) - } - - params.Authorization = Authorization - } else { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter Authorization is required, but not found")) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.AdminGetGames(ctx, params) - return err -} - -// AdminGetGame converts echo context to params. -func (w *ServerInterfaceWrapper) AdminGetGame(ctx echo.Context) error { - var err error - // ------------- Path parameter "game_id" ------------- - 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 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter game_id: %s", err)) - } - - // Parameter object where we will unmarshal all parameters from the context - var params AdminGetGameParams - - headers := ctx.Request().Header - // ------------- Required header parameter "Authorization" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization HeaderAuthorization - n := len(valueList) - if n != 1 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) - } - - err = runtime.BindStyledParameterWithOptions("simple", "Authorization", valueList[0], &Authorization, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter Authorization: %s", err)) - } - - params.Authorization = Authorization - } else { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter Authorization is required, but not found")) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.AdminGetGame(ctx, gameID, params) - return err -} - -// AdminPutGame converts echo context to params. -func (w *ServerInterfaceWrapper) AdminPutGame(ctx echo.Context) error { - var err error - // ------------- Path parameter "game_id" ------------- - 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 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter game_id: %s", err)) - } - - // Parameter object where we will unmarshal all parameters from the context - var params AdminPutGameParams - - headers := ctx.Request().Header - // ------------- Required header parameter "Authorization" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization HeaderAuthorization - n := len(valueList) - if n != 1 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) - } - - err = runtime.BindStyledParameterWithOptions("simple", "Authorization", valueList[0], &Authorization, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter Authorization: %s", err)) - } - - params.Authorization = Authorization - } else { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter Authorization is required, but not found")) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.AdminPutGame(ctx, gameID, params) - return err -} - -// 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 AdminGetUsersParams - - headers := ctx.Request().Header - // ------------- Required header parameter "Authorization" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Authorization")]; found { - var Authorization HeaderAuthorization - n := len(valueList) - if n != 1 { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for Authorization, got %d", n)) - } - - err = runtime.BindStyledParameterWithOptions("simple", "Authorization", valueList[0], &Authorization, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter Authorization: %s", err)) - } - - params.Authorization = Authorization - } else { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter Authorization is required, but not found")) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.AdminGetUsers(ctx, params) - return err -} - // GetGames converts echo context to params. func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error { var err error @@ -996,10 +800,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } - 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.GetGame) router.POST(baseURL+"/login", wrapper.PostLogin) @@ -1015,181 +815,6 @@ type NotFoundJSONResponse Error type UnauthorizedJSONResponse Error -type AdminGetGamesRequestObject struct { - Params AdminGetGamesParams -} - -type AdminGetGamesResponseObject interface { - VisitAdminGetGamesResponse(w http.ResponseWriter) error -} - -type AdminGetGames200JSONResponse struct { - Games []Game `json:"games"` -} - -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 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) -} - -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 AdminGetGameRequestObject struct { - GameID PathGameID `json:"game_id"` - Params AdminGetGameParams -} - -type AdminGetGameResponseObject interface { - VisitAdminGetGameResponse(w http.ResponseWriter) error -} - -type AdminGetGame200JSONResponse struct { - Game Game `json:"game"` -} - -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 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) -} - -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 AdminGetGame404JSONResponse struct{ NotFoundJSONResponse } - -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 AdminPutGameRequestObject struct { - GameID PathGameID `json:"game_id"` - Params AdminPutGameParams - Body *AdminPutGameJSONRequestBody -} - -type AdminPutGameResponseObject interface { - VisitAdminPutGameResponse(w http.ResponseWriter) error -} - -type AdminPutGame204Response struct { -} - -func (response AdminPutGame204Response) VisitAdminPutGameResponse(w http.ResponseWriter) error { - w.WriteHeader(204) - return nil -} - -type AdminPutGame400JSONResponse struct{ BadRequestJSONResponse } - -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 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) -} - -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 AdminPutGame404JSONResponse struct{ NotFoundJSONResponse } - -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 AdminGetUsersRequestObject struct { - Params AdminGetUsersParams -} - -type AdminGetUsersResponseObject interface { - VisitAdminGetUsersResponse(w http.ResponseWriter) error -} - -type AdminGetUsers200JSONResponse struct { - Users []User `json:"users"` -} - -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 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) -} - -type AdminGetUsers403JSONResponse struct{ ForbiddenJSONResponse } - -func (response AdminGetUsers403JSONResponse) VisitAdminGetUsersResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(403) - - return json.NewEncoder(w).Encode(response) -} - type GetGamesRequestObject struct { Params GetGamesParams } @@ -1332,18 +957,6 @@ func (response GetToken401JSONResponse) VisitGetTokenResponse(w http.ResponseWri // StrictServerInterface represents all server handlers. type StrictServerInterface interface { - // List games - // (GET /admin/games) - AdminGetGames(ctx context.Context, request AdminGetGamesRequestObject) (AdminGetGamesResponseObject, error) - // Get a game - // (GET /admin/games/{game_id}) - AdminGetGame(ctx context.Context, request AdminGetGameRequestObject) (AdminGetGameResponseObject, error) - // Update a game - // (PUT /admin/games/{game_id}) - AdminPutGame(ctx context.Context, request AdminPutGameRequestObject) (AdminPutGameResponseObject, error) - // List all users - // (GET /admin/users) - AdminGetUsers(ctx context.Context, request AdminGetUsersRequestObject) (AdminGetUsersResponseObject, error) // List games // (GET /games) GetGames(ctx context.Context, request GetGamesRequestObject) (GetGamesResponseObject, error) @@ -1370,114 +983,6 @@ type strictHandler struct { middlewares []StrictMiddlewareFunc } -// 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.AdminGetGames(ctx.Request().Context(), request.(AdminGetGamesRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "AdminGetGames") - } - - response, err := handler(ctx, request) - - if err != nil { - return err - } 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 -} - -// 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.AdminGetGame(ctx.Request().Context(), request.(AdminGetGameRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "AdminGetGame") - } - - response, err := handler(ctx, request) - - if err != nil { - return err - } 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 -} - -// 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 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.AdminPutGame(ctx.Request().Context(), request.(AdminPutGameRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "AdminPutGame") - } - - response, err := handler(ctx, request) - - if err != nil { - return err - } 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 -} - -// 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.AdminGetUsers(ctx.Request().Context(), request.(AdminGetUsersRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "AdminGetUsers") - } - - response, err := handler(ctx, request) - - if err != nil { - return err - } else if validResponse, ok := response.(AdminGetUsersResponseObject); ok { - return validResponse.VisitAdminGetUsersResponse(ctx.Response()) - } else if response != nil { - return fmt.Errorf("unexpected response type: %T", response) - } - return nil -} - // GetGames operation middleware func (sh *strictHandler) GetGames(ctx echo.Context, params GetGamesParams) error { var request GetGamesRequestObject @@ -1586,33 +1091,31 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "wKIoin8DAAD//5oSt8+jKgAA", + "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", + "//9iv6ZqPCEAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/api/handler_wrapper.go b/backend/api/handler_wrapper.go index 939e37a..fdee581 100644 --- a/backend/api/handler_wrapper.go +++ b/backend/api/handler_wrapper.go @@ -39,82 +39,6 @@ func parseJWTClaimsFromAuthorizationHeader(authorization string) (*auth.JWTClaim 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 { diff --git a/backend/api/handlers.go b/backend/api/handlers.go index 8dfc11c..a824f17 100644 --- a/backend/api/handlers.go +++ b/backend/api/handlers.go @@ -4,10 +4,8 @@ import ( "context" "errors" "net/http" - "time" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" "github.com/nsfisis/iosdc-japan-2024-albatross/backend/auth" @@ -23,191 +21,6 @@ type GameHubsInterface interface { StartGame(gameID int) error } -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()) - } - 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, - } - } - return AdminGetGames200JSONResponse{ - Games: games, - }, nil -} - -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 AdminGetGame404JSONResponse{ - NotFoundJSONResponse: NotFoundJSONResponse{ - Message: "Game not found", - }, - }, nil - } else { - return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - var startedAt *int - if row.StartedAt.Valid { - startedAtTimestamp := int(row.StartedAt.Time.Unix()) - startedAt = &startedAtTimestamp - } - var problem *Problem - if row.ProblemID != nil { - if row.Title == nil || row.Description == nil { - panic("inconsistent data") - } - problem = &Problem{ - ProblemID: int(*row.ProblemID), - Title: *row.Title, - Description: *row.Description, - } - } - game := Game{ - GameID: int(row.GameID), - State: GameState(row.State), - DisplayName: row.DisplayName, - DurationSeconds: int(row.DurationSeconds), - StartedAt: startedAt, - Problem: problem, - } - return AdminGetGame200JSONResponse{ - Game: game, - }, 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 - problemID := request.Body.ProblemID - startedAt := request.Body.StartedAt - state := request.Body.State - - game, err := h.q.GetGameByID(ctx, int32(gameID)) - if err != nil { - if err == pgx.ErrNoRows { - return AdminPutGame404JSONResponse{ - NotFoundJSONResponse: NotFoundJSONResponse{ - Message: "Game not found", - }, - }, nil - } else { - return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - - var changedState string - if state != nil { - changedState = string(*state) - // TODO: - if changedState != game.State && changedState == "prepare" { - h.hubs.StartGame(int(gameID)) - } - } else { - changedState = game.State - } - var changedDisplayName string - if displayName != nil { - changedDisplayName = *displayName - } else { - changedDisplayName = game.DisplayName - } - var changedDurationSeconds int32 - if durationSeconds != nil { - changedDurationSeconds = int32(*durationSeconds) - } else { - changedDurationSeconds = game.DurationSeconds - } - var changedStartedAt pgtype.Timestamp - if startedAt != nil { - startedAtValue, err := startedAt.Get() - if err == nil { - changedStartedAt = pgtype.Timestamp{ - Time: time.Unix(int64(startedAtValue), 0), - Valid: true, - } - } - } else { - changedStartedAt = game.StartedAt - } - var changedProblemID *int32 - if problemID != nil { - problemIDValue, err := problemID.Get() - if err == nil { - changedProblemID = new(int32) - *changedProblemID = int32(problemIDValue) - } - } else { - changedProblemID = game.ProblemID - } - - err = h.q.UpdateGame(ctx, db.UpdateGameParams{ - GameID: int32(gameID), - State: changedState, - DisplayName: changedDisplayName, - DurationSeconds: changedDurationSeconds, - StartedAt: changedStartedAt, - ProblemID: changedProblemID, - }) - if err != nil { - return AdminPutGame400JSONResponse{ - BadRequestJSONResponse: BadRequestJSONResponse{ - Message: err.Error(), - }, - }, nil - } - - return AdminPutGame204Response{}, 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()) - } - responseUsers := make([]User, len(users)) - for i, u := range users { - responseUsers[i] = User{ - UserID: int(u.UserID), - Username: u.Username, - DisplayName: u.DisplayName, - IconPath: u.IconPath, - IsAdmin: u.IsAdmin, - } - } - return AdminGetUsers200JSONResponse{ - Users: responseUsers, - }, nil -} - func (h *ApiHandler) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) { username := request.Body.Username password := request.Body.Password @@ -311,7 +124,7 @@ func (h *ApiHandler) GetGame(ctx context.Context, request GetGameRequestObject, if row.Title == nil || row.Description == nil { panic("inconsistent data") } - if user.IsAdmin || (GameState(row.State) != GameStateClosed && GameState(row.State) != GameStateWaitingEntries) { + if user.IsAdmin || (GameState(row.State) != Closed && GameState(row.State) != WaitingEntries) { problem = &Problem{ ProblemID: int(*row.ProblemID), Title: *row.Title, diff --git a/backend/game/models.go b/backend/game/models.go index 13e3d0f..6c299d6 100644 --- a/backend/game/models.go +++ b/backend/game/models.go @@ -9,13 +9,13 @@ import ( type gameState = api.GameState const ( - gameStateClosed gameState = api.GameStateClosed - gameStateWaitingEntries gameState = api.GameStateWaitingEntries - gameStateWaitingStart gameState = api.GameStateWaitingStart - gameStatePrepare gameState = api.GameStatePrepare - gameStateStarting gameState = api.GameStateStarting - gameStateGaming gameState = api.GameStateGaming - gameStateFinished gameState = api.GameStateFinished + gameStateClosed gameState = api.Closed + gameStateWaitingEntries gameState = api.WaitingEntries + gameStateWaitingStart gameState = api.WaitingStart + gameStatePrepare gameState = api.Prepare + gameStateStarting gameState = api.Starting + gameStateGaming gameState = api.Gaming + gameStateFinished gameState = api.Finished ) type game struct { diff --git a/frontend/app/.server/api/client.ts b/frontend/app/.server/api/client.ts index a78180b..0db4c14 100644 --- a/frontend/app/.server/api/client.ts +++ b/frontend/app/.server/api/client.ts @@ -1,5 +1,5 @@ import createClient from "openapi-fetch"; -import type { operations, paths } from "./schema"; +import type { paths } from "./schema"; const apiClient = createClient({ baseUrl: @@ -46,50 +46,3 @@ export async function apiGetToken(token: string) { 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 1c8cead..88067a8 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -72,58 +72,6 @@ export interface paths { patch?: never; trace?: never; }; - "/admin/users": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List all users */ - get: operations["adminGetUsers"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/admin/games": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List games */ - get: operations["adminGetGames"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/admin/games/{game_id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get a game */ - get: operations["adminGetGame"]; - /** Update a game */ - put: operations["adminPutGame"]; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { @@ -433,129 +381,4 @@ export interface operations { 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/openapi.yaml b/openapi.yaml index d04951d..2c91ad1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -108,128 +108,6 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' - /admin/users: - get: - operationId: adminGetUsers - summary: List all users - parameters: - - $ref: '#/components/parameters/header_authorization' - responses: - '200': - description: List of users - content: - application/json: - schema: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - required: - - users - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - /admin/games: - get: - operationId: adminGetGames - summary: List games - parameters: - - $ref: '#/components/parameters/header_authorization' - responses: - '200': - description: List of games - content: - application/json: - schema: - type: object - properties: - games: - type: array - items: - $ref: '#/components/schemas/Game' - required: - - games - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - /admin/games/{game_id}: - get: - operationId: adminGetGame - summary: Get a game - parameters: - - $ref: '#/components/parameters/header_authorization' - - $ref: '#/components/parameters/path_game_id' - responses: - '200': - description: A game - content: - application/json: - schema: - type: object - properties: - game: - $ref: '#/components/schemas/Game' - required: - - game - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - '404': - $ref: '#/components/responses/NotFound' - put: - operationId: adminPutGame - summary: Update a game - parameters: - - $ref: '#/components/parameters/header_authorization' - - $ref: '#/components/parameters/path_game_id' - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - state: - type: string - example: "closed" - enum: - - closed - - waiting_entries - - waiting_start - - prepare - - starting - - gaming - - finished - display_name: - type: string - example: "Game 1" - duration_seconds: - type: integer - example: 360 - started_at: - nullable: true - type: integer - example: 946684800 - problem_id: - nullable: true - type: integer - example: 1 - responses: - '204': - description: Successfully updated - '400': - $ref: '#/components/responses/BadRequest' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - '404': - $ref: '#/components/responses/NotFound' components: parameters: header_authorization: -- cgit v1.2.3-70-g09d2