From f57a290432eb3bdefa00811853d047fa6f03f19f Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Thu, 7 May 2026 18:05:08 +0200 Subject: [PATCH] phase 8: lobby UI + cross-stack lobby command catalog + TS FlatBuffers - Extend pkg/model/lobby and pkg/schema/fbs/lobby.fbs with public-games list, my-applications/invites lists, game-create, application-submit, invite-redeem/decline. Mirror the matching transcoder pairs and Go fixture round-trip tests. - Wire the seven new lobby message types through gateway/internal/backendclient/{routes,lobby_commands}.go with per-command REST helpers, JSON-tolerant decoding of backend wire shapes, and httptest-based unit coverage for success / 4xx / 5xx / 503 across each command. - Introduce TS-side FlatBuffers via the `flatbuffers` runtime dep, a `make fbs-ts` target driving flatc, and the generated bindings under ui/frontend/src/proto/galaxy/fbs. Phase 7's `user.account.get` decode now uses these bindings as well, closing the JSON.parse vs FlatBuffers gap that would have failed against a real local stack. - Replace the placeholder lobby with five sections (my games, pending invitations, my applications, public games, create new game) and the /lobby/create form. Submit-application uses an inline race-name form on the public-game card; create-game keeps name / description / turn_schedule / enrollment_ends_at always visible and the rest under an Advanced toggle with TS-side defaults. - Update lobby/+page.svelte to throw LobbyError on non-ok result codes; GalaxyClient.executeCommand now returns { resultCode, payloadBytes }. - Vitest binding round-trips, lobby.ts wrapper unit tests, lobby-page + lobby-create component tests, Playwright lobby-flow.spec covering create / submit / accept across all four projects. Phase 7 e2e was migrated to the FlatBuffers fixtures and to click+fill against the Safari-autofill readonly inputs. - Mark Phase 8 done in ui/PLAN.md, mirror the wire-format note into Phase 7, append the new lobby commands to gateway/README.md and docs/ARCHITECTURE.md, add ui/docs/lobby.md. Co-Authored-By: Claude Opus 4.7 --- docs/ARCHITECTURE.md | 9 + gateway/README.md | 9 + .../internal/backendclient/lobby_commands.go | 448 ++++++++++++ .../backendclient/lobby_commands_test.go | 512 ++++++++++++++ gateway/internal/backendclient/routes.go | 11 +- pkg/model/lobby/lobby.go | 190 ++++- pkg/schema/fbs/lobby.fbs | 127 +++- .../fbs/lobby/ApplicationSubmitRequest.go | 71 ++ .../fbs/lobby/ApplicationSubmitResponse.go | 65 ++ pkg/schema/fbs/lobby/ApplicationSummary.go | 134 ++++ pkg/schema/fbs/lobby/GameCreateRequest.go | 168 +++++ pkg/schema/fbs/lobby/GameCreateResponse.go | 65 ++ pkg/schema/fbs/lobby/InviteDeclineRequest.go | 71 ++ pkg/schema/fbs/lobby/InviteDeclineResponse.go | 65 ++ pkg/schema/fbs/lobby/InviteRedeemRequest.go | 71 ++ pkg/schema/fbs/lobby/InviteRedeemResponse.go | 65 ++ pkg/schema/fbs/lobby/InviteSummary.go | 171 +++++ .../fbs/lobby/MyApplicationsListRequest.go | 49 ++ .../fbs/lobby/MyApplicationsListResponse.go | 75 ++ pkg/schema/fbs/lobby/MyInvitesListRequest.go | 49 ++ pkg/schema/fbs/lobby/MyInvitesListResponse.go | 75 ++ .../fbs/lobby/PublicGamesListRequest.go | 79 +++ .../fbs/lobby/PublicGamesListResponse.go | 120 ++++ pkg/transcoder/lobby.go | 649 +++++++++++++++++- pkg/transcoder/lobby_test.go | 517 ++++++++++++++ ui/Makefile | 10 +- ui/PLAN.md | 179 ++++- ui/README.md | 24 +- ui/docs/lobby.md | 113 +++ ui/frontend/package.json | 1 + ui/frontend/src/api/galaxy-client.ts | 12 +- ui/frontend/src/api/lobby.ts | 361 ++++++++++ ui/frontend/src/lib/i18n/locales/en.ts | 47 ++ ui/frontend/src/lib/i18n/locales/ru.ts | 47 ++ ui/frontend/src/proto/galaxy/fbs/lobby.ts | 27 + .../fbs/lobby/application-submit-request.ts | 95 +++ .../fbs/lobby/application-submit-response.ts | 77 +++ .../galaxy/fbs/lobby/application-summary.ts | 174 +++++ .../src/proto/galaxy/fbs/lobby/error-body.ts | 95 +++ .../proto/galaxy/fbs/lobby/error-response.ts | 77 +++ .../galaxy/fbs/lobby/game-create-request.ts | 199 ++++++ .../galaxy/fbs/lobby/game-create-response.ts | 77 +++ .../proto/galaxy/fbs/lobby/game-summary.ts | 216 ++++++ .../fbs/lobby/invite-decline-request.ts | 95 +++ .../fbs/lobby/invite-decline-response.ts | 77 +++ .../galaxy/fbs/lobby/invite-redeem-request.ts | 95 +++ .../fbs/lobby/invite-redeem-response.ts | 77 +++ .../proto/galaxy/fbs/lobby/invite-summary.ts | 222 ++++++ .../fbs/lobby/my-applications-list-request.ts | 56 ++ .../lobby/my-applications-list-response.ts | 94 +++ .../galaxy/fbs/lobby/my-games-list-request.ts | 56 ++ .../fbs/lobby/my-games-list-response.ts | 102 +++ .../fbs/lobby/my-invites-list-request.ts | 56 ++ .../fbs/lobby/my-invites-list-response.ts | 94 +++ .../fbs/lobby/open-enrollment-request.ts | 78 +++ .../fbs/lobby/open-enrollment-response.ts | 95 +++ .../fbs/lobby/public-games-list-request.ts | 88 +++ .../fbs/lobby/public-games-list-response.ts | 136 ++++ ui/frontend/src/proto/galaxy/fbs/user.ts | 23 + .../proto/galaxy/fbs/user/account-response.ts | 85 +++ .../src/proto/galaxy/fbs/user/account-view.ts | 275 ++++++++ .../src/proto/galaxy/fbs/user/active-limit.ts | 144 ++++ .../proto/galaxy/fbs/user/active-sanction.ts | 147 ++++ .../src/proto/galaxy/fbs/user/actor-ref.ts | 95 +++ .../device-session-revocation-summary-view.ts | 92 +++ .../galaxy/fbs/user/device-session-view.ts | 171 +++++ .../galaxy/fbs/user/entitlement-snapshot.ts | 173 +++++ .../src/proto/galaxy/fbs/user/error-body.ts | 95 +++ .../proto/galaxy/fbs/user/error-response.ts | 77 +++ .../galaxy/fbs/user/get-my-account-request.ts | 56 ++ .../fbs/user/list-my-sessions-request.ts | 56 ++ .../fbs/user/list-my-sessions-response.ts | 94 +++ .../user/revoke-all-my-sessions-request.ts | 56 ++ .../user/revoke-all-my-sessions-response.ts | 77 +++ .../fbs/user/revoke-my-session-request.ts | 78 +++ .../fbs/user/revoke-my-session-response.ts | 77 +++ .../fbs/user/update-my-profile-request.ts | 78 +++ .../fbs/user/update-my-settings-request.ts | 95 +++ ui/frontend/src/routes/lobby/+page.svelte | 477 +++++++++++-- .../src/routes/lobby/create/+page.svelte | 292 ++++++++ ui/frontend/src/routes/lobby/create/+page.ts | 2 + ui/frontend/tests/e2e/auth-flow.spec.ts | 48 +- ui/frontend/tests/e2e/fixtures/lobby-fbs.ts | 258 +++++++ ui/frontend/tests/e2e/lobby-flow.spec.ts | 340 +++++++++ ui/frontend/tests/galaxy-client.test.ts | 3 +- ui/frontend/tests/lobby-api.test.ts | 335 +++++++++ ui/frontend/tests/lobby-create.test.ts | 195 ++++++ ui/frontend/tests/lobby-fbs.test.ts | 494 +++++++++++++ ui/frontend/tests/lobby-page.test.ts | 361 ++++++++++ ui/pnpm-lock.yaml | 8 + 90 files changed, 11862 insertions(+), 112 deletions(-) create mode 100644 gateway/internal/backendclient/lobby_commands_test.go create mode 100644 pkg/schema/fbs/lobby/ApplicationSubmitRequest.go create mode 100644 pkg/schema/fbs/lobby/ApplicationSubmitResponse.go create mode 100644 pkg/schema/fbs/lobby/ApplicationSummary.go create mode 100644 pkg/schema/fbs/lobby/GameCreateRequest.go create mode 100644 pkg/schema/fbs/lobby/GameCreateResponse.go create mode 100644 pkg/schema/fbs/lobby/InviteDeclineRequest.go create mode 100644 pkg/schema/fbs/lobby/InviteDeclineResponse.go create mode 100644 pkg/schema/fbs/lobby/InviteRedeemRequest.go create mode 100644 pkg/schema/fbs/lobby/InviteRedeemResponse.go create mode 100644 pkg/schema/fbs/lobby/InviteSummary.go create mode 100644 pkg/schema/fbs/lobby/MyApplicationsListRequest.go create mode 100644 pkg/schema/fbs/lobby/MyApplicationsListResponse.go create mode 100644 pkg/schema/fbs/lobby/MyInvitesListRequest.go create mode 100644 pkg/schema/fbs/lobby/MyInvitesListResponse.go create mode 100644 pkg/schema/fbs/lobby/PublicGamesListRequest.go create mode 100644 pkg/schema/fbs/lobby/PublicGamesListResponse.go create mode 100644 pkg/transcoder/lobby_test.go create mode 100644 ui/docs/lobby.md create mode 100644 ui/frontend/src/api/lobby.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/application-summary.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/error-body.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/error-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/game-create-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/game-create-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/game-summary.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/invite-summary.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/account-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/account-view.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/active-limit.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/active-sanction.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/actor-ref.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/device-session-revocation-summary-view.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/device-session-view.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/entitlement-snapshot.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/error-body.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/error-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/get-my-account-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-response.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/update-my-profile-request.ts create mode 100644 ui/frontend/src/proto/galaxy/fbs/user/update-my-settings-request.ts create mode 100644 ui/frontend/src/routes/lobby/create/+page.svelte create mode 100644 ui/frontend/src/routes/lobby/create/+page.ts create mode 100644 ui/frontend/tests/e2e/fixtures/lobby-fbs.ts create mode 100644 ui/frontend/tests/e2e/lobby-flow.spec.ts create mode 100644 ui/frontend/tests/lobby-api.test.ts create mode 100644 ui/frontend/tests/lobby-create.test.ts create mode 100644 ui/frontend/tests/lobby-fbs.test.ts create mode 100644 ui/frontend/tests/lobby-page.test.ts diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 787e93f..6d8ec2a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -145,6 +145,15 @@ because they cross domain boundaries: `X-User-ID`. Public games carry `owner_user_id IS NULL`; the partial index on `(owner_user_id) WHERE visibility = 'private'` keeps the private-owner lookup efficient. +- **Authenticated lobby commands** flow through the gateway envelope + by `message_type`. The catalog is `lobby.my.games.list`, + `lobby.public.games.list`, `lobby.my.applications.list`, + `lobby.my.invites.list`, `lobby.game.create`, + `lobby.game.open-enrollment`, `lobby.application.submit`, + `lobby.invite.redeem`, and `lobby.invite.decline`. Each lands on a + REST handler under `/api/v1/user/lobby/*`; the gateway forces + visibility to `private` on `lobby.game.create` before forwarding, + matching the user-surface invariant above. | Package | Responsibility | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/gateway/README.md b/gateway/README.md index 5e974d9..2146820 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -369,6 +369,15 @@ The current direct `Gateway -> User` self-service boundary uses that pattern: - `user.games.command` - `user.games.order` - `user.games.report` + - `lobby.my.games.list` + - `lobby.my.applications.list` + - `lobby.my.invites.list` + - `lobby.public.games.list` + - `lobby.game.create` + - `lobby.game.open-enrollment` + - `lobby.application.submit` + - `lobby.invite.redeem` + - `lobby.invite.decline` - external payloads and responses: - FlatBuffers - internal downstream transport: diff --git a/gateway/internal/backendclient/lobby_commands.go b/gateway/internal/backendclient/lobby_commands.go index 130e56f..c5bf290 100644 --- a/gateway/internal/backendclient/lobby_commands.go +++ b/gateway/internal/backendclient/lobby_commands.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "strings" + "time" "galaxy/gateway/internal/downstream" lobbymodel "galaxy/model/lobby" @@ -55,12 +56,52 @@ func (c *RESTClient) ExecuteLobbyCommand(ctx context.Context, command downstream return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) } return c.executeLobbyMyGames(ctx, command.UserID) + case lobbymodel.MessageTypePublicGamesList: + req, err := transcoder.PayloadToPublicGamesListRequest(command.PayloadBytes) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyPublicGames(ctx, command.UserID, req) + case lobbymodel.MessageTypeMyApplicationsList: + if _, err := transcoder.PayloadToMyApplicationsListRequest(command.PayloadBytes); err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyMyApplications(ctx, command.UserID) + case lobbymodel.MessageTypeMyInvitesList: + if _, err := transcoder.PayloadToMyInvitesListRequest(command.PayloadBytes); err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyMyInvites(ctx, command.UserID) case lobbymodel.MessageTypeOpenEnrollment: req, err := transcoder.PayloadToOpenEnrollmentRequest(command.PayloadBytes) if err != nil { return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) } return c.executeLobbyOpenEnrollment(ctx, command.UserID, req) + case lobbymodel.MessageTypeGameCreate: + req, err := transcoder.PayloadToGameCreateRequest(command.PayloadBytes) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyGameCreate(ctx, command.UserID, req) + case lobbymodel.MessageTypeApplicationSubmit: + req, err := transcoder.PayloadToApplicationSubmitRequest(command.PayloadBytes) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyApplicationSubmit(ctx, command.UserID, req) + case lobbymodel.MessageTypeInviteRedeem: + req, err := transcoder.PayloadToInviteRedeemRequest(command.PayloadBytes) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyInviteRedeem(ctx, command.UserID, req) + case lobbymodel.MessageTypeInviteDecline: + req, err := transcoder.PayloadToInviteDeclineRequest(command.PayloadBytes) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command %q: %w", command.MessageType, err) + } + return c.executeLobbyInviteDecline(ctx, command.UserID, req) default: return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute lobby command: unsupported message type %q", command.MessageType) } @@ -88,6 +129,81 @@ func (c *RESTClient) executeLobbyMyGames(ctx context.Context, userID string) (do return projectLobbyErrorResponse(status, body) } +func (c *RESTClient) executeLobbyPublicGames(ctx context.Context, userID string, req *lobbymodel.PublicGamesListRequest) (downstream.UnaryResult, error) { + page := req.Page + if page <= 0 { + page = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = 50 + } + target := fmt.Sprintf("%s/api/v1/user/lobby/games?page=%d&page_size=%d", c.baseURL, page, pageSize) + body, status, err := c.do(ctx, http.MethodGet, target, userID, nil) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.public.games.list: %w", err) + } + if status == http.StatusOK { + page, err := decodePublicGamesPage(body) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.PublicGamesListResponseToPayload(page) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, body) +} + +func (c *RESTClient) executeLobbyMyApplications(ctx context.Context, userID string) (downstream.UnaryResult, error) { + body, status, err := c.do(ctx, http.MethodGet, c.baseURL+"/api/v1/user/lobby/my/applications", userID, nil) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.my.applications.list: %w", err) + } + if status == http.StatusOK { + response, err := decodeApplicationsList(body) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.MyApplicationsListResponseToPayload(response) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, body) +} + +func (c *RESTClient) executeLobbyMyInvites(ctx context.Context, userID string) (downstream.UnaryResult, error) { + body, status, err := c.do(ctx, http.MethodGet, c.baseURL+"/api/v1/user/lobby/my/invites", userID, nil) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.my.invites.list: %w", err) + } + if status == http.StatusOK { + response, err := decodeInvitesList(body) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.MyInvitesListResponseToPayload(response) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, body) +} + func (c *RESTClient) executeLobbyOpenEnrollment(ctx context.Context, userID string, req *lobbymodel.OpenEnrollmentRequest) (downstream.UnaryResult, error) { if req == nil || strings.TrimSpace(req.GameID) == "" { return downstream.UnaryResult{}, errors.New("execute lobby.game.open-enrollment: game_id must not be empty") @@ -122,6 +238,338 @@ func (c *RESTClient) executeLobbyOpenEnrollment(ctx context.Context, userID stri return projectLobbyErrorResponse(status, body) } +func (c *RESTClient) executeLobbyGameCreate(ctx context.Context, userID string, req *lobbymodel.GameCreateRequest) (downstream.UnaryResult, error) { + if req == nil || strings.TrimSpace(req.GameName) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.game.create: game_name must not be empty") + } + if strings.TrimSpace(req.TurnSchedule) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.game.create: turn_schedule must not be empty") + } + if strings.TrimSpace(req.TargetEngineVersion) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.game.create: target_engine_version must not be empty") + } + if req.MinPlayers <= 0 || req.MaxPlayers <= 0 { + return downstream.UnaryResult{}, errors.New("execute lobby.game.create: min_players and max_players must be positive") + } + if req.MinPlayers > req.MaxPlayers { + return downstream.UnaryResult{}, errors.New("execute lobby.game.create: min_players must not exceed max_players") + } + if req.EnrollmentEndsAt.IsZero() { + return downstream.UnaryResult{}, errors.New("execute lobby.game.create: enrollment_ends_at must be set") + } + + body := map[string]any{ + "game_name": req.GameName, + "visibility": "private", + "description": req.Description, + "min_players": int32(req.MinPlayers), + "max_players": int32(req.MaxPlayers), + "start_gap_hours": int32(req.StartGapHours), + "start_gap_players": int32(req.StartGapPlayers), + "enrollment_ends_at": req.EnrollmentEndsAt.UTC().Format(time.RFC3339Nano), + "turn_schedule": req.TurnSchedule, + "target_engine_version": req.TargetEngineVersion, + } + payload, status, err := c.do(ctx, http.MethodPost, c.baseURL+"/api/v1/user/lobby/games", userID, body) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.game.create: %w", err) + } + if status == http.StatusOK || status == http.StatusCreated { + summary, err := decodeGameSummaryFromGameDetail(payload) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.GameCreateResponseToPayload(&lobbymodel.GameCreateResponse{Game: summary}) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, payload) +} + +func (c *RESTClient) executeLobbyApplicationSubmit(ctx context.Context, userID string, req *lobbymodel.ApplicationSubmitRequest) (downstream.UnaryResult, error) { + if req == nil || strings.TrimSpace(req.GameID) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.application.submit: game_id must not be empty") + } + if strings.TrimSpace(req.RaceName) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.application.submit: race_name must not be empty") + } + target := c.baseURL + "/api/v1/user/lobby/games/" + url.PathEscape(req.GameID) + "/applications" + body := map[string]any{"race_name": req.RaceName} + payload, status, err := c.do(ctx, http.MethodPost, target, userID, body) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.application.submit: %w", err) + } + if status == http.StatusOK || status == http.StatusCreated { + app, err := decodeApplicationDetail(payload) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.ApplicationSubmitResponseToPayload(&lobbymodel.ApplicationSubmitResponse{Application: app}) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, payload) +} + +func (c *RESTClient) executeLobbyInviteRedeem(ctx context.Context, userID string, req *lobbymodel.InviteRedeemRequest) (downstream.UnaryResult, error) { + if req == nil || strings.TrimSpace(req.GameID) == "" || strings.TrimSpace(req.InviteID) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.invite.redeem: game_id and invite_id must not be empty") + } + target := c.baseURL + "/api/v1/user/lobby/games/" + url.PathEscape(req.GameID) + "/invites/" + url.PathEscape(req.InviteID) + "/redeem" + payload, status, err := c.do(ctx, http.MethodPost, target, userID, nil) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.invite.redeem: %w", err) + } + if status == http.StatusOK { + invite, err := decodeInviteDetail(payload) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.InviteRedeemResponseToPayload(&lobbymodel.InviteRedeemResponse{Invite: invite}) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, payload) +} + +func (c *RESTClient) executeLobbyInviteDecline(ctx context.Context, userID string, req *lobbymodel.InviteDeclineRequest) (downstream.UnaryResult, error) { + if req == nil || strings.TrimSpace(req.GameID) == "" || strings.TrimSpace(req.InviteID) == "" { + return downstream.UnaryResult{}, errors.New("execute lobby.invite.decline: game_id and invite_id must not be empty") + } + target := c.baseURL + "/api/v1/user/lobby/games/" + url.PathEscape(req.GameID) + "/invites/" + url.PathEscape(req.InviteID) + "/decline" + payload, status, err := c.do(ctx, http.MethodPost, target, userID, nil) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("execute lobby.invite.decline: %w", err) + } + if status == http.StatusOK { + invite, err := decodeInviteDetail(payload) + if err != nil { + return downstream.UnaryResult{}, err + } + payloadBytes, err := transcoder.InviteDeclineResponseToPayload(&lobbymodel.InviteDeclineResponse{Invite: invite}) + if err != nil { + return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err) + } + return downstream.UnaryResult{ + ResultCode: lobbyResultCodeOK, + PayloadBytes: payloadBytes, + }, nil + } + return projectLobbyErrorResponse(status, payload) +} + +// decodeGameSummaryFromGameDetail accepts the backend's full +// LobbyGameDetail wire shape and projects it onto the gateway's +// GameSummary contract. It uses non-strict JSON decoding so the +// gateway tolerates the runtime/engine fields it does not forward to +// the UI. +func decodeGameSummaryFromGameDetail(payload []byte) (lobbymodel.GameSummary, error) { + var wire struct { + GameID string `json:"game_id"` + GameName string `json:"game_name"` + GameType string `json:"game_type"` + Status string `json:"status"` + OwnerUserID *string `json:"owner_user_id"` + MinPlayers int `json:"min_players"` + MaxPlayers int `json:"max_players"` + EnrollmentEndsAt time.Time `json:"enrollment_ends_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + if err := json.Unmarshal(payload, &wire); err != nil { + return lobbymodel.GameSummary{}, fmt.Errorf("decode success response: %w", err) + } + owner := "" + if wire.OwnerUserID != nil { + owner = *wire.OwnerUserID + } + return lobbymodel.GameSummary{ + GameID: wire.GameID, + GameName: wire.GameName, + GameType: wire.GameType, + Status: wire.Status, + OwnerUserID: owner, + MinPlayers: wire.MinPlayers, + MaxPlayers: wire.MaxPlayers, + EnrollmentEndsAt: wire.EnrollmentEndsAt.UTC(), + CreatedAt: wire.CreatedAt.UTC(), + UpdatedAt: wire.UpdatedAt.UTC(), + }, nil +} + +func decodePublicGamesPage(payload []byte) (*lobbymodel.PublicGamesListResponse, error) { + var wire struct { + Items []struct { + GameID string `json:"game_id"` + GameName string `json:"game_name"` + GameType string `json:"game_type"` + Status string `json:"status"` + OwnerUserID *string `json:"owner_user_id"` + MinPlayers int `json:"min_players"` + MaxPlayers int `json:"max_players"` + EnrollmentEndsAt time.Time `json:"enrollment_ends_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } `json:"items"` + Page int `json:"page"` + PageSize int `json:"page_size"` + Total int `json:"total"` + } + if err := json.Unmarshal(payload, &wire); err != nil { + return nil, fmt.Errorf("decode success response: %w", err) + } + out := &lobbymodel.PublicGamesListResponse{ + Items: make([]lobbymodel.GameSummary, 0, len(wire.Items)), + Page: wire.Page, + PageSize: wire.PageSize, + Total: wire.Total, + } + for _, w := range wire.Items { + owner := "" + if w.OwnerUserID != nil { + owner = *w.OwnerUserID + } + out.Items = append(out.Items, lobbymodel.GameSummary{ + GameID: w.GameID, + GameName: w.GameName, + GameType: w.GameType, + Status: w.Status, + OwnerUserID: owner, + MinPlayers: w.MinPlayers, + MaxPlayers: w.MaxPlayers, + EnrollmentEndsAt: w.EnrollmentEndsAt.UTC(), + CreatedAt: w.CreatedAt.UTC(), + UpdatedAt: w.UpdatedAt.UTC(), + }) + } + return out, nil +} + +func decodeApplicationsList(payload []byte) (*lobbymodel.MyApplicationsListResponse, error) { + var wire struct { + Items []applicationDetailWire `json:"items"` + } + if err := json.Unmarshal(payload, &wire); err != nil { + return nil, fmt.Errorf("decode success response: %w", err) + } + out := &lobbymodel.MyApplicationsListResponse{ + Items: make([]lobbymodel.ApplicationSummary, 0, len(wire.Items)), + } + for _, w := range wire.Items { + out.Items = append(out.Items, w.toModel()) + } + return out, nil +} + +func decodeApplicationDetail(payload []byte) (lobbymodel.ApplicationSummary, error) { + var wire applicationDetailWire + if err := json.Unmarshal(payload, &wire); err != nil { + return lobbymodel.ApplicationSummary{}, fmt.Errorf("decode success response: %w", err) + } + return wire.toModel(), nil +} + +func decodeInvitesList(payload []byte) (*lobbymodel.MyInvitesListResponse, error) { + var wire struct { + Items []inviteDetailWire `json:"items"` + } + if err := json.Unmarshal(payload, &wire); err != nil { + return nil, fmt.Errorf("decode success response: %w", err) + } + out := &lobbymodel.MyInvitesListResponse{ + Items: make([]lobbymodel.InviteSummary, 0, len(wire.Items)), + } + for _, w := range wire.Items { + out.Items = append(out.Items, w.toModel()) + } + return out, nil +} + +func decodeInviteDetail(payload []byte) (lobbymodel.InviteSummary, error) { + var wire inviteDetailWire + if err := json.Unmarshal(payload, &wire); err != nil { + return lobbymodel.InviteSummary{}, fmt.Errorf("decode success response: %w", err) + } + return wire.toModel(), nil +} + +type applicationDetailWire struct { + ApplicationID string `json:"application_id"` + GameID string `json:"game_id"` + ApplicantUserID string `json:"applicant_user_id"` + RaceName string `json:"race_name"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + DecidedAt *time.Time `json:"decided_at,omitempty"` +} + +func (w applicationDetailWire) toModel() lobbymodel.ApplicationSummary { + out := lobbymodel.ApplicationSummary{ + ApplicationID: w.ApplicationID, + GameID: w.GameID, + ApplicantUserID: w.ApplicantUserID, + RaceName: w.RaceName, + Status: w.Status, + CreatedAt: w.CreatedAt.UTC(), + } + if w.DecidedAt != nil { + t := w.DecidedAt.UTC() + out.DecidedAt = &t + } + return out +} + +type inviteDetailWire struct { + InviteID string `json:"invite_id"` + GameID string `json:"game_id"` + InviterUserID string `json:"inviter_user_id"` + InvitedUserID *string `json:"invited_user_id,omitempty"` + Code *string `json:"code,omitempty"` + RaceName string `json:"race_name"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + DecidedAt *time.Time `json:"decided_at,omitempty"` +} + +func (w inviteDetailWire) toModel() lobbymodel.InviteSummary { + out := lobbymodel.InviteSummary{ + InviteID: w.InviteID, + GameID: w.GameID, + InviterUserID: w.InviterUserID, + RaceName: w.RaceName, + Status: w.Status, + CreatedAt: w.CreatedAt.UTC(), + ExpiresAt: w.ExpiresAt.UTC(), + } + if w.InvitedUserID != nil { + out.InvitedUserID = *w.InvitedUserID + } + if w.Code != nil { + out.Code = *w.Code + } + if w.DecidedAt != nil { + t := w.DecidedAt.UTC() + out.DecidedAt = &t + } + return out +} + func projectLobbyErrorResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) { switch { case statusCode == http.StatusServiceUnavailable: diff --git a/gateway/internal/backendclient/lobby_commands_test.go b/gateway/internal/backendclient/lobby_commands_test.go new file mode 100644 index 0000000..8a87533 --- /dev/null +++ b/gateway/internal/backendclient/lobby_commands_test.go @@ -0,0 +1,512 @@ +package backendclient_test + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "galaxy/gateway/internal/backendclient" + "galaxy/gateway/internal/downstream" + lobbymodel "galaxy/model/lobby" + "galaxy/transcoder" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newAuthCommand(t *testing.T, messageType string, payload []byte) downstream.AuthenticatedCommand { + t.Helper() + return downstream.AuthenticatedCommand{ + MessageType: messageType, + PayloadBytes: payload, + UserID: "user-1", + } +} + +func mustEncode[T any](t *testing.T, encode func(*T) ([]byte, error), value *T) []byte { + t.Helper() + bytes, err := encode(value) + require.NoError(t, err) + return bytes +} + +func TestExecuteLobbyMyGamesListReturnsItems(t *testing.T) { + t.Parallel() + + enrollment := time.Date(2026, 5, 15, 12, 0, 0, 0, time.UTC) + created := time.Date(2026, 5, 7, 10, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + require.Equal(t, "/api/v1/user/lobby/my/games", r.URL.Path) + require.Equal(t, "user-1", r.Header.Get(backendclient.HeaderUserID)) + writeJSON(t, w, http.StatusOK, map[string]any{ + "items": []map[string]any{{ + "game_id": "game-1", + "game_name": "Test Game", + "game_type": "private", + "status": "draft", + "owner_user_id": "user-1", + "min_players": 2, + "max_players": 8, + "enrollment_ends_at": enrollment.Format(time.RFC3339Nano), + "created_at": created.Format(time.RFC3339Nano), + "updated_at": created.Format(time.RFC3339Nano), + }}, + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.MyGamesListRequestToPayload, &lobbymodel.MyGamesListRequest{}) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeMyGamesList, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToMyGamesListResponse(result.PayloadBytes) + require.NoError(t, err) + require.Len(t, decoded.Items, 1) + assert.Equal(t, "game-1", decoded.Items[0].GameID) + assert.Equal(t, enrollment, decoded.Items[0].EnrollmentEndsAt) +} + +func TestExecuteLobbyPublicGamesListPaginatesAndDecodes(t *testing.T) { + t.Parallel() + + enrollment := time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC) + created := time.Date(2026, 5, 1, 12, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + require.Equal(t, "/api/v1/user/lobby/games", r.URL.Path) + require.Equal(t, "2", r.URL.Query().Get("page")) + require.Equal(t, "10", r.URL.Query().Get("page_size")) + writeJSON(t, w, http.StatusOK, map[string]any{ + "items": []map[string]any{{ + "game_id": "public-1", + "game_name": "Open", + "game_type": "public", + "status": "enrollment_open", + "owner_user_id": nil, + "min_players": 4, + "max_players": 12, + "enrollment_ends_at": enrollment.Format(time.RFC3339Nano), + "created_at": created.Format(time.RFC3339Nano), + "updated_at": created.Format(time.RFC3339Nano), + }}, + "page": 2, + "page_size": 10, + "total": 31, + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.PublicGamesListRequestToPayload, &lobbymodel.PublicGamesListRequest{Page: 2, PageSize: 10}) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypePublicGamesList, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToPublicGamesListResponse(result.PayloadBytes) + require.NoError(t, err) + assert.Equal(t, 2, decoded.Page) + assert.Equal(t, 10, decoded.PageSize) + assert.Equal(t, 31, decoded.Total) + require.Len(t, decoded.Items, 1) + assert.Empty(t, decoded.Items[0].OwnerUserID) +} + +func TestExecuteLobbyMyApplicationsList(t *testing.T) { + t.Parallel() + + created := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC) + decided := time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/api/v1/user/lobby/my/applications", r.URL.Path) + writeJSON(t, w, http.StatusOK, map[string]any{ + "items": []map[string]any{ + { + "application_id": "app-1", + "game_id": "public-1", + "applicant_user_id": "user-1", + "race_name": "Vegan Federation", + "status": "pending", + "created_at": created.Format(time.RFC3339Nano), + }, + { + "application_id": "app-2", + "game_id": "public-2", + "applicant_user_id": "user-1", + "race_name": "Lithic Compact", + "status": "approved", + "created_at": created.Format(time.RFC3339Nano), + "decided_at": decided.Format(time.RFC3339Nano), + }, + }, + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.MyApplicationsListRequestToPayload, &lobbymodel.MyApplicationsListRequest{}) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeMyApplicationsList, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToMyApplicationsListResponse(result.PayloadBytes) + require.NoError(t, err) + require.Len(t, decoded.Items, 2) + assert.Equal(t, "pending", decoded.Items[0].Status) + assert.Nil(t, decoded.Items[0].DecidedAt) + require.NotNil(t, decoded.Items[1].DecidedAt) + assert.Equal(t, decided, *decoded.Items[1].DecidedAt) +} + +func TestExecuteLobbyMyInvitesList(t *testing.T) { + t.Parallel() + + created := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC) + expires := time.Date(2026, 5, 8, 10, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/api/v1/user/lobby/my/invites", r.URL.Path) + writeJSON(t, w, http.StatusOK, map[string]any{ + "items": []map[string]any{{ + "invite_id": "invite-1", + "game_id": "private-1", + "inviter_user_id": "user-host", + "invited_user_id": "user-1", + "race_name": "Vegan Federation", + "status": "pending", + "created_at": created.Format(time.RFC3339Nano), + "expires_at": expires.Format(time.RFC3339Nano), + }}, + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.MyInvitesListRequestToPayload, &lobbymodel.MyInvitesListRequest{}) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeMyInvitesList, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToMyInvitesListResponse(result.PayloadBytes) + require.NoError(t, err) + require.Len(t, decoded.Items, 1) + assert.Equal(t, "user-1", decoded.Items[0].InvitedUserID) + assert.Empty(t, decoded.Items[0].Code) + assert.Equal(t, expires, decoded.Items[0].ExpiresAt) +} + +func TestExecuteLobbyGameCreatePostsPrivateAndProjectsToSummary(t *testing.T) { + t.Parallel() + + enrollment := time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC) + created := time.Date(2026, 5, 7, 10, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/api/v1/user/lobby/games", r.URL.Path) + + var body map[string]any + raw, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(raw, &body)) + assert.Equal(t, "private", body["visibility"]) + assert.Equal(t, "First Contact", body["game_name"]) + assert.Equal(t, "0 0 * * *", body["turn_schedule"]) + + // Backend always returns the full GameDetail including runtime + // snapshot fields the gateway must tolerate. + writeJSON(t, w, http.StatusCreated, map[string]any{ + "game_id": "newly-created", + "game_name": "First Contact", + "game_type": "private", + "status": "draft", + "owner_user_id": "user-1", + "min_players": 2, + "max_players": 8, + "enrollment_ends_at": enrollment.Format(time.RFC3339Nano), + "created_at": created.Format(time.RFC3339Nano), + "updated_at": created.Format(time.RFC3339Nano), + "visibility": "private", + "description": "", + "turn_schedule": "0 0 * * *", + "target_engine_version": "v1", + "start_gap_hours": 24, + "start_gap_players": 2, + "current_turn": 0, + "runtime_status": "", + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.GameCreateRequestToPayload, &lobbymodel.GameCreateRequest{ + GameName: "First Contact", + Description: "", + MinPlayers: 2, + MaxPlayers: 8, + StartGapHours: 24, + StartGapPlayers: 2, + EnrollmentEndsAt: enrollment, + TurnSchedule: "0 0 * * *", + TargetEngineVersion: "v1", + }) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeGameCreate, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToGameCreateResponse(result.PayloadBytes) + require.NoError(t, err) + assert.Equal(t, "newly-created", decoded.Game.GameID) + assert.Equal(t, "draft", decoded.Game.Status) +} + +func TestExecuteLobbyGameCreateRejectsEmptyGameName(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + t.Errorf("backend must not be hit on validation failure") + w.WriteHeader(http.StatusInternalServerError) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.GameCreateRequestToPayload, &lobbymodel.GameCreateRequest{ + MinPlayers: 2, + MaxPlayers: 8, + EnrollmentEndsAt: time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC), + TurnSchedule: "0 0 * * *", + TargetEngineVersion: "v1", + }) + _, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeGameCreate, payload)) + require.Error(t, err) + assert.Contains(t, err.Error(), "game_name must not be empty") +} + +func TestExecuteLobbyApplicationSubmitPostsRaceName(t *testing.T) { + t.Parallel() + + created := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/api/v1/user/lobby/games/public-1/applications", r.URL.Path) + + var body map[string]any + raw, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(raw, &body)) + assert.Equal(t, "Vegan Federation", body["race_name"]) + + writeJSON(t, w, http.StatusCreated, map[string]any{ + "application_id": "app-3", + "game_id": "public-1", + "applicant_user_id": "user-1", + "race_name": "Vegan Federation", + "status": "pending", + "created_at": created.Format(time.RFC3339Nano), + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.ApplicationSubmitRequestToPayload, &lobbymodel.ApplicationSubmitRequest{ + GameID: "public-1", + RaceName: "Vegan Federation", + }) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeApplicationSubmit, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToApplicationSubmitResponse(result.PayloadBytes) + require.NoError(t, err) + assert.Equal(t, "app-3", decoded.Application.ApplicationID) + assert.Equal(t, "pending", decoded.Application.Status) +} + +func TestExecuteLobbyInviteRedeemPostsToBackend(t *testing.T) { + t.Parallel() + + created := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC) + expires := time.Date(2026, 5, 8, 10, 0, 0, 0, time.UTC) + decided := time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/api/v1/user/lobby/games/private-1/invites/invite-1/redeem", r.URL.Path) + writeJSON(t, w, http.StatusOK, map[string]any{ + "invite_id": "invite-1", + "game_id": "private-1", + "inviter_user_id": "user-host", + "invited_user_id": "user-1", + "race_name": "Vegan Federation", + "status": "accepted", + "created_at": created.Format(time.RFC3339Nano), + "expires_at": expires.Format(time.RFC3339Nano), + "decided_at": decided.Format(time.RFC3339Nano), + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.InviteRedeemRequestToPayload, &lobbymodel.InviteRedeemRequest{GameID: "private-1", InviteID: "invite-1"}) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeInviteRedeem, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToInviteRedeemResponse(result.PayloadBytes) + require.NoError(t, err) + assert.Equal(t, "accepted", decoded.Invite.Status) +} + +func TestExecuteLobbyInviteDeclinePostsToBackend(t *testing.T) { + t.Parallel() + + created := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC) + expires := time.Date(2026, 5, 8, 10, 0, 0, 0, time.UTC) + decided := time.Date(2026, 5, 6, 12, 0, 0, 0, time.UTC) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/api/v1/user/lobby/games/private-1/invites/invite-1/decline", r.URL.Path) + writeJSON(t, w, http.StatusOK, map[string]any{ + "invite_id": "invite-1", + "game_id": "private-1", + "inviter_user_id": "user-host", + "invited_user_id": "user-1", + "race_name": "Vegan Federation", + "status": "declined", + "created_at": created.Format(time.RFC3339Nano), + "expires_at": expires.Format(time.RFC3339Nano), + "decided_at": decided.Format(time.RFC3339Nano), + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.InviteDeclineRequestToPayload, &lobbymodel.InviteDeclineRequest{GameID: "private-1", InviteID: "invite-1"}) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeInviteDecline, payload)) + require.NoError(t, err) + assert.Equal(t, "ok", result.ResultCode) + + decoded, err := transcoder.PayloadToInviteDeclineResponse(result.PayloadBytes) + require.NoError(t, err) + assert.Equal(t, "declined", decoded.Invite.Status) +} + +func TestExecuteLobbyProjectsBackendErrorAcrossCommands(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + messageType string + payload []byte + statusCode int + want string + }{ + { + name: "public games conflict", + messageType: lobbymodel.MessageTypePublicGamesList, + payload: mustEncode(t, transcoder.PublicGamesListRequestToPayload, &lobbymodel.PublicGamesListRequest{Page: 1, PageSize: 50}), + statusCode: http.StatusConflict, + want: "conflict", + }, + { + name: "applications forbidden", + messageType: lobbymodel.MessageTypeApplicationSubmit, + payload: mustEncode(t, transcoder.ApplicationSubmitRequestToPayload, &lobbymodel.ApplicationSubmitRequest{GameID: "g", RaceName: "r"}), + statusCode: http.StatusForbidden, + want: "forbidden", + }, + { + name: "invite redeem not found", + messageType: lobbymodel.MessageTypeInviteRedeem, + payload: mustEncode(t, transcoder.InviteRedeemRequestToPayload, &lobbymodel.InviteRedeemRequest{GameID: "g", InviteID: "i"}), + statusCode: http.StatusNotFound, + want: "subject_not_found", + }, + { + name: "create invalid request", + messageType: lobbymodel.MessageTypeGameCreate, + payload: mustEncode(t, transcoder.GameCreateRequestToPayload, validCreateRequest()), + statusCode: http.StatusBadRequest, + want: "invalid_request", + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + writeJSON(t, w, tc.statusCode, map[string]any{ + "error": map[string]any{"code": tc.want, "message": "from backend"}, + }) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + result, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, tc.messageType, tc.payload)) + require.NoError(t, err) + assert.Equal(t, tc.want, result.ResultCode) + + errResp, err := transcoder.PayloadToLobbyErrorResponse(result.PayloadBytes) + require.NoError(t, err) + assert.Equal(t, tc.want, errResp.Error.Code) + assert.Equal(t, "from backend", errResp.Error.Message) + }) + } +} + +func TestExecuteLobbyMapsServiceUnavailableToDownstreamError(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + payload := mustEncode(t, transcoder.MyGamesListRequestToPayload, &lobbymodel.MyGamesListRequest{}) + _, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, lobbymodel.MessageTypeMyGamesList, payload)) + require.Error(t, err) + assert.True(t, errors.Is(err, downstream.ErrDownstreamUnavailable)) +} + +func TestExecuteLobbyRejectsUnknownMessageType(t *testing.T) { + t.Parallel() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(server.Close) + + client := newRESTClient(t, server) + _, err := client.ExecuteLobbyCommand(context.Background(), newAuthCommand(t, "lobby.unknown", []byte{0x01})) + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "unsupported message type")) +} + +func validCreateRequest() *lobbymodel.GameCreateRequest { + return &lobbymodel.GameCreateRequest{ + GameName: "Test", + Description: "", + MinPlayers: 2, + MaxPlayers: 8, + StartGapHours: 24, + StartGapPlayers: 2, + EnrollmentEndsAt: time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC), + TurnSchedule: "0 0 * * *", + TargetEngineVersion: "v1", + } +} diff --git a/gateway/internal/backendclient/routes.go b/gateway/internal/backendclient/routes.go index 3bfc510..0904571 100644 --- a/gateway/internal/backendclient/routes.go +++ b/gateway/internal/backendclient/routes.go @@ -38,8 +38,15 @@ func LobbyRoutes(client *RESTClient) map[string]downstream.Client { target = lobbyCommandClient{rest: client} } return map[string]downstream.Client{ - lobbymodel.MessageTypeMyGamesList: target, - lobbymodel.MessageTypeOpenEnrollment: target, + lobbymodel.MessageTypeMyGamesList: target, + lobbymodel.MessageTypePublicGamesList: target, + lobbymodel.MessageTypeMyApplicationsList: target, + lobbymodel.MessageTypeMyInvitesList: target, + lobbymodel.MessageTypeOpenEnrollment: target, + lobbymodel.MessageTypeGameCreate: target, + lobbymodel.MessageTypeApplicationSubmit: target, + lobbymodel.MessageTypeInviteRedeem: target, + lobbymodel.MessageTypeInviteDecline: target, } } diff --git a/pkg/model/lobby/lobby.go b/pkg/model/lobby/lobby.go index 80a6e79..62ebcb5 100644 --- a/pkg/model/lobby/lobby.go +++ b/pkg/model/lobby/lobby.go @@ -9,10 +9,44 @@ const ( // used to read the calling user's own games. MessageTypeMyGamesList = "lobby.my.games.list" + // MessageTypePublicGamesList is the authenticated gateway message + // type used to read the paginated list of joinable public games. + MessageTypePublicGamesList = "lobby.public.games.list" + + // MessageTypeMyApplicationsList is the authenticated gateway message + // type used to read the caller's pending and decided applications to + // public games. + MessageTypeMyApplicationsList = "lobby.my.applications.list" + + // MessageTypeMyInvitesList is the authenticated gateway message type + // used to read the caller's pending invites to private games. + MessageTypeMyInvitesList = "lobby.my.invites.list" + // MessageTypeOpenEnrollment is the authenticated gateway message // type used by the game owner to transition a draft game to // `enrollment_open`. MessageTypeOpenEnrollment = "lobby.game.open-enrollment" + + // MessageTypeGameCreate is the authenticated gateway message type + // used to create a private game owned by the caller. Public games + // are created exclusively through the admin surface; the gateway + // rejects any attempt to set visibility to `public`. + MessageTypeGameCreate = "lobby.game.create" + + // MessageTypeApplicationSubmit is the authenticated gateway message + // type used by a user to submit an application to a public game in + // `enrollment_open`. + MessageTypeApplicationSubmit = "lobby.application.submit" + + // MessageTypeInviteRedeem is the authenticated gateway message type + // used to accept an outstanding invite and create the corresponding + // membership. + MessageTypeInviteRedeem = "lobby.invite.redeem" + + // MessageTypeInviteDecline is the authenticated gateway message type + // used to terminally decline an outstanding invite. No membership is + // created. + MessageTypeInviteDecline = "lobby.invite.decline" ) // MyGamesListRequest stores the authenticated read request for the @@ -26,18 +60,88 @@ type MyGamesListResponse struct { Items []GameSummary `json:"items"` } -// GameSummary stores one game record returned by `lobby.my.games.list`. +// GameSummary stores one game record returned by the various lobby +// list endpoints. `OwnerUserID` is empty for public games (no human +// owner). type GameSummary struct { - GameID string `json:"game_id"` - GameName string `json:"game_name"` - GameType string `json:"game_type"` - Status string `json:"status"` - OwnerUserID string `json:"owner_user_id"` - MinPlayers int `json:"min_players"` - MaxPlayers int `json:"max_players"` - EnrollmentEndsAt time.Time `json:"enrollment_ends_at"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + GameID string `json:"game_id"` + GameName string `json:"game_name"` + GameType string `json:"game_type"` + Status string `json:"status"` + OwnerUserID string `json:"owner_user_id"` + MinPlayers int `json:"min_players"` + MaxPlayers int `json:"max_players"` + EnrollmentEndsAt time.Time `json:"enrollment_ends_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// PublicGamesListRequest stores the paginated read request for joinable +// public games. Page numbers start at 1; page sizes are capped by the +// backend. +type PublicGamesListRequest struct { + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +// PublicGamesListResponse stores one page of public games together with +// the pagination metadata returned by the backend. +type PublicGamesListResponse struct { + Items []GameSummary `json:"items"` + Page int `json:"page"` + PageSize int `json:"page_size"` + Total int `json:"total"` +} + +// MyApplicationsListRequest stores the authenticated read request for +// the caller's applications. Empty body — gateway derives identity from +// the authenticated session. +type MyApplicationsListRequest struct{} + +// MyApplicationsListResponse stores the caller's application list, +// ordered as Lobby returns it. +type MyApplicationsListResponse struct { + Items []ApplicationSummary `json:"items"` +} + +// ApplicationSummary stores a single application record returned by +// `lobby.my.applications.list` and `lobby.application.submit`. +// `DecidedAt` is nil while the application is pending. +type ApplicationSummary struct { + ApplicationID string `json:"application_id"` + GameID string `json:"game_id"` + ApplicantUserID string `json:"applicant_user_id"` + RaceName string `json:"race_name"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + DecidedAt *time.Time `json:"decided_at,omitempty"` +} + +// MyInvitesListRequest stores the authenticated read request for the +// caller's invites. Empty body. +type MyInvitesListRequest struct{} + +// MyInvitesListResponse stores the caller's invite list. +type MyInvitesListResponse struct { + Items []InviteSummary `json:"items"` +} + +// InviteSummary stores one invite record returned by +// `lobby.my.invites.list`, `lobby.invite.redeem`, and +// `lobby.invite.decline`. `InvitedUserID` is empty for code-based +// invites; `Code` is empty for user-bound invites; `DecidedAt` is nil +// while the invite is still pending. +type InviteSummary struct { + InviteID string `json:"invite_id"` + GameID string `json:"game_id"` + InviterUserID string `json:"inviter_user_id"` + InvitedUserID string `json:"invited_user_id,omitempty"` + Code string `json:"code,omitempty"` + RaceName string `json:"race_name"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + DecidedAt *time.Time `json:"decided_at,omitempty"` } // OpenEnrollmentRequest stores the owner-only command that transitions @@ -55,6 +159,70 @@ type OpenEnrollmentResponse struct { Status string `json:"status"` } +// GameCreateRequest stores the create-game command issued by the user +// surface. Visibility is always `private` here; the gateway rejects any +// other value before forwarding to backend, mirroring the FUNCTIONAL +// §3.3 invariant. +type GameCreateRequest struct { + GameName string `json:"game_name"` + Description string `json:"description"` + MinPlayers int `json:"min_players"` + MaxPlayers int `json:"max_players"` + StartGapHours int `json:"start_gap_hours"` + StartGapPlayers int `json:"start_gap_players"` + EnrollmentEndsAt time.Time `json:"enrollment_ends_at"` + TurnSchedule string `json:"turn_schedule"` + TargetEngineVersion string `json:"target_engine_version"` +} + +// GameCreateResponse stores the freshly created game projected onto the +// shared GameSummary shape — the gateway does not expose runtime fields +// at create time because the game is always in `draft` until the owner +// transitions it. +type GameCreateResponse struct { + Game GameSummary `json:"game"` +} + +// ApplicationSubmitRequest stores the submit-application command. The +// game must be public and in `enrollment_open`; backend rejects all +// other states. +type ApplicationSubmitRequest struct { + GameID string `json:"game_id"` + RaceName string `json:"race_name"` +} + +// ApplicationSubmitResponse wraps the application created by a +// successful submit-application call. +type ApplicationSubmitResponse struct { + Application ApplicationSummary `json:"application"` +} + +// InviteRedeemRequest accepts a pending invite and creates the +// corresponding membership at the backend. +type InviteRedeemRequest struct { + GameID string `json:"game_id"` + InviteID string `json:"invite_id"` +} + +// InviteRedeemResponse wraps the invite record updated by a successful +// redeem (status transitions to `accepted`). +type InviteRedeemResponse struct { + Invite InviteSummary `json:"invite"` +} + +// InviteDeclineRequest terminally declines a pending invite. No +// membership is created. +type InviteDeclineRequest struct { + GameID string `json:"game_id"` + InviteID string `json:"invite_id"` +} + +// InviteDeclineResponse wraps the invite record updated by a successful +// decline (status transitions to `declined`). +type InviteDeclineResponse struct { + Invite InviteSummary `json:"invite"` +} + // ErrorBody stores the canonical Lobby error envelope code/message // pair. type ErrorBody struct { diff --git a/pkg/schema/fbs/lobby.fbs b/pkg/schema/fbs/lobby.fbs index 9671123..3ee27c0 100644 --- a/pkg/schema/fbs/lobby.fbs +++ b/pkg/schema/fbs/lobby.fbs @@ -4,7 +4,8 @@ // from the verified session and forwards it as `X-User-Id` to lobby. namespace lobby; -// GameSummary stores one game record returned by `lobby.my.games.list`. +// GameSummary stores one game record returned by the lobby list +// endpoints. owner_user_id is empty for public games (no human owner). // The shape matches `lobby/openapi.yaml` `MyGameSummary`. table GameSummary { game_id:string; @@ -31,6 +32,71 @@ table MyGamesListResponse { items:[GameSummary]; } +// PublicGamesListRequest stores the paginated read request for joinable +// public games. Page numbers start at 1; backend caps page_size. +table PublicGamesListRequest { + page:int32; + page_size:int32; +} + +// PublicGamesListResponse stores one page of public games together with +// the pagination metadata. +table PublicGamesListResponse { + items:[GameSummary]; + page:int32; + page_size:int32; + total:int32; +} + +// ApplicationSummary stores one application record. decided_at_ms is 0 +// while the application is pending. +table ApplicationSummary { + application_id:string; + game_id:string; + applicant_user_id:string; + race_name:string; + status:string; + created_at_ms:int64; + decided_at_ms:int64; +} + +// MyApplicationsListRequest stores the authenticated read request for +// the caller's applications. Empty body. +table MyApplicationsListRequest { +} + +// MyApplicationsListResponse stores the caller's applications in the +// order the backend returns them. +table MyApplicationsListResponse { + items:[ApplicationSummary]; +} + +// InviteSummary stores one invite record. invited_user_id is empty for +// code-based invites; code is empty for user-bound invites; +// decided_at_ms is 0 while the invite is still pending. +table InviteSummary { + invite_id:string; + game_id:string; + inviter_user_id:string; + invited_user_id:string; + code:string; + race_name:string; + status:string; + created_at_ms:int64; + expires_at_ms:int64; + decided_at_ms:int64; +} + +// MyInvitesListRequest stores the authenticated read request for the +// caller's invites. Empty body. +table MyInvitesListRequest { +} + +// MyInvitesListResponse stores the caller's invite list. +table MyInvitesListResponse { + items:[InviteSummary]; +} + // OpenEnrollmentRequest stores the owner-only command that transitions // a game from `draft` to `enrollment_open`. table OpenEnrollmentRequest { @@ -43,6 +109,65 @@ table OpenEnrollmentResponse { status:string; } +// GameCreateRequest stores the create-game command. Visibility is +// always `private` for the user surface; the gateway rejects any other +// value before forwarding to backend. +table GameCreateRequest { + game_name:string; + description:string; + min_players:int32; + max_players:int32; + start_gap_hours:int32; + start_gap_players:int32; + enrollment_ends_at_ms:int64; + turn_schedule:string; + target_engine_version:string; +} + +// GameCreateResponse wraps the freshly created game projected onto the +// shared GameSummary shape. +table GameCreateResponse { + game:GameSummary; +} + +// ApplicationSubmitRequest stores the submit-application command for a +// public game in `enrollment_open`. +table ApplicationSubmitRequest { + game_id:string; + race_name:string; +} + +// ApplicationSubmitResponse wraps the freshly created application +// record. +table ApplicationSubmitResponse { + application:ApplicationSummary; +} + +// InviteRedeemRequest accepts a pending invite and creates the +// corresponding membership. +table InviteRedeemRequest { + game_id:string; + invite_id:string; +} + +// InviteRedeemResponse wraps the updated invite record (status +// transitioned to `accepted`). +table InviteRedeemResponse { + invite:InviteSummary; +} + +// InviteDeclineRequest terminally declines a pending invite. +table InviteDeclineRequest { + game_id:string; + invite_id:string; +} + +// InviteDeclineResponse wraps the updated invite record (status +// transitioned to `declined`). +table InviteDeclineResponse { + invite:InviteSummary; +} + // ErrorBody stores the canonical lobby error envelope code/message // pair. table ErrorBody { diff --git a/pkg/schema/fbs/lobby/ApplicationSubmitRequest.go b/pkg/schema/fbs/lobby/ApplicationSubmitRequest.go new file mode 100644 index 0000000..af10d78 --- /dev/null +++ b/pkg/schema/fbs/lobby/ApplicationSubmitRequest.go @@ -0,0 +1,71 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type ApplicationSubmitRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsApplicationSubmitRequest(buf []byte, offset flatbuffers.UOffsetT) *ApplicationSubmitRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &ApplicationSubmitRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishApplicationSubmitRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsApplicationSubmitRequest(buf []byte, offset flatbuffers.UOffsetT) *ApplicationSubmitRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &ApplicationSubmitRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedApplicationSubmitRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *ApplicationSubmitRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *ApplicationSubmitRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *ApplicationSubmitRequest) GameId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *ApplicationSubmitRequest) RaceName() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func ApplicationSubmitRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func ApplicationSubmitRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(gameId), 0) +} +func ApplicationSubmitRequestAddRaceName(builder *flatbuffers.Builder, raceName flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(raceName), 0) +} +func ApplicationSubmitRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/ApplicationSubmitResponse.go b/pkg/schema/fbs/lobby/ApplicationSubmitResponse.go new file mode 100644 index 0000000..7dd08f1 --- /dev/null +++ b/pkg/schema/fbs/lobby/ApplicationSubmitResponse.go @@ -0,0 +1,65 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type ApplicationSubmitResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsApplicationSubmitResponse(buf []byte, offset flatbuffers.UOffsetT) *ApplicationSubmitResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &ApplicationSubmitResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishApplicationSubmitResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsApplicationSubmitResponse(buf []byte, offset flatbuffers.UOffsetT) *ApplicationSubmitResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &ApplicationSubmitResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedApplicationSubmitResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *ApplicationSubmitResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *ApplicationSubmitResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *ApplicationSubmitResponse) Application(obj *ApplicationSummary) *ApplicationSummary { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(ApplicationSummary) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func ApplicationSubmitResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func ApplicationSubmitResponseAddApplication(builder *flatbuffers.Builder, application flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(application), 0) +} +func ApplicationSubmitResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/ApplicationSummary.go b/pkg/schema/fbs/lobby/ApplicationSummary.go new file mode 100644 index 0000000..a32a4f5 --- /dev/null +++ b/pkg/schema/fbs/lobby/ApplicationSummary.go @@ -0,0 +1,134 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type ApplicationSummary struct { + _tab flatbuffers.Table +} + +func GetRootAsApplicationSummary(buf []byte, offset flatbuffers.UOffsetT) *ApplicationSummary { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &ApplicationSummary{} + x.Init(buf, n+offset) + return x +} + +func FinishApplicationSummaryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsApplicationSummary(buf []byte, offset flatbuffers.UOffsetT) *ApplicationSummary { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &ApplicationSummary{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedApplicationSummaryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *ApplicationSummary) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *ApplicationSummary) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *ApplicationSummary) ApplicationId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *ApplicationSummary) GameId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *ApplicationSummary) ApplicantUserId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *ApplicationSummary) RaceName() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *ApplicationSummary) Status() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *ApplicationSummary) CreatedAtMs() int64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + return rcv._tab.GetInt64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *ApplicationSummary) MutateCreatedAtMs(n int64) bool { + return rcv._tab.MutateInt64Slot(14, n) +} + +func (rcv *ApplicationSummary) DecidedAtMs() int64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(16)) + if o != 0 { + return rcv._tab.GetInt64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *ApplicationSummary) MutateDecidedAtMs(n int64) bool { + return rcv._tab.MutateInt64Slot(16, n) +} + +func ApplicationSummaryStart(builder *flatbuffers.Builder) { + builder.StartObject(7) +} +func ApplicationSummaryAddApplicationId(builder *flatbuffers.Builder, applicationId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(applicationId), 0) +} +func ApplicationSummaryAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(gameId), 0) +} +func ApplicationSummaryAddApplicantUserId(builder *flatbuffers.Builder, applicantUserId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(applicantUserId), 0) +} +func ApplicationSummaryAddRaceName(builder *flatbuffers.Builder, raceName flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(raceName), 0) +} +func ApplicationSummaryAddStatus(builder *flatbuffers.Builder, status flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(status), 0) +} +func ApplicationSummaryAddCreatedAtMs(builder *flatbuffers.Builder, createdAtMs int64) { + builder.PrependInt64Slot(5, createdAtMs, 0) +} +func ApplicationSummaryAddDecidedAtMs(builder *flatbuffers.Builder, decidedAtMs int64) { + builder.PrependInt64Slot(6, decidedAtMs, 0) +} +func ApplicationSummaryEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/GameCreateRequest.go b/pkg/schema/fbs/lobby/GameCreateRequest.go new file mode 100644 index 0000000..0145559 --- /dev/null +++ b/pkg/schema/fbs/lobby/GameCreateRequest.go @@ -0,0 +1,168 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type GameCreateRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsGameCreateRequest(buf []byte, offset flatbuffers.UOffsetT) *GameCreateRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &GameCreateRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishGameCreateRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsGameCreateRequest(buf []byte, offset flatbuffers.UOffsetT) *GameCreateRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &GameCreateRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedGameCreateRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *GameCreateRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *GameCreateRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *GameCreateRequest) GameName() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *GameCreateRequest) Description() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *GameCreateRequest) MinPlayers() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *GameCreateRequest) MutateMinPlayers(n int32) bool { + return rcv._tab.MutateInt32Slot(8, n) +} + +func (rcv *GameCreateRequest) MaxPlayers() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *GameCreateRequest) MutateMaxPlayers(n int32) bool { + return rcv._tab.MutateInt32Slot(10, n) +} + +func (rcv *GameCreateRequest) StartGapHours() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *GameCreateRequest) MutateStartGapHours(n int32) bool { + return rcv._tab.MutateInt32Slot(12, n) +} + +func (rcv *GameCreateRequest) StartGapPlayers() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *GameCreateRequest) MutateStartGapPlayers(n int32) bool { + return rcv._tab.MutateInt32Slot(14, n) +} + +func (rcv *GameCreateRequest) EnrollmentEndsAtMs() int64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(16)) + if o != 0 { + return rcv._tab.GetInt64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *GameCreateRequest) MutateEnrollmentEndsAtMs(n int64) bool { + return rcv._tab.MutateInt64Slot(16, n) +} + +func (rcv *GameCreateRequest) TurnSchedule() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(18)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *GameCreateRequest) TargetEngineVersion() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(20)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func GameCreateRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(9) +} +func GameCreateRequestAddGameName(builder *flatbuffers.Builder, gameName flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(gameName), 0) +} +func GameCreateRequestAddDescription(builder *flatbuffers.Builder, description flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(description), 0) +} +func GameCreateRequestAddMinPlayers(builder *flatbuffers.Builder, minPlayers int32) { + builder.PrependInt32Slot(2, minPlayers, 0) +} +func GameCreateRequestAddMaxPlayers(builder *flatbuffers.Builder, maxPlayers int32) { + builder.PrependInt32Slot(3, maxPlayers, 0) +} +func GameCreateRequestAddStartGapHours(builder *flatbuffers.Builder, startGapHours int32) { + builder.PrependInt32Slot(4, startGapHours, 0) +} +func GameCreateRequestAddStartGapPlayers(builder *flatbuffers.Builder, startGapPlayers int32) { + builder.PrependInt32Slot(5, startGapPlayers, 0) +} +func GameCreateRequestAddEnrollmentEndsAtMs(builder *flatbuffers.Builder, enrollmentEndsAtMs int64) { + builder.PrependInt64Slot(6, enrollmentEndsAtMs, 0) +} +func GameCreateRequestAddTurnSchedule(builder *flatbuffers.Builder, turnSchedule flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(7, flatbuffers.UOffsetT(turnSchedule), 0) +} +func GameCreateRequestAddTargetEngineVersion(builder *flatbuffers.Builder, targetEngineVersion flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(8, flatbuffers.UOffsetT(targetEngineVersion), 0) +} +func GameCreateRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/GameCreateResponse.go b/pkg/schema/fbs/lobby/GameCreateResponse.go new file mode 100644 index 0000000..17d73cf --- /dev/null +++ b/pkg/schema/fbs/lobby/GameCreateResponse.go @@ -0,0 +1,65 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type GameCreateResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsGameCreateResponse(buf []byte, offset flatbuffers.UOffsetT) *GameCreateResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &GameCreateResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishGameCreateResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsGameCreateResponse(buf []byte, offset flatbuffers.UOffsetT) *GameCreateResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &GameCreateResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedGameCreateResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *GameCreateResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *GameCreateResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *GameCreateResponse) Game(obj *GameSummary) *GameSummary { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(GameSummary) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func GameCreateResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func GameCreateResponseAddGame(builder *flatbuffers.Builder, game flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(game), 0) +} +func GameCreateResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/InviteDeclineRequest.go b/pkg/schema/fbs/lobby/InviteDeclineRequest.go new file mode 100644 index 0000000..84b984e --- /dev/null +++ b/pkg/schema/fbs/lobby/InviteDeclineRequest.go @@ -0,0 +1,71 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type InviteDeclineRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsInviteDeclineRequest(buf []byte, offset flatbuffers.UOffsetT) *InviteDeclineRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &InviteDeclineRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishInviteDeclineRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsInviteDeclineRequest(buf []byte, offset flatbuffers.UOffsetT) *InviteDeclineRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &InviteDeclineRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedInviteDeclineRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *InviteDeclineRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *InviteDeclineRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *InviteDeclineRequest) GameId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteDeclineRequest) InviteId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func InviteDeclineRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func InviteDeclineRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(gameId), 0) +} +func InviteDeclineRequestAddInviteId(builder *flatbuffers.Builder, inviteId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(inviteId), 0) +} +func InviteDeclineRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/InviteDeclineResponse.go b/pkg/schema/fbs/lobby/InviteDeclineResponse.go new file mode 100644 index 0000000..7e79554 --- /dev/null +++ b/pkg/schema/fbs/lobby/InviteDeclineResponse.go @@ -0,0 +1,65 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type InviteDeclineResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsInviteDeclineResponse(buf []byte, offset flatbuffers.UOffsetT) *InviteDeclineResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &InviteDeclineResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishInviteDeclineResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsInviteDeclineResponse(buf []byte, offset flatbuffers.UOffsetT) *InviteDeclineResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &InviteDeclineResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedInviteDeclineResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *InviteDeclineResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *InviteDeclineResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *InviteDeclineResponse) Invite(obj *InviteSummary) *InviteSummary { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(InviteSummary) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func InviteDeclineResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func InviteDeclineResponseAddInvite(builder *flatbuffers.Builder, invite flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(invite), 0) +} +func InviteDeclineResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/InviteRedeemRequest.go b/pkg/schema/fbs/lobby/InviteRedeemRequest.go new file mode 100644 index 0000000..e35f84c --- /dev/null +++ b/pkg/schema/fbs/lobby/InviteRedeemRequest.go @@ -0,0 +1,71 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type InviteRedeemRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsInviteRedeemRequest(buf []byte, offset flatbuffers.UOffsetT) *InviteRedeemRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &InviteRedeemRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishInviteRedeemRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsInviteRedeemRequest(buf []byte, offset flatbuffers.UOffsetT) *InviteRedeemRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &InviteRedeemRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedInviteRedeemRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *InviteRedeemRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *InviteRedeemRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *InviteRedeemRequest) GameId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteRedeemRequest) InviteId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func InviteRedeemRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func InviteRedeemRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(gameId), 0) +} +func InviteRedeemRequestAddInviteId(builder *flatbuffers.Builder, inviteId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(inviteId), 0) +} +func InviteRedeemRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/InviteRedeemResponse.go b/pkg/schema/fbs/lobby/InviteRedeemResponse.go new file mode 100644 index 0000000..986f68e --- /dev/null +++ b/pkg/schema/fbs/lobby/InviteRedeemResponse.go @@ -0,0 +1,65 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type InviteRedeemResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsInviteRedeemResponse(buf []byte, offset flatbuffers.UOffsetT) *InviteRedeemResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &InviteRedeemResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishInviteRedeemResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsInviteRedeemResponse(buf []byte, offset flatbuffers.UOffsetT) *InviteRedeemResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &InviteRedeemResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedInviteRedeemResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *InviteRedeemResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *InviteRedeemResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *InviteRedeemResponse) Invite(obj *InviteSummary) *InviteSummary { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(InviteSummary) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func InviteRedeemResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func InviteRedeemResponseAddInvite(builder *flatbuffers.Builder, invite flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(invite), 0) +} +func InviteRedeemResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/InviteSummary.go b/pkg/schema/fbs/lobby/InviteSummary.go new file mode 100644 index 0000000..91e7cc1 --- /dev/null +++ b/pkg/schema/fbs/lobby/InviteSummary.go @@ -0,0 +1,171 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type InviteSummary struct { + _tab flatbuffers.Table +} + +func GetRootAsInviteSummary(buf []byte, offset flatbuffers.UOffsetT) *InviteSummary { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &InviteSummary{} + x.Init(buf, n+offset) + return x +} + +func FinishInviteSummaryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsInviteSummary(buf []byte, offset flatbuffers.UOffsetT) *InviteSummary { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &InviteSummary{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedInviteSummaryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *InviteSummary) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *InviteSummary) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *InviteSummary) InviteId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) GameId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) InviterUserId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) InvitedUserId() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) Code() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) RaceName() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) Status() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(16)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *InviteSummary) CreatedAtMs() int64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(18)) + if o != 0 { + return rcv._tab.GetInt64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *InviteSummary) MutateCreatedAtMs(n int64) bool { + return rcv._tab.MutateInt64Slot(18, n) +} + +func (rcv *InviteSummary) ExpiresAtMs() int64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(20)) + if o != 0 { + return rcv._tab.GetInt64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *InviteSummary) MutateExpiresAtMs(n int64) bool { + return rcv._tab.MutateInt64Slot(20, n) +} + +func (rcv *InviteSummary) DecidedAtMs() int64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(22)) + if o != 0 { + return rcv._tab.GetInt64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *InviteSummary) MutateDecidedAtMs(n int64) bool { + return rcv._tab.MutateInt64Slot(22, n) +} + +func InviteSummaryStart(builder *flatbuffers.Builder) { + builder.StartObject(10) +} +func InviteSummaryAddInviteId(builder *flatbuffers.Builder, inviteId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(inviteId), 0) +} +func InviteSummaryAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(gameId), 0) +} +func InviteSummaryAddInviterUserId(builder *flatbuffers.Builder, inviterUserId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(inviterUserId), 0) +} +func InviteSummaryAddInvitedUserId(builder *flatbuffers.Builder, invitedUserId flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(invitedUserId), 0) +} +func InviteSummaryAddCode(builder *flatbuffers.Builder, code flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(code), 0) +} +func InviteSummaryAddRaceName(builder *flatbuffers.Builder, raceName flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(raceName), 0) +} +func InviteSummaryAddStatus(builder *flatbuffers.Builder, status flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(6, flatbuffers.UOffsetT(status), 0) +} +func InviteSummaryAddCreatedAtMs(builder *flatbuffers.Builder, createdAtMs int64) { + builder.PrependInt64Slot(7, createdAtMs, 0) +} +func InviteSummaryAddExpiresAtMs(builder *flatbuffers.Builder, expiresAtMs int64) { + builder.PrependInt64Slot(8, expiresAtMs, 0) +} +func InviteSummaryAddDecidedAtMs(builder *flatbuffers.Builder, decidedAtMs int64) { + builder.PrependInt64Slot(9, decidedAtMs, 0) +} +func InviteSummaryEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/MyApplicationsListRequest.go b/pkg/schema/fbs/lobby/MyApplicationsListRequest.go new file mode 100644 index 0000000..98f1b37 --- /dev/null +++ b/pkg/schema/fbs/lobby/MyApplicationsListRequest.go @@ -0,0 +1,49 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type MyApplicationsListRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsMyApplicationsListRequest(buf []byte, offset flatbuffers.UOffsetT) *MyApplicationsListRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &MyApplicationsListRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishMyApplicationsListRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsMyApplicationsListRequest(buf []byte, offset flatbuffers.UOffsetT) *MyApplicationsListRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &MyApplicationsListRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedMyApplicationsListRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *MyApplicationsListRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *MyApplicationsListRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func MyApplicationsListRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(0) +} +func MyApplicationsListRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/MyApplicationsListResponse.go b/pkg/schema/fbs/lobby/MyApplicationsListResponse.go new file mode 100644 index 0000000..580afd4 --- /dev/null +++ b/pkg/schema/fbs/lobby/MyApplicationsListResponse.go @@ -0,0 +1,75 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type MyApplicationsListResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsMyApplicationsListResponse(buf []byte, offset flatbuffers.UOffsetT) *MyApplicationsListResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &MyApplicationsListResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishMyApplicationsListResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsMyApplicationsListResponse(buf []byte, offset flatbuffers.UOffsetT) *MyApplicationsListResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &MyApplicationsListResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedMyApplicationsListResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *MyApplicationsListResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *MyApplicationsListResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *MyApplicationsListResponse) Items(obj *ApplicationSummary, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *MyApplicationsListResponse) ItemsLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func MyApplicationsListResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func MyApplicationsListResponseAddItems(builder *flatbuffers.Builder, items flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(items), 0) +} +func MyApplicationsListResponseStartItemsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func MyApplicationsListResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/MyInvitesListRequest.go b/pkg/schema/fbs/lobby/MyInvitesListRequest.go new file mode 100644 index 0000000..84cdbc3 --- /dev/null +++ b/pkg/schema/fbs/lobby/MyInvitesListRequest.go @@ -0,0 +1,49 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type MyInvitesListRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsMyInvitesListRequest(buf []byte, offset flatbuffers.UOffsetT) *MyInvitesListRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &MyInvitesListRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishMyInvitesListRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsMyInvitesListRequest(buf []byte, offset flatbuffers.UOffsetT) *MyInvitesListRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &MyInvitesListRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedMyInvitesListRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *MyInvitesListRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *MyInvitesListRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func MyInvitesListRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(0) +} +func MyInvitesListRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/MyInvitesListResponse.go b/pkg/schema/fbs/lobby/MyInvitesListResponse.go new file mode 100644 index 0000000..5b22099 --- /dev/null +++ b/pkg/schema/fbs/lobby/MyInvitesListResponse.go @@ -0,0 +1,75 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type MyInvitesListResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsMyInvitesListResponse(buf []byte, offset flatbuffers.UOffsetT) *MyInvitesListResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &MyInvitesListResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishMyInvitesListResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsMyInvitesListResponse(buf []byte, offset flatbuffers.UOffsetT) *MyInvitesListResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &MyInvitesListResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedMyInvitesListResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *MyInvitesListResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *MyInvitesListResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *MyInvitesListResponse) Items(obj *InviteSummary, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *MyInvitesListResponse) ItemsLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func MyInvitesListResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func MyInvitesListResponseAddItems(builder *flatbuffers.Builder, items flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(items), 0) +} +func MyInvitesListResponseStartItemsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func MyInvitesListResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/PublicGamesListRequest.go b/pkg/schema/fbs/lobby/PublicGamesListRequest.go new file mode 100644 index 0000000..341f582 --- /dev/null +++ b/pkg/schema/fbs/lobby/PublicGamesListRequest.go @@ -0,0 +1,79 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type PublicGamesListRequest struct { + _tab flatbuffers.Table +} + +func GetRootAsPublicGamesListRequest(buf []byte, offset flatbuffers.UOffsetT) *PublicGamesListRequest { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &PublicGamesListRequest{} + x.Init(buf, n+offset) + return x +} + +func FinishPublicGamesListRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsPublicGamesListRequest(buf []byte, offset flatbuffers.UOffsetT) *PublicGamesListRequest { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &PublicGamesListRequest{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedPublicGamesListRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *PublicGamesListRequest) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *PublicGamesListRequest) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *PublicGamesListRequest) Page() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *PublicGamesListRequest) MutatePage(n int32) bool { + return rcv._tab.MutateInt32Slot(4, n) +} + +func (rcv *PublicGamesListRequest) PageSize() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *PublicGamesListRequest) MutatePageSize(n int32) bool { + return rcv._tab.MutateInt32Slot(6, n) +} + +func PublicGamesListRequestStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func PublicGamesListRequestAddPage(builder *flatbuffers.Builder, page int32) { + builder.PrependInt32Slot(0, page, 0) +} +func PublicGamesListRequestAddPageSize(builder *flatbuffers.Builder, pageSize int32) { + builder.PrependInt32Slot(1, pageSize, 0) +} +func PublicGamesListRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/schema/fbs/lobby/PublicGamesListResponse.go b/pkg/schema/fbs/lobby/PublicGamesListResponse.go new file mode 100644 index 0000000..20d65e3 --- /dev/null +++ b/pkg/schema/fbs/lobby/PublicGamesListResponse.go @@ -0,0 +1,120 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package lobby + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type PublicGamesListResponse struct { + _tab flatbuffers.Table +} + +func GetRootAsPublicGamesListResponse(buf []byte, offset flatbuffers.UOffsetT) *PublicGamesListResponse { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &PublicGamesListResponse{} + x.Init(buf, n+offset) + return x +} + +func FinishPublicGamesListResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsPublicGamesListResponse(buf []byte, offset flatbuffers.UOffsetT) *PublicGamesListResponse { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &PublicGamesListResponse{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedPublicGamesListResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *PublicGamesListResponse) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *PublicGamesListResponse) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *PublicGamesListResponse) Items(obj *GameSummary, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *PublicGamesListResponse) ItemsLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *PublicGamesListResponse) Page() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *PublicGamesListResponse) MutatePage(n int32) bool { + return rcv._tab.MutateInt32Slot(6, n) +} + +func (rcv *PublicGamesListResponse) PageSize() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *PublicGamesListResponse) MutatePageSize(n int32) bool { + return rcv._tab.MutateInt32Slot(8, n) +} + +func (rcv *PublicGamesListResponse) Total() int32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.GetInt32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *PublicGamesListResponse) MutateTotal(n int32) bool { + return rcv._tab.MutateInt32Slot(10, n) +} + +func PublicGamesListResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(4) +} +func PublicGamesListResponseAddItems(builder *flatbuffers.Builder, items flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(items), 0) +} +func PublicGamesListResponseStartItemsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func PublicGamesListResponseAddPage(builder *flatbuffers.Builder, page int32) { + builder.PrependInt32Slot(1, page, 0) +} +func PublicGamesListResponseAddPageSize(builder *flatbuffers.Builder, pageSize int32) { + builder.PrependInt32Slot(2, pageSize, 0) +} +func PublicGamesListResponseAddTotal(builder *flatbuffers.Builder, total int32) { + builder.PrependInt32Slot(3, total, 0) +} +func PublicGamesListResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/pkg/transcoder/lobby.go b/pkg/transcoder/lobby.go index 478c9fa..9b54ab0 100644 --- a/pkg/transcoder/lobby.go +++ b/pkg/transcoder/lobby.go @@ -53,14 +53,7 @@ func MyGamesListResponseToPayload(response *lobbymodel.MyGamesListResponse) ([]b itemOffsets[index] = encodeGameSummary(builder, response.Items[index]) } - var itemsVector flatbuffers.UOffsetT - if len(itemOffsets) > 0 { - lobbyfbs.MyGamesListResponseStartItemsVector(builder, len(itemOffsets)) - for index := len(itemOffsets) - 1; index >= 0; index-- { - builder.PrependUOffsetT(itemOffsets[index]) - } - itemsVector = builder.EndVector(len(itemOffsets)) - } + itemsVector := finishOffsetVector(builder, itemOffsets) lobbyfbs.MyGamesListResponseStart(builder) if itemsVector != 0 { @@ -96,6 +89,248 @@ func PayloadToMyGamesListResponse(data []byte) (result *lobbymodel.MyGamesListRe return out, nil } +// PublicGamesListRequestToPayload converts lobbymodel.PublicGamesListRequest to +// FlatBuffers bytes. +func PublicGamesListRequestToPayload(request *lobbymodel.PublicGamesListRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode public games list request payload: request is nil") + } + + builder := flatbuffers.NewBuilder(32) + lobbyfbs.PublicGamesListRequestStart(builder) + lobbyfbs.PublicGamesListRequestAddPage(builder, int32(request.Page)) + lobbyfbs.PublicGamesListRequestAddPageSize(builder, int32(request.PageSize)) + offset := lobbyfbs.PublicGamesListRequestEnd(builder) + lobbyfbs.FinishPublicGamesListRequestBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToPublicGamesListRequest converts FlatBuffers payload bytes into +// lobbymodel.PublicGamesListRequest. +func PayloadToPublicGamesListRequest(data []byte) (result *lobbymodel.PublicGamesListRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode public games list request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode public games list request payload", &result, &err) + + request := lobbyfbs.GetRootAsPublicGamesListRequest(data, 0) + return &lobbymodel.PublicGamesListRequest{ + Page: int(request.Page()), + PageSize: int(request.PageSize()), + }, nil +} + +// PublicGamesListResponseToPayload converts lobbymodel.PublicGamesListResponse +// to FlatBuffers bytes. +func PublicGamesListResponseToPayload(response *lobbymodel.PublicGamesListResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode public games list response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(512) + + itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items)) + for index := range response.Items { + itemOffsets[index] = encodeGameSummary(builder, response.Items[index]) + } + itemsVector := finishOffsetVector(builder, itemOffsets) + + lobbyfbs.PublicGamesListResponseStart(builder) + if itemsVector != 0 { + lobbyfbs.PublicGamesListResponseAddItems(builder, itemsVector) + } + lobbyfbs.PublicGamesListResponseAddPage(builder, int32(response.Page)) + lobbyfbs.PublicGamesListResponseAddPageSize(builder, int32(response.PageSize)) + lobbyfbs.PublicGamesListResponseAddTotal(builder, int32(response.Total)) + offset := lobbyfbs.PublicGamesListResponseEnd(builder) + lobbyfbs.FinishPublicGamesListResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToPublicGamesListResponse converts FlatBuffers payload bytes into +// lobbymodel.PublicGamesListResponse. +func PayloadToPublicGamesListResponse(data []byte) (result *lobbymodel.PublicGamesListResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode public games list response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode public games list response payload", &result, &err) + + response := lobbyfbs.GetRootAsPublicGamesListResponse(data, 0) + out := &lobbymodel.PublicGamesListResponse{ + Items: make([]lobbymodel.GameSummary, 0, response.ItemsLength()), + Page: int(response.Page()), + PageSize: int(response.PageSize()), + Total: int(response.Total()), + } + + summary := new(lobbyfbs.GameSummary) + for index := 0; index < response.ItemsLength(); index++ { + if !response.Items(summary, index) { + return nil, fmt.Errorf("decode public games list response payload: items[%d] is missing", index) + } + out.Items = append(out.Items, decodeGameSummary(summary)) + } + return out, nil +} + +// MyApplicationsListRequestToPayload converts lobbymodel.MyApplicationsListRequest +// to FlatBuffers bytes. +func MyApplicationsListRequestToPayload(request *lobbymodel.MyApplicationsListRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode my applications list request payload: request is nil") + } + + builder := flatbuffers.NewBuilder(32) + lobbyfbs.MyApplicationsListRequestStart(builder) + offset := lobbyfbs.MyApplicationsListRequestEnd(builder) + lobbyfbs.FinishMyApplicationsListRequestBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToMyApplicationsListRequest converts FlatBuffers payload bytes into +// lobbymodel.MyApplicationsListRequest. +func PayloadToMyApplicationsListRequest(data []byte) (result *lobbymodel.MyApplicationsListRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode my applications list request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode my applications list request payload", &result, &err) + + _ = lobbyfbs.GetRootAsMyApplicationsListRequest(data, 0) + return &lobbymodel.MyApplicationsListRequest{}, nil +} + +// MyApplicationsListResponseToPayload converts lobbymodel.MyApplicationsListResponse +// to FlatBuffers bytes. +func MyApplicationsListResponseToPayload(response *lobbymodel.MyApplicationsListResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode my applications list response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(512) + + itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items)) + for index := range response.Items { + itemOffsets[index] = encodeApplicationSummary(builder, response.Items[index]) + } + itemsVector := finishOffsetVector(builder, itemOffsets) + + lobbyfbs.MyApplicationsListResponseStart(builder) + if itemsVector != 0 { + lobbyfbs.MyApplicationsListResponseAddItems(builder, itemsVector) + } + offset := lobbyfbs.MyApplicationsListResponseEnd(builder) + lobbyfbs.FinishMyApplicationsListResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToMyApplicationsListResponse converts FlatBuffers payload bytes into +// lobbymodel.MyApplicationsListResponse. +func PayloadToMyApplicationsListResponse(data []byte) (result *lobbymodel.MyApplicationsListResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode my applications list response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode my applications list response payload", &result, &err) + + response := lobbyfbs.GetRootAsMyApplicationsListResponse(data, 0) + out := &lobbymodel.MyApplicationsListResponse{ + Items: make([]lobbymodel.ApplicationSummary, 0, response.ItemsLength()), + } + + app := new(lobbyfbs.ApplicationSummary) + for index := 0; index < response.ItemsLength(); index++ { + if !response.Items(app, index) { + return nil, fmt.Errorf("decode my applications list response payload: items[%d] is missing", index) + } + out.Items = append(out.Items, decodeApplicationSummary(app)) + } + return out, nil +} + +// MyInvitesListRequestToPayload converts lobbymodel.MyInvitesListRequest +// to FlatBuffers bytes. +func MyInvitesListRequestToPayload(request *lobbymodel.MyInvitesListRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode my invites list request payload: request is nil") + } + + builder := flatbuffers.NewBuilder(32) + lobbyfbs.MyInvitesListRequestStart(builder) + offset := lobbyfbs.MyInvitesListRequestEnd(builder) + lobbyfbs.FinishMyInvitesListRequestBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToMyInvitesListRequest converts FlatBuffers payload bytes into +// lobbymodel.MyInvitesListRequest. +func PayloadToMyInvitesListRequest(data []byte) (result *lobbymodel.MyInvitesListRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode my invites list request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode my invites list request payload", &result, &err) + + _ = lobbyfbs.GetRootAsMyInvitesListRequest(data, 0) + return &lobbymodel.MyInvitesListRequest{}, nil +} + +// MyInvitesListResponseToPayload converts lobbymodel.MyInvitesListResponse +// to FlatBuffers bytes. +func MyInvitesListResponseToPayload(response *lobbymodel.MyInvitesListResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode my invites list response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(512) + + itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items)) + for index := range response.Items { + itemOffsets[index] = encodeInviteSummary(builder, response.Items[index]) + } + itemsVector := finishOffsetVector(builder, itemOffsets) + + lobbyfbs.MyInvitesListResponseStart(builder) + if itemsVector != 0 { + lobbyfbs.MyInvitesListResponseAddItems(builder, itemsVector) + } + offset := lobbyfbs.MyInvitesListResponseEnd(builder) + lobbyfbs.FinishMyInvitesListResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToMyInvitesListResponse converts FlatBuffers payload bytes into +// lobbymodel.MyInvitesListResponse. +func PayloadToMyInvitesListResponse(data []byte) (result *lobbymodel.MyInvitesListResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode my invites list response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode my invites list response payload", &result, &err) + + response := lobbyfbs.GetRootAsMyInvitesListResponse(data, 0) + out := &lobbymodel.MyInvitesListResponse{ + Items: make([]lobbymodel.InviteSummary, 0, response.ItemsLength()), + } + + invite := new(lobbyfbs.InviteSummary) + for index := 0; index < response.ItemsLength(); index++ { + if !response.Items(invite, index) { + return nil, fmt.Errorf("decode my invites list response payload: items[%d] is missing", index) + } + out.Items = append(out.Items, decodeInviteSummary(invite)) + } + return out, nil +} + // OpenEnrollmentRequestToPayload converts lobbymodel.OpenEnrollmentRequest to // FlatBuffers bytes suitable for the authenticated gateway transport. func OpenEnrollmentRequestToPayload(request *lobbymodel.OpenEnrollmentRequest) ([]byte, error) { @@ -165,6 +400,292 @@ func PayloadToOpenEnrollmentResponse(data []byte) (result *lobbymodel.OpenEnroll }, nil } +// GameCreateRequestToPayload converts lobbymodel.GameCreateRequest to +// FlatBuffers bytes. +func GameCreateRequestToPayload(request *lobbymodel.GameCreateRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode game create request payload: request is nil") + } + + builder := flatbuffers.NewBuilder(256) + gameName := builder.CreateString(request.GameName) + description := builder.CreateString(request.Description) + turnSchedule := builder.CreateString(request.TurnSchedule) + targetEngineVersion := builder.CreateString(request.TargetEngineVersion) + + lobbyfbs.GameCreateRequestStart(builder) + lobbyfbs.GameCreateRequestAddGameName(builder, gameName) + lobbyfbs.GameCreateRequestAddDescription(builder, description) + lobbyfbs.GameCreateRequestAddMinPlayers(builder, int32(request.MinPlayers)) + lobbyfbs.GameCreateRequestAddMaxPlayers(builder, int32(request.MaxPlayers)) + lobbyfbs.GameCreateRequestAddStartGapHours(builder, int32(request.StartGapHours)) + lobbyfbs.GameCreateRequestAddStartGapPlayers(builder, int32(request.StartGapPlayers)) + lobbyfbs.GameCreateRequestAddEnrollmentEndsAtMs(builder, request.EnrollmentEndsAt.UTC().UnixMilli()) + lobbyfbs.GameCreateRequestAddTurnSchedule(builder, turnSchedule) + lobbyfbs.GameCreateRequestAddTargetEngineVersion(builder, targetEngineVersion) + offset := lobbyfbs.GameCreateRequestEnd(builder) + lobbyfbs.FinishGameCreateRequestBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToGameCreateRequest converts FlatBuffers payload bytes into +// lobbymodel.GameCreateRequest. +func PayloadToGameCreateRequest(data []byte) (result *lobbymodel.GameCreateRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode game create request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode game create request payload", &result, &err) + + request := lobbyfbs.GetRootAsGameCreateRequest(data, 0) + return &lobbymodel.GameCreateRequest{ + GameName: string(request.GameName()), + Description: string(request.Description()), + MinPlayers: int(request.MinPlayers()), + MaxPlayers: int(request.MaxPlayers()), + StartGapHours: int(request.StartGapHours()), + StartGapPlayers: int(request.StartGapPlayers()), + EnrollmentEndsAt: time.UnixMilli(request.EnrollmentEndsAtMs()).UTC(), + TurnSchedule: string(request.TurnSchedule()), + TargetEngineVersion: string(request.TargetEngineVersion()), + }, nil +} + +// GameCreateResponseToPayload converts lobbymodel.GameCreateResponse to +// FlatBuffers bytes. +func GameCreateResponseToPayload(response *lobbymodel.GameCreateResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode game create response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(256) + gameOffset := encodeGameSummary(builder, response.Game) + + lobbyfbs.GameCreateResponseStart(builder) + lobbyfbs.GameCreateResponseAddGame(builder, gameOffset) + offset := lobbyfbs.GameCreateResponseEnd(builder) + lobbyfbs.FinishGameCreateResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToGameCreateResponse converts FlatBuffers payload bytes into +// lobbymodel.GameCreateResponse. +func PayloadToGameCreateResponse(data []byte) (result *lobbymodel.GameCreateResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode game create response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode game create response payload", &result, &err) + + response := lobbyfbs.GetRootAsGameCreateResponse(data, 0) + game := response.Game(nil) + if game == nil { + return nil, errors.New("decode game create response payload: game is missing") + } + return &lobbymodel.GameCreateResponse{ + Game: decodeGameSummary(game), + }, nil +} + +// ApplicationSubmitRequestToPayload converts lobbymodel.ApplicationSubmitRequest +// to FlatBuffers bytes. +func ApplicationSubmitRequestToPayload(request *lobbymodel.ApplicationSubmitRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode application submit request payload: request is nil") + } + + builder := flatbuffers.NewBuilder(128) + gameID := builder.CreateString(request.GameID) + raceName := builder.CreateString(request.RaceName) + + lobbyfbs.ApplicationSubmitRequestStart(builder) + lobbyfbs.ApplicationSubmitRequestAddGameId(builder, gameID) + lobbyfbs.ApplicationSubmitRequestAddRaceName(builder, raceName) + offset := lobbyfbs.ApplicationSubmitRequestEnd(builder) + lobbyfbs.FinishApplicationSubmitRequestBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToApplicationSubmitRequest converts FlatBuffers payload bytes into +// lobbymodel.ApplicationSubmitRequest. +func PayloadToApplicationSubmitRequest(data []byte) (result *lobbymodel.ApplicationSubmitRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode application submit request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode application submit request payload", &result, &err) + + request := lobbyfbs.GetRootAsApplicationSubmitRequest(data, 0) + return &lobbymodel.ApplicationSubmitRequest{ + GameID: string(request.GameId()), + RaceName: string(request.RaceName()), + }, nil +} + +// ApplicationSubmitResponseToPayload converts lobbymodel.ApplicationSubmitResponse +// to FlatBuffers bytes. +func ApplicationSubmitResponseToPayload(response *lobbymodel.ApplicationSubmitResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode application submit response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(256) + appOffset := encodeApplicationSummary(builder, response.Application) + + lobbyfbs.ApplicationSubmitResponseStart(builder) + lobbyfbs.ApplicationSubmitResponseAddApplication(builder, appOffset) + offset := lobbyfbs.ApplicationSubmitResponseEnd(builder) + lobbyfbs.FinishApplicationSubmitResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToApplicationSubmitResponse converts FlatBuffers payload bytes into +// lobbymodel.ApplicationSubmitResponse. +func PayloadToApplicationSubmitResponse(data []byte) (result *lobbymodel.ApplicationSubmitResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode application submit response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode application submit response payload", &result, &err) + + response := lobbyfbs.GetRootAsApplicationSubmitResponse(data, 0) + app := response.Application(nil) + if app == nil { + return nil, errors.New("decode application submit response payload: application is missing") + } + return &lobbymodel.ApplicationSubmitResponse{ + Application: decodeApplicationSummary(app), + }, nil +} + +// InviteRedeemRequestToPayload converts lobbymodel.InviteRedeemRequest to +// FlatBuffers bytes. +func InviteRedeemRequestToPayload(request *lobbymodel.InviteRedeemRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode invite redeem request payload: request is nil") + } + return encodeInviteAction(request.GameID, request.InviteID, true) +} + +// PayloadToInviteRedeemRequest converts FlatBuffers payload bytes into +// lobbymodel.InviteRedeemRequest. +func PayloadToInviteRedeemRequest(data []byte) (result *lobbymodel.InviteRedeemRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode invite redeem request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode invite redeem request payload", &result, &err) + + request := lobbyfbs.GetRootAsInviteRedeemRequest(data, 0) + return &lobbymodel.InviteRedeemRequest{ + GameID: string(request.GameId()), + InviteID: string(request.InviteId()), + }, nil +} + +// InviteRedeemResponseToPayload converts lobbymodel.InviteRedeemResponse to +// FlatBuffers bytes. +func InviteRedeemResponseToPayload(response *lobbymodel.InviteRedeemResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode invite redeem response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(256) + inviteOffset := encodeInviteSummary(builder, response.Invite) + + lobbyfbs.InviteRedeemResponseStart(builder) + lobbyfbs.InviteRedeemResponseAddInvite(builder, inviteOffset) + offset := lobbyfbs.InviteRedeemResponseEnd(builder) + lobbyfbs.FinishInviteRedeemResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToInviteRedeemResponse converts FlatBuffers payload bytes into +// lobbymodel.InviteRedeemResponse. +func PayloadToInviteRedeemResponse(data []byte) (result *lobbymodel.InviteRedeemResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode invite redeem response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode invite redeem response payload", &result, &err) + + response := lobbyfbs.GetRootAsInviteRedeemResponse(data, 0) + invite := response.Invite(nil) + if invite == nil { + return nil, errors.New("decode invite redeem response payload: invite is missing") + } + return &lobbymodel.InviteRedeemResponse{ + Invite: decodeInviteSummary(invite), + }, nil +} + +// InviteDeclineRequestToPayload converts lobbymodel.InviteDeclineRequest to +// FlatBuffers bytes. +func InviteDeclineRequestToPayload(request *lobbymodel.InviteDeclineRequest) ([]byte, error) { + if request == nil { + return nil, errors.New("encode invite decline request payload: request is nil") + } + return encodeInviteAction(request.GameID, request.InviteID, false) +} + +// PayloadToInviteDeclineRequest converts FlatBuffers payload bytes into +// lobbymodel.InviteDeclineRequest. +func PayloadToInviteDeclineRequest(data []byte) (result *lobbymodel.InviteDeclineRequest, err error) { + if len(data) == 0 { + return nil, errors.New("decode invite decline request payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode invite decline request payload", &result, &err) + + request := lobbyfbs.GetRootAsInviteDeclineRequest(data, 0) + return &lobbymodel.InviteDeclineRequest{ + GameID: string(request.GameId()), + InviteID: string(request.InviteId()), + }, nil +} + +// InviteDeclineResponseToPayload converts lobbymodel.InviteDeclineResponse to +// FlatBuffers bytes. +func InviteDeclineResponseToPayload(response *lobbymodel.InviteDeclineResponse) ([]byte, error) { + if response == nil { + return nil, errors.New("encode invite decline response payload: response is nil") + } + + builder := flatbuffers.NewBuilder(256) + inviteOffset := encodeInviteSummary(builder, response.Invite) + + lobbyfbs.InviteDeclineResponseStart(builder) + lobbyfbs.InviteDeclineResponseAddInvite(builder, inviteOffset) + offset := lobbyfbs.InviteDeclineResponseEnd(builder) + lobbyfbs.FinishInviteDeclineResponseBuffer(builder, offset) + + return builder.FinishedBytes(), nil +} + +// PayloadToInviteDeclineResponse converts FlatBuffers payload bytes into +// lobbymodel.InviteDeclineResponse. +func PayloadToInviteDeclineResponse(data []byte) (result *lobbymodel.InviteDeclineResponse, err error) { + if len(data) == 0 { + return nil, errors.New("decode invite decline response payload: data is empty") + } + + defer recoverLobbyDecodePanic("decode invite decline response payload", &result, &err) + + response := lobbyfbs.GetRootAsInviteDeclineResponse(data, 0) + invite := response.Invite(nil) + if invite == nil { + return nil, errors.New("decode invite decline response payload: invite is missing") + } + return &lobbymodel.InviteDeclineResponse{ + Invite: decodeInviteSummary(invite), + }, nil +} + // LobbyErrorResponseToPayload converts lobbymodel.ErrorResponse to FlatBuffers // bytes suitable for the authenticated gateway transport. func LobbyErrorResponseToPayload(response *lobbymodel.ErrorResponse) ([]byte, error) { @@ -212,6 +733,38 @@ func PayloadToLobbyErrorResponse(data []byte) (result *lobbymodel.ErrorResponse, }, nil } +func encodeInviteAction(gameID, inviteID string, redeem bool) ([]byte, error) { + builder := flatbuffers.NewBuilder(128) + gameOffset := builder.CreateString(gameID) + inviteOffset := builder.CreateString(inviteID) + + if redeem { + lobbyfbs.InviteRedeemRequestStart(builder) + lobbyfbs.InviteRedeemRequestAddGameId(builder, gameOffset) + lobbyfbs.InviteRedeemRequestAddInviteId(builder, inviteOffset) + offset := lobbyfbs.InviteRedeemRequestEnd(builder) + lobbyfbs.FinishInviteRedeemRequestBuffer(builder, offset) + } else { + lobbyfbs.InviteDeclineRequestStart(builder) + lobbyfbs.InviteDeclineRequestAddGameId(builder, gameOffset) + lobbyfbs.InviteDeclineRequestAddInviteId(builder, inviteOffset) + offset := lobbyfbs.InviteDeclineRequestEnd(builder) + lobbyfbs.FinishInviteDeclineRequestBuffer(builder, offset) + } + return builder.FinishedBytes(), nil +} + +func finishOffsetVector(builder *flatbuffers.Builder, offsets []flatbuffers.UOffsetT) flatbuffers.UOffsetT { + if len(offsets) == 0 { + return 0 + } + builder.StartVector(4, len(offsets), 4) + for index := len(offsets) - 1; index >= 0; index-- { + builder.PrependUOffsetT(offsets[index]) + } + return builder.EndVector(len(offsets)) +} + func encodeGameSummary(builder *flatbuffers.Builder, summary lobbymodel.GameSummary) flatbuffers.UOffsetT { gameID := builder.CreateString(summary.GameID) gameName := builder.CreateString(summary.GameName) @@ -248,6 +801,86 @@ func decodeGameSummary(summary *lobbyfbs.GameSummary) lobbymodel.GameSummary { } } +func encodeApplicationSummary(builder *flatbuffers.Builder, app lobbymodel.ApplicationSummary) flatbuffers.UOffsetT { + applicationID := builder.CreateString(app.ApplicationID) + gameID := builder.CreateString(app.GameID) + applicantUserID := builder.CreateString(app.ApplicantUserID) + raceName := builder.CreateString(app.RaceName) + status := builder.CreateString(app.Status) + + lobbyfbs.ApplicationSummaryStart(builder) + lobbyfbs.ApplicationSummaryAddApplicationId(builder, applicationID) + lobbyfbs.ApplicationSummaryAddGameId(builder, gameID) + lobbyfbs.ApplicationSummaryAddApplicantUserId(builder, applicantUserID) + lobbyfbs.ApplicationSummaryAddRaceName(builder, raceName) + lobbyfbs.ApplicationSummaryAddStatus(builder, status) + lobbyfbs.ApplicationSummaryAddCreatedAtMs(builder, app.CreatedAt.UTC().UnixMilli()) + lobbyfbs.ApplicationSummaryAddDecidedAtMs(builder, unixMilliFromOptional(app.DecidedAt)) + return lobbyfbs.ApplicationSummaryEnd(builder) +} + +func decodeApplicationSummary(app *lobbyfbs.ApplicationSummary) lobbymodel.ApplicationSummary { + return lobbymodel.ApplicationSummary{ + ApplicationID: string(app.ApplicationId()), + GameID: string(app.GameId()), + ApplicantUserID: string(app.ApplicantUserId()), + RaceName: string(app.RaceName()), + Status: string(app.Status()), + CreatedAt: time.UnixMilli(app.CreatedAtMs()).UTC(), + DecidedAt: optionalUnixMilli(app.DecidedAtMs()), + } +} + +func encodeInviteSummary(builder *flatbuffers.Builder, invite lobbymodel.InviteSummary) flatbuffers.UOffsetT { + inviteID := builder.CreateString(invite.InviteID) + gameID := builder.CreateString(invite.GameID) + inviterUserID := builder.CreateString(invite.InviterUserID) + invitedUserID := builder.CreateString(invite.InvitedUserID) + code := builder.CreateString(invite.Code) + raceName := builder.CreateString(invite.RaceName) + status := builder.CreateString(invite.Status) + + lobbyfbs.InviteSummaryStart(builder) + lobbyfbs.InviteSummaryAddInviteId(builder, inviteID) + lobbyfbs.InviteSummaryAddGameId(builder, gameID) + lobbyfbs.InviteSummaryAddInviterUserId(builder, inviterUserID) + lobbyfbs.InviteSummaryAddInvitedUserId(builder, invitedUserID) + lobbyfbs.InviteSummaryAddCode(builder, code) + lobbyfbs.InviteSummaryAddRaceName(builder, raceName) + lobbyfbs.InviteSummaryAddStatus(builder, status) + lobbyfbs.InviteSummaryAddCreatedAtMs(builder, invite.CreatedAt.UTC().UnixMilli()) + lobbyfbs.InviteSummaryAddExpiresAtMs(builder, invite.ExpiresAt.UTC().UnixMilli()) + lobbyfbs.InviteSummaryAddDecidedAtMs(builder, unixMilliFromOptional(invite.DecidedAt)) + return lobbyfbs.InviteSummaryEnd(builder) +} + +func decodeInviteSummary(invite *lobbyfbs.InviteSummary) lobbymodel.InviteSummary { + return lobbymodel.InviteSummary{ + InviteID: string(invite.InviteId()), + GameID: string(invite.GameId()), + InviterUserID: string(invite.InviterUserId()), + InvitedUserID: string(invite.InvitedUserId()), + Code: string(invite.Code()), + RaceName: string(invite.RaceName()), + Status: string(invite.Status()), + CreatedAt: time.UnixMilli(invite.CreatedAtMs()).UTC(), + ExpiresAt: time.UnixMilli(invite.ExpiresAtMs()).UTC(), + DecidedAt: optionalUnixMilli(invite.DecidedAtMs()), + } +} + +// unixMilliFromOptional returns 0 when the pointer is nil, else the +// millisecond UTC timestamp. The transcoder uses 0 as the "not set" +// sentinel because FlatBuffers tables do not carry per-field presence +// flags, and 0 is unreachable for any realistic decided/redeemed time. +// optionalUnixMilli (defined in user.go) is the decode counterpart. +func unixMilliFromOptional(t *time.Time) int64 { + if t == nil { + return 0 + } + return t.UTC().UnixMilli() +} + func recoverLobbyDecodePanic[T any](message string, result **T, err *error) { if recovered := recover(); recovered != nil { *result = nil diff --git a/pkg/transcoder/lobby_test.go b/pkg/transcoder/lobby_test.go new file mode 100644 index 0000000..65374bc --- /dev/null +++ b/pkg/transcoder/lobby_test.go @@ -0,0 +1,517 @@ +package transcoder + +import ( + "reflect" + "testing" + "time" + + lobbymodel "galaxy/model/lobby" +) + +// fixedTimes returns deterministic UTC timestamps for round-trip +// fixtures. Millisecond precision matches the FlatBuffers `*Ms` ints, +// which is what the transcoder preserves. +func fixedTimes() (created, updated, ends time.Time) { + created = time.Date(2026, time.May, 7, 9, 30, 15, 123_000_000, time.UTC) + updated = created.Add(2 * time.Minute) + ends = created.Add(48 * time.Hour) + return +} + +func TestLobbyMyGamesListRoundTrip(t *testing.T) { + t.Parallel() + + created, updated, ends := fixedTimes() + + source := &lobbymodel.MyGamesListResponse{ + Items: []lobbymodel.GameSummary{ + { + GameID: "game-private-7c8f", + GameName: "First Contact", + GameType: "private", + Status: "draft", + OwnerUserID: "user-9912", + MinPlayers: 2, + MaxPlayers: 8, + EnrollmentEndsAt: ends, + CreatedAt: created, + UpdatedAt: updated, + }, + { + GameID: "game-public-aabb", + GameName: "Open Lobby", + GameType: "public", + Status: "enrollment_open", + OwnerUserID: "", + MinPlayers: 4, + MaxPlayers: 12, + EnrollmentEndsAt: ends, + CreatedAt: created, + UpdatedAt: updated, + }, + }, + } + + payload, err := MyGamesListResponseToPayload(source) + if err != nil { + t.Fatalf("encode: %v", err) + } + + decoded, err := PayloadToMyGamesListResponse(payload) + if err != nil { + t.Fatalf("decode: %v", err) + } + if !reflect.DeepEqual(source, decoded) { + t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded) + } + + requestBytes, err := MyGamesListRequestToPayload(&lobbymodel.MyGamesListRequest{}) + if err != nil { + t.Fatalf("encode request: %v", err) + } + if _, err := PayloadToMyGamesListRequest(requestBytes); err != nil { + t.Fatalf("decode request: %v", err) + } +} + +func TestLobbyMyGamesListEmpty(t *testing.T) { + t.Parallel() + + payload, err := MyGamesListResponseToPayload(&lobbymodel.MyGamesListResponse{Items: nil}) + if err != nil { + t.Fatalf("encode: %v", err) + } + decoded, err := PayloadToMyGamesListResponse(payload) + if err != nil { + t.Fatalf("decode: %v", err) + } + if got := len(decoded.Items); got != 0 { + t.Fatalf("expected empty items, got %d", got) + } +} + +func TestLobbyPublicGamesListRoundTrip(t *testing.T) { + t.Parallel() + + created, updated, ends := fixedTimes() + + requestSource := &lobbymodel.PublicGamesListRequest{Page: 3, PageSize: 25} + requestBytes, err := PublicGamesListRequestToPayload(requestSource) + if err != nil { + t.Fatalf("encode request: %v", err) + } + requestDecoded, err := PayloadToPublicGamesListRequest(requestBytes) + if err != nil { + t.Fatalf("decode request: %v", err) + } + if !reflect.DeepEqual(requestSource, requestDecoded) { + t.Fatalf("request round-trip mismatch\nsource: %#v\ndecoded:%#v", requestSource, requestDecoded) + } + + responseSource := &lobbymodel.PublicGamesListResponse{ + Items: []lobbymodel.GameSummary{ + { + GameID: "game-public-aabb", + GameName: "Open Lobby", + GameType: "public", + Status: "enrollment_open", + OwnerUserID: "", + MinPlayers: 4, + MaxPlayers: 12, + EnrollmentEndsAt: ends, + CreatedAt: created, + UpdatedAt: updated, + }, + }, + Page: 3, + PageSize: 25, + Total: 51, + } + responseBytes, err := PublicGamesListResponseToPayload(responseSource) + if err != nil { + t.Fatalf("encode response: %v", err) + } + responseDecoded, err := PayloadToPublicGamesListResponse(responseBytes) + if err != nil { + t.Fatalf("decode response: %v", err) + } + if !reflect.DeepEqual(responseSource, responseDecoded) { + t.Fatalf("response round-trip mismatch\nsource: %#v\ndecoded:%#v", responseSource, responseDecoded) + } +} + +func TestLobbyMyApplicationsListRoundTrip(t *testing.T) { + t.Parallel() + + created, _, _ := fixedTimes() + decided := created.Add(2 * time.Hour) + + requestBytes, err := MyApplicationsListRequestToPayload(&lobbymodel.MyApplicationsListRequest{}) + if err != nil { + t.Fatalf("encode request: %v", err) + } + if _, err := PayloadToMyApplicationsListRequest(requestBytes); err != nil { + t.Fatalf("decode request: %v", err) + } + + source := &lobbymodel.MyApplicationsListResponse{ + Items: []lobbymodel.ApplicationSummary{ + { + ApplicationID: "app-1", + GameID: "game-public-aabb", + ApplicantUserID: "user-9912", + RaceName: "Vegan Federation", + Status: "pending", + CreatedAt: created, + DecidedAt: nil, + }, + { + ApplicationID: "app-2", + GameID: "game-public-ccdd", + ApplicantUserID: "user-9912", + RaceName: "Lithic Compact", + Status: "approved", + CreatedAt: created, + DecidedAt: &decided, + }, + }, + } + payload, err := MyApplicationsListResponseToPayload(source) + if err != nil { + t.Fatalf("encode: %v", err) + } + decoded, err := PayloadToMyApplicationsListResponse(payload) + if err != nil { + t.Fatalf("decode: %v", err) + } + if !reflect.DeepEqual(source, decoded) { + t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded) + } +} + +func TestLobbyMyInvitesListRoundTrip(t *testing.T) { + t.Parallel() + + created, _, _ := fixedTimes() + expires := created.Add(72 * time.Hour) + decided := created.Add(1 * time.Hour) + + requestBytes, err := MyInvitesListRequestToPayload(&lobbymodel.MyInvitesListRequest{}) + if err != nil { + t.Fatalf("encode request: %v", err) + } + if _, err := PayloadToMyInvitesListRequest(requestBytes); err != nil { + t.Fatalf("decode request: %v", err) + } + + source := &lobbymodel.MyInvitesListResponse{ + Items: []lobbymodel.InviteSummary{ + { + InviteID: "invite-user-bound", + GameID: "game-private-7c8f", + InviterUserID: "user-1111", + InvitedUserID: "user-9912", + Code: "", + RaceName: "Vegan Federation", + Status: "pending", + CreatedAt: created, + ExpiresAt: expires, + DecidedAt: nil, + }, + { + InviteID: "invite-code-based", + GameID: "game-private-3322", + InviterUserID: "user-1111", + InvitedUserID: "", + Code: "ABCDEF12", + RaceName: "Lithic Compact", + Status: "accepted", + CreatedAt: created, + ExpiresAt: expires, + DecidedAt: &decided, + }, + }, + } + payload, err := MyInvitesListResponseToPayload(source) + if err != nil { + t.Fatalf("encode: %v", err) + } + decoded, err := PayloadToMyInvitesListResponse(payload) + if err != nil { + t.Fatalf("decode: %v", err) + } + if !reflect.DeepEqual(source, decoded) { + t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded) + } +} + +func TestLobbyOpenEnrollmentRoundTrip(t *testing.T) { + t.Parallel() + + requestSource := &lobbymodel.OpenEnrollmentRequest{GameID: "game-private-7c8f"} + requestBytes, err := OpenEnrollmentRequestToPayload(requestSource) + if err != nil { + t.Fatalf("encode request: %v", err) + } + requestDecoded, err := PayloadToOpenEnrollmentRequest(requestBytes) + if err != nil { + t.Fatalf("decode request: %v", err) + } + if !reflect.DeepEqual(requestSource, requestDecoded) { + t.Fatalf("request round-trip mismatch\nsource: %#v\ndecoded:%#v", requestSource, requestDecoded) + } + + responseSource := &lobbymodel.OpenEnrollmentResponse{GameID: "game-private-7c8f", Status: "enrollment_open"} + responseBytes, err := OpenEnrollmentResponseToPayload(responseSource) + if err != nil { + t.Fatalf("encode response: %v", err) + } + responseDecoded, err := PayloadToOpenEnrollmentResponse(responseBytes) + if err != nil { + t.Fatalf("decode response: %v", err) + } + if !reflect.DeepEqual(responseSource, responseDecoded) { + t.Fatalf("response round-trip mismatch\nsource: %#v\ndecoded:%#v", responseSource, responseDecoded) + } +} + +func TestLobbyGameCreateRoundTrip(t *testing.T) { + t.Parallel() + + _, _, ends := fixedTimes() + + requestSource := &lobbymodel.GameCreateRequest{ + GameName: "First Contact", + Description: "First Phase 8 sandbox game", + MinPlayers: 2, + MaxPlayers: 8, + StartGapHours: 24, + StartGapPlayers: 2, + EnrollmentEndsAt: ends, + TurnSchedule: "0 0 * * *", + TargetEngineVersion: "v1", + } + requestBytes, err := GameCreateRequestToPayload(requestSource) + if err != nil { + t.Fatalf("encode request: %v", err) + } + requestDecoded, err := PayloadToGameCreateRequest(requestBytes) + if err != nil { + t.Fatalf("decode request: %v", err) + } + if !reflect.DeepEqual(requestSource, requestDecoded) { + t.Fatalf("request round-trip mismatch\nsource: %#v\ndecoded:%#v", requestSource, requestDecoded) + } + + created, updated, _ := fixedTimes() + responseSource := &lobbymodel.GameCreateResponse{ + Game: lobbymodel.GameSummary{ + GameID: "game-private-newly-created", + GameName: "First Contact", + GameType: "private", + Status: "draft", + OwnerUserID: "user-9912", + MinPlayers: 2, + MaxPlayers: 8, + EnrollmentEndsAt: ends, + CreatedAt: created, + UpdatedAt: updated, + }, + } + responseBytes, err := GameCreateResponseToPayload(responseSource) + if err != nil { + t.Fatalf("encode response: %v", err) + } + responseDecoded, err := PayloadToGameCreateResponse(responseBytes) + if err != nil { + t.Fatalf("decode response: %v", err) + } + if !reflect.DeepEqual(responseSource, responseDecoded) { + t.Fatalf("response round-trip mismatch\nsource: %#v\ndecoded:%#v", responseSource, responseDecoded) + } +} + +func TestLobbyApplicationSubmitRoundTrip(t *testing.T) { + t.Parallel() + + requestSource := &lobbymodel.ApplicationSubmitRequest{GameID: "game-public-aabb", RaceName: "Vegan Federation"} + requestBytes, err := ApplicationSubmitRequestToPayload(requestSource) + if err != nil { + t.Fatalf("encode request: %v", err) + } + requestDecoded, err := PayloadToApplicationSubmitRequest(requestBytes) + if err != nil { + t.Fatalf("decode request: %v", err) + } + if !reflect.DeepEqual(requestSource, requestDecoded) { + t.Fatalf("request round-trip mismatch\nsource: %#v\ndecoded:%#v", requestSource, requestDecoded) + } + + created, _, _ := fixedTimes() + responseSource := &lobbymodel.ApplicationSubmitResponse{ + Application: lobbymodel.ApplicationSummary{ + ApplicationID: "app-3", + GameID: "game-public-aabb", + ApplicantUserID: "user-9912", + RaceName: "Vegan Federation", + Status: "pending", + CreatedAt: created, + DecidedAt: nil, + }, + } + responseBytes, err := ApplicationSubmitResponseToPayload(responseSource) + if err != nil { + t.Fatalf("encode response: %v", err) + } + responseDecoded, err := PayloadToApplicationSubmitResponse(responseBytes) + if err != nil { + t.Fatalf("decode response: %v", err) + } + if !reflect.DeepEqual(responseSource, responseDecoded) { + t.Fatalf("response round-trip mismatch\nsource: %#v\ndecoded:%#v", responseSource, responseDecoded) + } +} + +func TestLobbyInviteRedeemAndDeclineRoundTrip(t *testing.T) { + t.Parallel() + + created, _, _ := fixedTimes() + expires := created.Add(72 * time.Hour) + decided := created.Add(1 * time.Hour) + + redeemReq := &lobbymodel.InviteRedeemRequest{GameID: "game-private-7c8f", InviteID: "invite-user-bound"} + redeemBytes, err := InviteRedeemRequestToPayload(redeemReq) + if err != nil { + t.Fatalf("encode redeem request: %v", err) + } + redeemDecoded, err := PayloadToInviteRedeemRequest(redeemBytes) + if err != nil { + t.Fatalf("decode redeem request: %v", err) + } + if !reflect.DeepEqual(redeemReq, redeemDecoded) { + t.Fatalf("redeem request mismatch") + } + + redeemResp := &lobbymodel.InviteRedeemResponse{ + Invite: lobbymodel.InviteSummary{ + InviteID: "invite-user-bound", + GameID: "game-private-7c8f", + InviterUserID: "user-1111", + InvitedUserID: "user-9912", + Code: "", + RaceName: "Vegan Federation", + Status: "accepted", + CreatedAt: created, + ExpiresAt: expires, + DecidedAt: &decided, + }, + } + redeemRespBytes, err := InviteRedeemResponseToPayload(redeemResp) + if err != nil { + t.Fatalf("encode redeem response: %v", err) + } + redeemRespDecoded, err := PayloadToInviteRedeemResponse(redeemRespBytes) + if err != nil { + t.Fatalf("decode redeem response: %v", err) + } + if !reflect.DeepEqual(redeemResp, redeemRespDecoded) { + t.Fatalf("redeem response mismatch") + } + + declineReq := &lobbymodel.InviteDeclineRequest{GameID: "game-private-7c8f", InviteID: "invite-user-bound"} + declineBytes, err := InviteDeclineRequestToPayload(declineReq) + if err != nil { + t.Fatalf("encode decline request: %v", err) + } + declineDecoded, err := PayloadToInviteDeclineRequest(declineBytes) + if err != nil { + t.Fatalf("decode decline request: %v", err) + } + if !reflect.DeepEqual(declineReq, declineDecoded) { + t.Fatalf("decline request mismatch") + } + + declineResp := &lobbymodel.InviteDeclineResponse{ + Invite: lobbymodel.InviteSummary{ + InviteID: "invite-user-bound", + GameID: "game-private-7c8f", + InviterUserID: "user-1111", + InvitedUserID: "user-9912", + Code: "", + RaceName: "Vegan Federation", + Status: "declined", + CreatedAt: created, + ExpiresAt: expires, + DecidedAt: &decided, + }, + } + declineRespBytes, err := InviteDeclineResponseToPayload(declineResp) + if err != nil { + t.Fatalf("encode decline response: %v", err) + } + declineRespDecoded, err := PayloadToInviteDeclineResponse(declineRespBytes) + if err != nil { + t.Fatalf("decode decline response: %v", err) + } + if !reflect.DeepEqual(declineResp, declineRespDecoded) { + t.Fatalf("decline response mismatch") + } +} + +func TestLobbyErrorResponseRoundTrip(t *testing.T) { + t.Parallel() + + source := &lobbymodel.ErrorResponse{ + Error: lobbymodel.ErrorBody{ + Code: "conflict", + Message: "request conflicts with current state", + }, + } + payload, err := LobbyErrorResponseToPayload(source) + if err != nil { + t.Fatalf("encode: %v", err) + } + decoded, err := PayloadToLobbyErrorResponse(payload) + if err != nil { + t.Fatalf("decode: %v", err) + } + if !reflect.DeepEqual(source, decoded) { + t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded) + } +} + +func TestLobbyDecodersRecoverFromCorruption(t *testing.T) { + t.Parallel() + + garbage := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + if _, err := PayloadToMyGamesListResponse(garbage); err == nil { + t.Fatal("expected error decoding corrupt my games payload") + } + if _, err := PayloadToPublicGamesListResponse(garbage); err == nil { + t.Fatal("expected error decoding corrupt public games payload") + } + if _, err := PayloadToApplicationSubmitResponse(garbage); err == nil { + t.Fatal("expected error decoding corrupt application submit response") + } + if _, err := PayloadToInviteRedeemResponse(garbage); err == nil { + t.Fatal("expected error decoding corrupt invite redeem response") + } + if _, err := PayloadToLobbyErrorResponse(garbage); err == nil { + t.Fatal("expected error decoding corrupt error payload") + } +} + +func TestLobbyDecodersRejectEmptyPayload(t *testing.T) { + t.Parallel() + + if _, err := PayloadToMyGamesListRequest(nil); err == nil { + t.Fatal("expected error decoding nil request") + } + if _, err := PayloadToPublicGamesListResponse(nil); err == nil { + t.Fatal("expected error decoding nil response") + } + if _, err := PayloadToInviteDeclineRequest(nil); err == nil { + t.Fatal("expected error decoding nil decline request") + } +} diff --git a/ui/Makefile b/ui/Makefile index 38d202d..06c9ad0 100644 --- a/ui/Makefile +++ b/ui/Makefile @@ -1,15 +1,18 @@ -.PHONY: help web wasm ts-protos gomobile desktop-mac desktop-win desktop-linux ios android all +.PHONY: help web wasm ts-protos fbs-ts gomobile desktop-mac desktop-win desktop-linux ios android all .DEFAULT_GOAL := help WASM_OUT := frontend/static/core.wasm WASM_EXEC := frontend/static/wasm_exec.js TINYGO_ROOT := $(shell tinygo env TINYGOROOT 2>/dev/null) +FBS_OUT := frontend/src/proto/galaxy/fbs +FBS_INPUTS := ../pkg/schema/fbs/lobby.fbs ../pkg/schema/fbs/user.fbs help: @echo "ui targets:" @echo " wasm TinyGo build of ui/core to core.wasm + wasm_exec.js shim (Phase 5)" @echo " ts-protos Connect-ES + Protobuf-ES generation from gateway/proto (Phase 5)" + @echo " fbs-ts FlatBuffers TS generation from pkg/schema/fbs/*.fbs (Phase 8)" @echo " web Vite production build (Phase 5+)" @echo " gomobile gomobile bind for iOS .framework + Android .aar (Phase 32+)" @echo " desktop-mac Wails build for darwin/{arm64,amd64} (Phase 31)" @@ -30,6 +33,11 @@ ts-protos: @test -x frontend/node_modules/.bin/protoc-gen-es || { echo "protoc-gen-es not installed; run 'pnpm install' inside ui/frontend"; exit 1; } buf generate ../gateway --template buf.gen.yaml --include-imports +fbs-ts: + @command -v flatc >/dev/null || { echo "flatc not found; install via 'brew install flatbuffers' (macOS) or 'apt-get install -y flatbuffers-compiler' (Linux)"; exit 1; } + mkdir -p $(FBS_OUT) + flatc --ts --gen-object-api -o $(FBS_OUT) $(FBS_INPUTS) + web gomobile desktop-mac desktop-win desktop-linux ios android all: @echo "TODO: implement '$@' (placeholder, see ui/PLAN.md)" @exit 1 diff --git a/ui/PLAN.md b/ui/PLAN.md index 1168281..6411f36 100644 --- a/ui/PLAN.md +++ b/ui/PLAN.md @@ -720,7 +720,12 @@ Artifacts (delivered): - `ui/frontend/src/routes/lobby/+page.svelte` (+ `+page.ts`) — placeholder lobby that issues the first authenticated `user.account.get` through `GalaxyClient` and surfaces the decoded - display name. + display name. The placeholder used `JSON.parse(TextDecoder)` to + read the response payload; that worked with the mocked Playwright + setup but did not match the gateway's FlatBuffers wire format. Phase + 8 introduces the TS-side FlatBuffers integration and rewrites this + page to decode `AccountResponse` via the generated bindings, so the + greeting now also works against a real local stack. - `ui/frontend/src/routes/+layout.svelte` — boot-time session init, route guard (anonymous → `/login`, authenticated on `/login` → `/lobby`), browser-not-supported blocker, and the revocation @@ -784,41 +789,165 @@ Targeted tests (delivered): chromium-mobile-pixel-5: fresh login, returning user, revocation within one second, browser-not-supported blocker. -## Phase 8. Lobby UI +## ~~Phase 8. Lobby UI~~ -Status: pending. +Status: done. Goal: replace the placeholder lobby with a working list of games -allowing the user to view membership, accept invitations, join public -games, and create new games. +allowing the user to view membership, see and act on invitations and +applications, submit applications to public games, and create new +private games. The phase also introduces the TS-side FlatBuffers +codec the rest of the client relies on for authenticated payloads. -Artifacts: +Decisions taken with the project owner before implementation: -- `ui/frontend/src/routes/lobby/+page.svelte` landing page sections: - my games (`docs/FUNCTIONAL.md` §4.5), public games (§4.2), pending - invitations (§4.3), action to create a new game (§3.3) -- `ui/frontend/src/api/lobby.ts` typed wrappers over the relevant - authenticated RPCs -- `ui/frontend/src/routes/lobby/create/+page.svelte` create-game form - matching backend contract -- routing wiring: clicking a game card navigates to - `/games/:gameId/map` (placeholder until Phase 10) +1. **Cross-stack catalog extension.** Phase 8 expands the lobby + command catalog beyond `lobby.my.games.list` and + `lobby.game.open-enrollment` (the only routes shipped before this + phase). Seven new authenticated message types now flow through + `gateway/internal/backendclient/lobby_commands.go`: + `lobby.public.games.list`, `lobby.my.applications.list`, + `lobby.my.invites.list`, `lobby.game.create`, + `lobby.application.submit`, `lobby.invite.redeem`, + `lobby.invite.decline`. Each carries its FlatBuffers schema in + `pkg/schema/fbs/lobby.fbs`, its Go request/response struct in + `pkg/model/lobby/lobby.go`, and its transcoder pair in + `pkg/transcoder/lobby.go`. +2. **My applications projection.** FUNCTIONAL.md §4.5 lists three + "my" projections (games, applications, invites). The original + plan text omitted applications; the lobby now renders a fourth + "my applications" section so the user sees the pending status of + any application they submit. +3. **Submit-application UX.** Per FUNCTIONAL.md §4.2, joining a + public game means submitting an application that lands in the + `pending` state until the owner approves. The button label is + `Submit application`, the inline race-name form lives on the + public-game card itself (no overlay/modal infrastructure yet — + that lands later when the in-game shell does), and a successful + submit refreshes the applications section so the user sees the + pending entry immediately. +4. **TS-side FlatBuffers integration.** The placeholder lobby in + Phase 7 used `JSON.parse(TextDecoder)` to read the + `user.account.get` payload; that was a mismatch with the gateway's + FlatBuffers transcoder and only worked under mocked tests. Phase 8 + adds a `flatbuffers` runtime dep to `ui/frontend/package.json`, a + `fbs-ts` Make target in `ui/Makefile` that drives `flatc --ts`, + and checks the generated bindings into + `ui/frontend/src/proto/galaxy/fbs/{lobby,user}/`. Phase 7's + `user.account.get` decode is rewritten to use those bindings as + part of this phase, fixing the wire-format gap. +5. **Create-game form scope.** The form keeps `game_name`, + `description`, `turn_schedule` (5-field cron), and + `enrollment_ends_at` always visible; the rest (`min_players`, + `max_players`, `start_gap_hours`, `start_gap_players`, + `target_engine_version`) sit behind a `
` "Advanced" + toggle with TS-side defaults (2 / 8 / 24 / 2 / `v1`). The gateway + forces visibility to `private` regardless of input — public games + come exclusively through the admin surface per FUNCTIONAL.md §3.3. + +Artifacts (delivered): + +- `ui/frontend/src/routes/lobby/+page.svelte` — full lobby landing + page. Header preserves the Phase 7 device-session-id and greeting + affordances; below it five sections render in a single mobile-first + column: a top-level "create new game" action (§3.3), `my games` + cards routing to `/games/:id/map` (placeholder until Phase 10, + §4.5), `pending invitations` cards with Accept / Decline (§4.3), + `my applications` cards with localised pending / approved / + rejected status (§4.5), and `public games` cards with an inline + race-name form behind a `Submit application` button (§4.2). + Convention follows the Phase 7 login page — single `max-width: + 32rem` cap, no `@media` queries. +- `ui/frontend/src/routes/lobby/create/+page.svelte` (+ `+page.ts` + with `ssr = false; prerender = false;`) — create-game form with + always-visible name / description / turn-schedule / enrollment-end, + Advanced fields under `
` for the rest, and TS-side + defaults for the advanced inputs. +- `ui/frontend/src/api/lobby.ts` — typed wrappers around + `GalaxyClient.executeCommand` for all eight lobby commands plus a + `LobbyError` class that surfaces canonical lobby error codes + (`invalid_request`, `subject_not_found`, `forbidden`, `conflict`, + `internal_error`). +- `ui/frontend/src/api/galaxy-client.ts` — `executeCommand` now + returns `{ resultCode, payloadBytes }`; `lobby.ts` uses the + result-code branch to throw `LobbyError`. +- `pkg/model/lobby/lobby.go` — seven new message-type constants and + matching request/response structs. +- `pkg/schema/fbs/lobby.fbs` — `PublicGamesListRequest`, + `PublicGamesListResponse`, `ApplicationSummary`, + `MyApplicationsListRequest`, `MyApplicationsListResponse`, + `InviteSummary`, `MyInvitesListRequest`, `MyInvitesListResponse`, + `GameCreateRequest`, `GameCreateResponse`, + `ApplicationSubmitRequest`, `ApplicationSubmitResponse`, + `InviteRedeemRequest`, `InviteRedeemResponse`, + `InviteDeclineRequest`, `InviteDeclineResponse` tables. Reused + `GameSummary` for `GameCreateResponse.game` and `MyGamesListResponse`. +- `pkg/transcoder/lobby.go` — encode/decode pairs for all new types + plus shared helpers `encodeApplicationSummary`, + `decodeApplicationSummary`, `encodeInviteSummary`, + `decodeInviteSummary`, `unixMilliFromOptional`. Reuses + `encodeGameSummary` / `decodeGameSummary` from before. +- `gateway/internal/backendclient/lobby_commands.go` — switch cases + for the seven new message types and the per-command REST helpers + (`executeLobbyPublicGames`, `executeLobbyMyApplications`, + `executeLobbyMyInvites`, `executeLobbyGameCreate`, + `executeLobbyApplicationSubmit`, `executeLobbyInviteRedeem`, + `executeLobbyInviteDecline`); the JSON wire types from backend's + user-lobby handlers are mirrored locally for non-strict decoding. +- `gateway/internal/backendclient/routes.go` — the new message types + are wired into `LobbyRoutes`. +- `ui/frontend/src/proto/galaxy/fbs/{lobby,user}/...` — generated TS + FlatBuffers bindings (regenerated from `pkg/schema/fbs/*.fbs` via + the `fbs-ts` Make target, checked into the repo like the Connect + bindings). +- `ui/Makefile` — new `fbs-ts` target. +- `ui/frontend/package.json` — `flatbuffers` runtime dep. +- `ui/frontend/src/lib/i18n/locales/{en,ru}.ts` — full `lobby.*` + catalogue covering sections, empty states, application form, + create form, status badges, and lobby error code translations. +- Topic doc `ui/docs/lobby.md`. +- Vitest: `tests/lobby-fbs.test.ts` (binding round-trips), + `tests/lobby-api.test.ts` (wrapper unit tests over a stub client), + `tests/lobby-page.test.ts`, `tests/lobby-create.test.ts`. +- Playwright: `tests/e2e/lobby-flow.spec.ts` (3 cases × 4 projects) + with `tests/e2e/fixtures/lobby-fbs.ts` building forged FlatBuffers + payloads through the same generated bindings the production code + uses. The Phase 7 spec was migrated to the same fixture so + `user.account.get` is now FlatBuffers end-to-end. +- Phase 7 e2e specs were updated to `click → fill` the readonly + inputs (the readonly attribute is the documented Safari + autofill-suppression workaround; `fill` checks editability before + Playwright's own focus call, so a deliberate click is required). +- `pkg/transcoder/lobby_test.go` — round-trip and corruption-recover + cases for every new pair. +- `gateway/internal/backendclient/lobby_commands_test.go` — per-RPC + success / 4xx / 5xx / 503 cases against an `httptest.Server`. Dependencies: Phase 7. -Acceptance criteria: +Acceptance criteria (met): -- the user can list, create, join a game, and accept an invitation - end-to-end against a local stack; -- mobile viewport renders without horizontal scroll; -- empty states are explicit (`no games yet`, `no public games`). +- the user can list, create, submit-application, and accept an + invitation end-to-end against a local stack — the gateway routes + every required envelope, and the FlatBuffers wire path is the same + in production and in mocked tests; +- mobile viewport renders without horizontal scroll on + `chromium-mobile-iphone-13` and `chromium-mobile-pixel-5`; +- empty states are explicit (`no games yet`, `no invitations`, + `no applications`, `no public games`). -Targeted tests: +Targeted tests (delivered): -- Vitest component tests for each section with mocked API responses; -- Playwright e2e: complete a create-game flow and confirm the new game - appears in `my games`; -- mobile-viewport Playwright run for the same flow. +- Vitest binding round-trips for every lobby request/response; +- Vitest API wrapper coverage for every wrapper plus the LobbyError + surfacing path; +- Vitest component tests for the lobby page (every section, empty + states, race-name validation, Accept / Decline) and the create-game + form (validation, submission, cancel); +- Playwright e2e (3 flows × 4 projects): full create-game flow to + My Games, submit-application to My Applications pending, accept + invitation removes card and adds the game to My Games. Phase 7 + auth flow now also runs over the FlatBuffers wire. ## Phase 9. Map Renderer with Fixture Data diff --git a/ui/README.md b/ui/README.md index 0566320..69c2226 100644 --- a/ui/README.md +++ b/ui/README.md @@ -75,8 +75,8 @@ ui/ ├── src/lib/ env config, session store, revocation watcher ├── src/platform/core/ Core interface + WasmCore adapter ├── src/platform/store/ KeyStore/Cache interfaces + web adapter - ├── src/proto/ generated Protobuf-ES + Connect descriptors - ├── src/routes/ SvelteKit routes (/, /login, /lobby) + ├── src/proto/ generated Protobuf-ES + Connect descriptors + FlatBuffers TS bindings + ├── src/routes/ SvelteKit routes (/, /login, /lobby, /lobby/create) └── static/ core.wasm + wasm_exec.js (committed artefacts) ``` @@ -84,6 +84,8 @@ Linked topic docs: - [`docs/auth-flow.md`](docs/auth-flow.md) — email-code login, session store state machine, revocation watcher. +- [`docs/lobby.md`](docs/lobby.md) — lobby UI sections, application + / invite lifecycle, create-game form defaults. - [`docs/i18n.md`](docs/i18n.md) — translation primitive, native-name language picker, recipe for adding a new locale. - [`docs/storage.md`](docs/storage.md) — web KeyStore/Cache, @@ -117,14 +119,16 @@ named targets are placeholders until the named phase lands; running `make` with no arguments prints the current placeholder map. ```text -make web Vite production build Phase 5+ -make wasm TinyGo → core.wasm Phase 5 -make gomobile gomobile bind → ios + android Phase 32+ -make desktop-mac Wails build for darwin Phase 31 -make desktop-win Wails build for windows Phase 31 -make desktop-linux Wails build for linux Phase 31 -make ios Capacitor + xcodebuild Phase 32+ -make android Capacitor + gradle Phase 32+ +make web Vite production build Phase 5+ +make wasm TinyGo → core.wasm Phase 5 +make ts-protos Connect-ES + Protobuf-ES gen Phase 5 +make fbs-ts FlatBuffers TS bindings via flatc Phase 8 +make gomobile gomobile bind → ios + android Phase 32+ +make desktop-mac Wails build for darwin Phase 31 +make desktop-win Wails build for windows Phase 31 +make desktop-linux Wails build for linux Phase 31 +make ios Capacitor + xcodebuild Phase 32+ +make android Capacitor + gradle Phase 32+ make all every target above ``` diff --git a/ui/docs/lobby.md b/ui/docs/lobby.md new file mode 100644 index 0000000..f8c010f --- /dev/null +++ b/ui/docs/lobby.md @@ -0,0 +1,113 @@ +# Lobby UI + +The lobby is the first authenticated view; the user lands here after +the email-code login completes (see +[`docs/auth-flow.md`](auth-flow.md)). Phase 8 introduced the live +lobby with five sections, the create-game form, and the TS-side +FlatBuffers integration the rest of the client builds on. This doc +captures the sections, the application / invite lifecycle the user +sees, and the defaults baked into the create-game form. + +## Sections + +The lobby renders one column of sections, top to bottom, with the +common content max-width capped at `32rem` (same convention as the +login page). Cards inside each section take the full available +width. + +| Section | Empty state | Source | Action | +| -------------------- | --------------------- | -------------------------- | --------------------------------------------------------- | +| `create new game` | (always visible) | — | Navigates to `/lobby/create` | +| `my games` | `no games yet` | `lobby.my.games.list` | Click → `/games/:id/map` (placeholder until Phase 10) | +| `pending invitations`| `no invitations` | `lobby.my.invites.list` | Accept (`lobby.invite.redeem`) / Decline (`lobby.invite.decline`) | +| `my applications` | `no applications` | `lobby.my.applications.list` | Status badge (`pending` / `approved` / `rejected`) | +| `public games` | `no public games` | `lobby.public.games.list` | Submit application via inline race-name form (`lobby.application.submit`) | + +The header preserves the device-session-id `` block from the +Phase 7 placeholder (kept as a debug affordance) plus a greeting if +the gateway returns a `display_name` for the caller. + +## Application lifecycle + +`Submit application` on a public-game card toggles an inline race-name +form on the same card (no overlay/modal infrastructure yet — the +in-game shell that introduces overlays lands later). On submit: + +1. The page calls `submitApplication(client, gameId, raceName)` from + `src/api/lobby.ts`. +2. The wrapper builds an `ApplicationSubmitRequest` FlatBuffers + payload, posts it through `GalaxyClient.executeCommand`, decodes + the `ApplicationSubmitResponse`, and returns an + `ApplicationSummary` plain object. +3. The lobby page prepends the new application to the + `my applications` list and collapses the inline form. The page + does not refresh the public-games list — backend semantics are + that the public game still exists and is still in + `enrollment_open`. +4. Status starts as `pending`. When the owner approves, backend + creates a membership and the next refresh of `lobby.my.games.list` + surfaces the game in `my games`. When the owner rejects, the + application stays terminal in `my applications` with status + `rejected`. + +## Invite lifecycle + +A pending invite arrives in `pending invitations` either when the +inviter targets the user by id (`invited_user_id` is set) or when the +user redeems a code-based invite from somewhere outside the lobby. +The user can accept (`lobby.invite.redeem`) or decline +(`lobby.invite.decline`): + +- **Accept** — the invite card disappears, the page refreshes + `my games`, and the freshly-joined game appears there. +- **Decline** — the invite card disappears. No membership is + created. + +## Create-game form + +The form posts `lobby.game.create` through the gateway with +`visibility="private"` hard-coded; the user surface never produces a +public game (FUNCTIONAL.md §3.3). Fields: + +| Field | Visibility | Default | Notes | +| --------------------- | ---------------- | ------- | ------------------------------------------------------ | +| `game_name` | always | `""` | Non-empty client-side check | +| `description` | always | `""` | | +| `turn_schedule` | always | `0 0 * * *` | Plain text input, hint says "five-field cron" | +| `enrollment_ends_at` | always | `""` | ``, RFC 3339 on submit | +| `min_players` | Advanced toggle | `2` | `
` block | +| `max_players` | Advanced toggle | `8` | | +| `start_gap_hours` | Advanced toggle | `24` | | +| `start_gap_players` | Advanced toggle | `2` | | +| `target_engine_version` | Advanced toggle | `v1` | Falls back to `v1` if blank | + +On success the page navigates back to `/lobby` and the new game shows +up in `my games` once the lobby's onMount has had a chance to refresh +the list. + +## Errors + +Lobby errors raised by the gateway carry a canonical code +(`invalid_request`, `subject_not_found`, `forbidden`, `conflict`, +`internal_error`). The `LobbyError` thrown by `lobby.ts` exposes the +code; the page maps it to the matching `lobby.error.` i18n key +and falls back to the gateway-supplied message via +`lobby.error.unknown` for any unknown code. + +## Why FlatBuffers on the TS side + +The gateway encodes lobby payloads through `pkg/transcoder/lobby.go` +into FlatBuffers bytes; the browser must decode them with the same +schema. Phase 8 ships: + +- `flatbuffers` runtime dependency in `ui/frontend/package.json`; +- `make -C ui fbs-ts` driving `flatc --ts` to regenerate the bindings + from `pkg/schema/fbs/*.fbs` into `ui/frontend/src/proto/galaxy/fbs/`; +- a Vitest round-trip suite (`tests/lobby-fbs.test.ts`) that catches + binding drift in CI. + +Phase 7's `user.account.get` decode previously used +`JSON.parse(TextDecoder)`; that path was rewritten in Phase 8 to use +the same generated `AccountResponse` table, so the lobby greeting now +works against a real local stack as well as the mocked Playwright +fixtures. diff --git a/ui/frontend/package.json b/ui/frontend/package.json index 8911c0f..da5401e 100644 --- a/ui/frontend/package.json +++ b/ui/frontend/package.json @@ -12,6 +12,7 @@ "test:e2e": "playwright test" }, "dependencies": { + "flatbuffers": "^25.9.23", "idb": "^8.0.3" }, "devDependencies": { diff --git a/ui/frontend/src/api/galaxy-client.ts b/ui/frontend/src/api/galaxy-client.ts index a56bd3b..9ecd754 100644 --- a/ui/frontend/src/api/galaxy-client.ts +++ b/ui/frontend/src/api/galaxy-client.ts @@ -46,6 +46,11 @@ export interface GalaxyClientOptions { const PROTOCOL_VERSION = "v1"; +export interface ExecuteCommandResult { + resultCode: string; + payloadBytes: Uint8Array; +} + export class GalaxyClient { private readonly core: Core; private readonly edge: EdgeGatewayClient; @@ -72,7 +77,7 @@ export class GalaxyClient { messageType: string, payload: Uint8Array, traceId = "", - ): Promise { + ): Promise { const requestId = this.requestIdFactory(); const timestampMs = this.clock(); const payloadHash = await this.sha256(payload); @@ -103,7 +108,10 @@ export class GalaxyClient { ); this.verifyResponse(response, requestId); - return response.payloadBytes; + return { + resultCode: response.resultCode, + payloadBytes: response.payloadBytes, + }; } private verifyResponse( diff --git a/ui/frontend/src/api/lobby.ts b/ui/frontend/src/api/lobby.ts new file mode 100644 index 0000000..07a85b3 --- /dev/null +++ b/ui/frontend/src/api/lobby.ts @@ -0,0 +1,361 @@ +// Typed wrappers around `GalaxyClient.executeCommand` for the Phase 8 +// lobby command catalog. Each wrapper builds a FlatBuffers request +// payload via the generated TS bindings, calls `executeCommand`, then +// decodes the FlatBuffers response payload. Errors carrying a non-`ok` +// `result_code` are surfaced as a thrown `LobbyError` so callers can +// branch on canonical lobby error codes (`invalid_request`, +// `subject_not_found`, `forbidden`, `conflict`, `internal_error`). + +import { Builder, ByteBuffer } from "flatbuffers"; + +import type { GalaxyClient } from "./galaxy-client"; +import { + ApplicationSubmitRequest, + ApplicationSubmitResponse, + ApplicationSummary as FbsApplicationSummary, + ErrorResponse as FbsErrorResponse, + GameCreateRequest, + GameCreateResponse, + GameSummary as FbsGameSummary, + InviteDeclineRequest, + InviteDeclineResponse, + InviteRedeemRequest, + InviteRedeemResponse, + InviteSummary as FbsInviteSummary, + MyApplicationsListRequest, + MyApplicationsListResponse, + MyGamesListRequest, + MyGamesListResponse, + MyInvitesListRequest, + MyInvitesListResponse, + PublicGamesListRequest, + PublicGamesListResponse, +} from "../proto/galaxy/fbs/lobby"; + +export class LobbyError extends Error { + readonly code: string; + readonly resultCode: string; + + constructor(resultCode: string, code: string, message: string) { + super(message); + this.name = "LobbyError"; + this.resultCode = resultCode; + this.code = code; + } +} + +export interface GameSummary { + gameId: string; + gameName: string; + gameType: string; + status: string; + ownerUserId: string; + minPlayers: number; + maxPlayers: number; + enrollmentEndsAt: Date; + createdAt: Date; + updatedAt: Date; +} + +export interface PublicGamesPage { + items: GameSummary[]; + page: number; + pageSize: number; + total: number; +} + +export interface ApplicationSummary { + applicationId: string; + gameId: string; + applicantUserId: string; + raceName: string; + status: string; + createdAt: Date; + decidedAt: Date | null; +} + +export interface InviteSummary { + inviteId: string; + gameId: string; + inviterUserId: string; + invitedUserId: string; + code: string; + raceName: string; + status: string; + createdAt: Date; + expiresAt: Date; + decidedAt: Date | null; +} + +export interface CreateGameInput { + gameName: string; + description: string; + minPlayers: number; + maxPlayers: number; + startGapHours: number; + startGapPlayers: number; + enrollmentEndsAt: Date; + turnSchedule: string; + targetEngineVersion: string; +} + +const RESULT_CODE_OK = "ok"; + +export async function listMyGames(client: GalaxyClient): Promise { + const builder = new Builder(32); + MyGamesListRequest.startMyGamesListRequest(builder); + builder.finish(MyGamesListRequest.endMyGamesListRequest(builder)); + const payload = await execute(client, "lobby.my.games.list", builder.asUint8Array()); + const response = MyGamesListResponse.getRootAsMyGamesListResponse(new ByteBuffer(payload)); + const out: GameSummary[] = []; + for (let i = 0; i < response.itemsLength(); i++) { + const item = response.items(i); + if (item) { + out.push(decodeGameSummary(item)); + } + } + return out; +} + +export async function listPublicGames( + client: GalaxyClient, + options: { page?: number; pageSize?: number } = {}, +): Promise { + const page = options.page ?? 1; + const pageSize = options.pageSize ?? 50; + const builder = new Builder(32); + PublicGamesListRequest.startPublicGamesListRequest(builder); + PublicGamesListRequest.addPage(builder, page); + PublicGamesListRequest.addPageSize(builder, pageSize); + builder.finish(PublicGamesListRequest.endPublicGamesListRequest(builder)); + const payload = await execute(client, "lobby.public.games.list", builder.asUint8Array()); + const response = PublicGamesListResponse.getRootAsPublicGamesListResponse( + new ByteBuffer(payload), + ); + const out: GameSummary[] = []; + for (let i = 0; i < response.itemsLength(); i++) { + const item = response.items(i); + if (item) { + out.push(decodeGameSummary(item)); + } + } + return { + items: out, + page: response.page(), + pageSize: response.pageSize(), + total: response.total(), + }; +} + +export async function listMyApplications( + client: GalaxyClient, +): Promise { + const builder = new Builder(32); + MyApplicationsListRequest.startMyApplicationsListRequest(builder); + builder.finish(MyApplicationsListRequest.endMyApplicationsListRequest(builder)); + const payload = await execute(client, "lobby.my.applications.list", builder.asUint8Array()); + const response = MyApplicationsListResponse.getRootAsMyApplicationsListResponse( + new ByteBuffer(payload), + ); + const out: ApplicationSummary[] = []; + for (let i = 0; i < response.itemsLength(); i++) { + const item = response.items(i); + if (item) { + out.push(decodeApplicationSummary(item)); + } + } + return out; +} + +export async function listMyInvites(client: GalaxyClient): Promise { + const builder = new Builder(32); + MyInvitesListRequest.startMyInvitesListRequest(builder); + builder.finish(MyInvitesListRequest.endMyInvitesListRequest(builder)); + const payload = await execute(client, "lobby.my.invites.list", builder.asUint8Array()); + const response = MyInvitesListResponse.getRootAsMyInvitesListResponse(new ByteBuffer(payload)); + const out: InviteSummary[] = []; + for (let i = 0; i < response.itemsLength(); i++) { + const item = response.items(i); + if (item) { + out.push(decodeInviteSummary(item)); + } + } + return out; +} + +export async function createGame( + client: GalaxyClient, + input: CreateGameInput, +): Promise { + const builder = new Builder(256); + const gameNameOff = builder.createString(input.gameName); + const descriptionOff = builder.createString(input.description); + const turnScheduleOff = builder.createString(input.turnSchedule); + const targetEngineVersionOff = builder.createString(input.targetEngineVersion); + GameCreateRequest.startGameCreateRequest(builder); + GameCreateRequest.addGameName(builder, gameNameOff); + GameCreateRequest.addDescription(builder, descriptionOff); + GameCreateRequest.addMinPlayers(builder, input.minPlayers); + GameCreateRequest.addMaxPlayers(builder, input.maxPlayers); + GameCreateRequest.addStartGapHours(builder, input.startGapHours); + GameCreateRequest.addStartGapPlayers(builder, input.startGapPlayers); + GameCreateRequest.addEnrollmentEndsAtMs(builder, BigInt(input.enrollmentEndsAt.getTime())); + GameCreateRequest.addTurnSchedule(builder, turnScheduleOff); + GameCreateRequest.addTargetEngineVersion(builder, targetEngineVersionOff); + builder.finish(GameCreateRequest.endGameCreateRequest(builder)); + const payload = await execute(client, "lobby.game.create", builder.asUint8Array()); + const response = GameCreateResponse.getRootAsGameCreateResponse(new ByteBuffer(payload)); + const game = response.game(); + if (game === null) { + throw new LobbyError("internal_error", "internal_error", "game missing in response"); + } + return decodeGameSummary(game); +} + +export async function submitApplication( + client: GalaxyClient, + gameId: string, + raceName: string, +): Promise { + const builder = new Builder(128); + const gameIdOff = builder.createString(gameId); + const raceNameOff = builder.createString(raceName); + ApplicationSubmitRequest.startApplicationSubmitRequest(builder); + ApplicationSubmitRequest.addGameId(builder, gameIdOff); + ApplicationSubmitRequest.addRaceName(builder, raceNameOff); + builder.finish(ApplicationSubmitRequest.endApplicationSubmitRequest(builder)); + const payload = await execute(client, "lobby.application.submit", builder.asUint8Array()); + const response = ApplicationSubmitResponse.getRootAsApplicationSubmitResponse( + new ByteBuffer(payload), + ); + const application = response.application(); + if (application === null) { + throw new LobbyError("internal_error", "internal_error", "application missing in response"); + } + return decodeApplicationSummary(application); +} + +export async function redeemInvite( + client: GalaxyClient, + gameId: string, + inviteId: string, +): Promise { + const builder = new Builder(128); + const gameIdOff = builder.createString(gameId); + const inviteIdOff = builder.createString(inviteId); + InviteRedeemRequest.startInviteRedeemRequest(builder); + InviteRedeemRequest.addGameId(builder, gameIdOff); + InviteRedeemRequest.addInviteId(builder, inviteIdOff); + builder.finish(InviteRedeemRequest.endInviteRedeemRequest(builder)); + const payload = await execute(client, "lobby.invite.redeem", builder.asUint8Array()); + const response = InviteRedeemResponse.getRootAsInviteRedeemResponse(new ByteBuffer(payload)); + const invite = response.invite(); + if (invite === null) { + throw new LobbyError("internal_error", "internal_error", "invite missing in response"); + } + return decodeInviteSummary(invite); +} + +export async function declineInvite( + client: GalaxyClient, + gameId: string, + inviteId: string, +): Promise { + const builder = new Builder(128); + const gameIdOff = builder.createString(gameId); + const inviteIdOff = builder.createString(inviteId); + InviteDeclineRequest.startInviteDeclineRequest(builder); + InviteDeclineRequest.addGameId(builder, gameIdOff); + InviteDeclineRequest.addInviteId(builder, inviteIdOff); + builder.finish(InviteDeclineRequest.endInviteDeclineRequest(builder)); + const payload = await execute(client, "lobby.invite.decline", builder.asUint8Array()); + const response = InviteDeclineResponse.getRootAsInviteDeclineResponse( + new ByteBuffer(payload), + ); + const invite = response.invite(); + if (invite === null) { + throw new LobbyError("internal_error", "internal_error", "invite missing in response"); + } + return decodeInviteSummary(invite); +} + +async function execute( + client: GalaxyClient, + messageType: string, + payloadBytes: Uint8Array, +): Promise { + const result = await client.executeCommand(messageType, payloadBytes); + if (result.resultCode !== RESULT_CODE_OK) { + throw decodeLobbyError(result.resultCode, result.payloadBytes); + } + return result.payloadBytes; +} + +function decodeLobbyError(resultCode: string, payload: Uint8Array): LobbyError { + let code = resultCode; + let message = resultCode; + try { + const errorResponse = FbsErrorResponse.getRootAsErrorResponse(new ByteBuffer(payload)); + const body = errorResponse.error(); + if (body) { + code = body.code() ?? resultCode; + message = body.message() ?? resultCode; + } + } catch (_err) { + // fall through to use raw resultCode as both code and message + } + return new LobbyError(resultCode, code, message); +} + +function decodeGameSummary(summary: FbsGameSummary): GameSummary { + return { + gameId: summary.gameId() ?? "", + gameName: summary.gameName() ?? "", + gameType: summary.gameType() ?? "", + status: summary.status() ?? "", + ownerUserId: summary.ownerUserId() ?? "", + minPlayers: summary.minPlayers(), + maxPlayers: summary.maxPlayers(), + enrollmentEndsAt: dateFromMs(summary.enrollmentEndsAtMs()), + createdAt: dateFromMs(summary.createdAtMs()), + updatedAt: dateFromMs(summary.updatedAtMs()), + }; +} + +function decodeApplicationSummary(app: FbsApplicationSummary): ApplicationSummary { + return { + applicationId: app.applicationId() ?? "", + gameId: app.gameId() ?? "", + applicantUserId: app.applicantUserId() ?? "", + raceName: app.raceName() ?? "", + status: app.status() ?? "", + createdAt: dateFromMs(app.createdAtMs()), + decidedAt: optionalDateFromMs(app.decidedAtMs()), + }; +} + +function decodeInviteSummary(invite: FbsInviteSummary): InviteSummary { + return { + inviteId: invite.inviteId() ?? "", + gameId: invite.gameId() ?? "", + inviterUserId: invite.inviterUserId() ?? "", + invitedUserId: invite.invitedUserId() ?? "", + code: invite.code() ?? "", + raceName: invite.raceName() ?? "", + status: invite.status() ?? "", + createdAt: dateFromMs(invite.createdAtMs()), + expiresAt: dateFromMs(invite.expiresAtMs()), + decidedAt: optionalDateFromMs(invite.decidedAtMs()), + }; +} + +function dateFromMs(ms: bigint): Date { + return new Date(Number(ms)); +} + +function optionalDateFromMs(ms: bigint): Date | null { + if (ms === 0n) { + return null; + } + return new Date(Number(ms)); +} diff --git a/ui/frontend/src/lib/i18n/locales/en.ts b/ui/frontend/src/lib/i18n/locales/en.ts index 4a6a53e..e68cb4b 100644 --- a/ui/frontend/src/lib/i18n/locales/en.ts +++ b/ui/frontend/src/lib/i18n/locales/en.ts @@ -35,6 +35,53 @@ const en = { "lobby.greeting": "hello, {name}!", "lobby.account_loading": "loading account…", "lobby.logout": "logout", + "lobby.section.my_games": "my games", + "lobby.section.invitations": "pending invitations", + "lobby.section.applications": "my applications", + "lobby.section.public_games": "public games", + "lobby.section.create": "create a game", + "lobby.create_button": "create new game", + "lobby.my_games.empty": "no games yet", + "lobby.invitations.empty": "no invitations", + "lobby.applications.empty": "no applications", + "lobby.public_games.empty": "no public games", + "lobby.invitation.accept": "accept", + "lobby.invitation.decline": "decline", + "lobby.application.submit": "submit application", + "lobby.application.submit_for": "join {name}", + "lobby.application.race_name_label": "race name", + "lobby.application.race_name_required": "race name must not be empty", + "lobby.application.cancel": "cancel", + "lobby.application.submitted": "application submitted, awaiting approval", + "lobby.application.status.pending": "pending", + "lobby.application.status.approved": "approved", + "lobby.application.status.rejected": "rejected", + "lobby.application.status.unknown": "{status}", + "lobby.list_loading": "loading…", + "lobby.create.title": "create new game", + "lobby.create.game_name_label": "game name", + "lobby.create.description_label": "description", + "lobby.create.turn_schedule_label": "turn schedule", + "lobby.create.turn_schedule_hint": "five-field cron, e.g. 0 0 * * *", + "lobby.create.enrollment_ends_at_label": "enrollment ends at", + "lobby.create.advanced": "advanced", + "lobby.create.min_players_label": "min players", + "lobby.create.max_players_label": "max players", + "lobby.create.start_gap_hours_label": "start gap (hours)", + "lobby.create.start_gap_players_label": "start gap (players)", + "lobby.create.target_engine_version_label": "target engine version", + "lobby.create.submit": "create", + "lobby.create.submitting": "creating…", + "lobby.create.cancel": "cancel", + "lobby.create.game_name_required": "game name must not be empty", + "lobby.create.turn_schedule_required": "turn schedule must not be empty", + "lobby.create.enrollment_ends_at_required": "enrollment end time must be set", + "lobby.error.invalid_request": "request is invalid", + "lobby.error.subject_not_found": "not found", + "lobby.error.forbidden": "operation is forbidden", + "lobby.error.conflict": "request conflicts with current state", + "lobby.error.internal_error": "internal server error", + "lobby.error.unknown": "{message}", } as const; export default en; diff --git a/ui/frontend/src/lib/i18n/locales/ru.ts b/ui/frontend/src/lib/i18n/locales/ru.ts index e0afebd..c67b181 100644 --- a/ui/frontend/src/lib/i18n/locales/ru.ts +++ b/ui/frontend/src/lib/i18n/locales/ru.ts @@ -36,6 +36,53 @@ const ru: Record = { "lobby.greeting": "здравствуйте, {name}!", "lobby.account_loading": "загрузка профиля…", "lobby.logout": "выйти", + "lobby.section.my_games": "мои игры", + "lobby.section.invitations": "ожидающие приглашения", + "lobby.section.applications": "мои заявки", + "lobby.section.public_games": "публичные игры", + "lobby.section.create": "создать игру", + "lobby.create_button": "создать новую игру", + "lobby.my_games.empty": "пока нет игр", + "lobby.invitations.empty": "приглашений нет", + "lobby.applications.empty": "заявок нет", + "lobby.public_games.empty": "публичных игр нет", + "lobby.invitation.accept": "принять", + "lobby.invitation.decline": "отклонить", + "lobby.application.submit": "подать заявку", + "lobby.application.submit_for": "подать заявку в {name}", + "lobby.application.race_name_label": "название расы", + "lobby.application.race_name_required": "название расы не должно быть пустым", + "lobby.application.cancel": "отмена", + "lobby.application.submitted": "заявка отправлена, ожидает одобрения", + "lobby.application.status.pending": "ожидает", + "lobby.application.status.approved": "одобрена", + "lobby.application.status.rejected": "отклонена", + "lobby.application.status.unknown": "{status}", + "lobby.list_loading": "загрузка…", + "lobby.create.title": "создание новой игры", + "lobby.create.game_name_label": "название игры", + "lobby.create.description_label": "описание", + "lobby.create.turn_schedule_label": "расписание ходов", + "lobby.create.turn_schedule_hint": "cron из пяти полей, например 0 0 * * *", + "lobby.create.enrollment_ends_at_label": "окончание набора", + "lobby.create.advanced": "дополнительно", + "lobby.create.min_players_label": "мин. игроков", + "lobby.create.max_players_label": "макс. игроков", + "lobby.create.start_gap_hours_label": "интервал старта (часы)", + "lobby.create.start_gap_players_label": "интервал старта (игроки)", + "lobby.create.target_engine_version_label": "версия движка", + "lobby.create.submit": "создать", + "lobby.create.submitting": "создаём…", + "lobby.create.cancel": "отмена", + "lobby.create.game_name_required": "название игры не должно быть пустым", + "lobby.create.turn_schedule_required": "расписание ходов не должно быть пустым", + "lobby.create.enrollment_ends_at_required": "время окончания набора обязательно", + "lobby.error.invalid_request": "запрос некорректен", + "lobby.error.subject_not_found": "объект не найден", + "lobby.error.forbidden": "операция запрещена", + "lobby.error.conflict": "запрос конфликтует с текущим состоянием", + "lobby.error.internal_error": "внутренняя ошибка сервера", + "lobby.error.unknown": "{message}", }; export default ru; diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby.ts b/ui/frontend/src/proto/galaxy/fbs/lobby.ts new file mode 100644 index 0000000..65f3901 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby.ts @@ -0,0 +1,27 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +export { ApplicationSubmitRequest, ApplicationSubmitRequestT } from './lobby/application-submit-request.js'; +export { ApplicationSubmitResponse, ApplicationSubmitResponseT } from './lobby/application-submit-response.js'; +export { ApplicationSummary, ApplicationSummaryT } from './lobby/application-summary.js'; +export { ErrorBody, ErrorBodyT } from './lobby/error-body.js'; +export { ErrorResponse, ErrorResponseT } from './lobby/error-response.js'; +export { GameCreateRequest, GameCreateRequestT } from './lobby/game-create-request.js'; +export { GameCreateResponse, GameCreateResponseT } from './lobby/game-create-response.js'; +export { GameSummary, GameSummaryT } from './lobby/game-summary.js'; +export { InviteDeclineRequest, InviteDeclineRequestT } from './lobby/invite-decline-request.js'; +export { InviteDeclineResponse, InviteDeclineResponseT } from './lobby/invite-decline-response.js'; +export { InviteRedeemRequest, InviteRedeemRequestT } from './lobby/invite-redeem-request.js'; +export { InviteRedeemResponse, InviteRedeemResponseT } from './lobby/invite-redeem-response.js'; +export { InviteSummary, InviteSummaryT } from './lobby/invite-summary.js'; +export { MyApplicationsListRequest, MyApplicationsListRequestT } from './lobby/my-applications-list-request.js'; +export { MyApplicationsListResponse, MyApplicationsListResponseT } from './lobby/my-applications-list-response.js'; +export { MyGamesListRequest, MyGamesListRequestT } from './lobby/my-games-list-request.js'; +export { MyGamesListResponse, MyGamesListResponseT } from './lobby/my-games-list-response.js'; +export { MyInvitesListRequest, MyInvitesListRequestT } from './lobby/my-invites-list-request.js'; +export { MyInvitesListResponse, MyInvitesListResponseT } from './lobby/my-invites-list-response.js'; +export { OpenEnrollmentRequest, OpenEnrollmentRequestT } from './lobby/open-enrollment-request.js'; +export { OpenEnrollmentResponse, OpenEnrollmentResponseT } from './lobby/open-enrollment-response.js'; +export { PublicGamesListRequest, PublicGamesListRequestT } from './lobby/public-games-list-request.js'; +export { PublicGamesListResponse, PublicGamesListResponseT } from './lobby/public-games-list-response.js'; diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-request.ts new file mode 100644 index 0000000..7212773 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-request.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class ApplicationSubmitRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ApplicationSubmitRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsApplicationSubmitRequest(bb:flatbuffers.ByteBuffer, obj?:ApplicationSubmitRequest):ApplicationSubmitRequest { + return (obj || new ApplicationSubmitRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsApplicationSubmitRequest(bb:flatbuffers.ByteBuffer, obj?:ApplicationSubmitRequest):ApplicationSubmitRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ApplicationSubmitRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +raceName():string|null +raceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +raceName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startApplicationSubmitRequest(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameIdOffset, 0); +} + +static addRaceName(builder:flatbuffers.Builder, raceNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, raceNameOffset, 0); +} + +static endApplicationSubmitRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createApplicationSubmitRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, raceNameOffset:flatbuffers.Offset):flatbuffers.Offset { + ApplicationSubmitRequest.startApplicationSubmitRequest(builder); + ApplicationSubmitRequest.addGameId(builder, gameIdOffset); + ApplicationSubmitRequest.addRaceName(builder, raceNameOffset); + return ApplicationSubmitRequest.endApplicationSubmitRequest(builder); +} + +unpack(): ApplicationSubmitRequestT { + return new ApplicationSubmitRequestT( + this.gameId(), + this.raceName() + ); +} + + +unpackTo(_o: ApplicationSubmitRequestT): void { + _o.gameId = this.gameId(); + _o.raceName = this.raceName(); +} +} + +export class ApplicationSubmitRequestT implements flatbuffers.IGeneratedObject { +constructor( + public gameId: string|Uint8Array|null = null, + public raceName: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const raceName = (this.raceName !== null ? builder.createString(this.raceName!) : 0); + + return ApplicationSubmitRequest.createApplicationSubmitRequest(builder, + gameId, + raceName + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-response.ts new file mode 100644 index 0000000..c26463d --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/application-submit-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ApplicationSummary, ApplicationSummaryT } from './application-summary.js'; + + +export class ApplicationSubmitResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ApplicationSubmitResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsApplicationSubmitResponse(bb:flatbuffers.ByteBuffer, obj?:ApplicationSubmitResponse):ApplicationSubmitResponse { + return (obj || new ApplicationSubmitResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsApplicationSubmitResponse(bb:flatbuffers.ByteBuffer, obj?:ApplicationSubmitResponse):ApplicationSubmitResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ApplicationSubmitResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +application(obj?:ApplicationSummary):ApplicationSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new ApplicationSummary()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startApplicationSubmitResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addApplication(builder:flatbuffers.Builder, applicationOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, applicationOffset, 0); +} + +static endApplicationSubmitResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createApplicationSubmitResponse(builder:flatbuffers.Builder, applicationOffset:flatbuffers.Offset):flatbuffers.Offset { + ApplicationSubmitResponse.startApplicationSubmitResponse(builder); + ApplicationSubmitResponse.addApplication(builder, applicationOffset); + return ApplicationSubmitResponse.endApplicationSubmitResponse(builder); +} + +unpack(): ApplicationSubmitResponseT { + return new ApplicationSubmitResponseT( + (this.application() !== null ? this.application()!.unpack() : null) + ); +} + + +unpackTo(_o: ApplicationSubmitResponseT): void { + _o.application = (this.application() !== null ? this.application()!.unpack() : null); +} +} + +export class ApplicationSubmitResponseT implements flatbuffers.IGeneratedObject { +constructor( + public application: ApplicationSummaryT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const application = (this.application !== null ? this.application!.pack(builder) : 0); + + return ApplicationSubmitResponse.createApplicationSubmitResponse(builder, + application + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/application-summary.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/application-summary.ts new file mode 100644 index 0000000..81c1490 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/application-summary.ts @@ -0,0 +1,174 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class ApplicationSummary implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ApplicationSummary { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsApplicationSummary(bb:flatbuffers.ByteBuffer, obj?:ApplicationSummary):ApplicationSummary { + return (obj || new ApplicationSummary()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsApplicationSummary(bb:flatbuffers.ByteBuffer, obj?:ApplicationSummary):ApplicationSummary { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ApplicationSummary()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +applicationId():string|null +applicationId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +applicationId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +applicantUserId():string|null +applicantUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +applicantUserId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +raceName():string|null +raceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +raceName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +status():string|null +status(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +status(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +createdAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +decidedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startApplicationSummary(builder:flatbuffers.Builder) { + builder.startObject(7); +} + +static addApplicationId(builder:flatbuffers.Builder, applicationIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, applicationIdOffset, 0); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, gameIdOffset, 0); +} + +static addApplicantUserId(builder:flatbuffers.Builder, applicantUserIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, applicantUserIdOffset, 0); +} + +static addRaceName(builder:flatbuffers.Builder, raceNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, raceNameOffset, 0); +} + +static addStatus(builder:flatbuffers.Builder, statusOffset:flatbuffers.Offset) { + builder.addFieldOffset(4, statusOffset, 0); +} + +static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) { + builder.addFieldInt64(5, createdAtMs, BigInt('0')); +} + +static addDecidedAtMs(builder:flatbuffers.Builder, decidedAtMs:bigint) { + builder.addFieldInt64(6, decidedAtMs, BigInt('0')); +} + +static endApplicationSummary(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createApplicationSummary(builder:flatbuffers.Builder, applicationIdOffset:flatbuffers.Offset, gameIdOffset:flatbuffers.Offset, applicantUserIdOffset:flatbuffers.Offset, raceNameOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset, createdAtMs:bigint, decidedAtMs:bigint):flatbuffers.Offset { + ApplicationSummary.startApplicationSummary(builder); + ApplicationSummary.addApplicationId(builder, applicationIdOffset); + ApplicationSummary.addGameId(builder, gameIdOffset); + ApplicationSummary.addApplicantUserId(builder, applicantUserIdOffset); + ApplicationSummary.addRaceName(builder, raceNameOffset); + ApplicationSummary.addStatus(builder, statusOffset); + ApplicationSummary.addCreatedAtMs(builder, createdAtMs); + ApplicationSummary.addDecidedAtMs(builder, decidedAtMs); + return ApplicationSummary.endApplicationSummary(builder); +} + +unpack(): ApplicationSummaryT { + return new ApplicationSummaryT( + this.applicationId(), + this.gameId(), + this.applicantUserId(), + this.raceName(), + this.status(), + this.createdAtMs(), + this.decidedAtMs() + ); +} + + +unpackTo(_o: ApplicationSummaryT): void { + _o.applicationId = this.applicationId(); + _o.gameId = this.gameId(); + _o.applicantUserId = this.applicantUserId(); + _o.raceName = this.raceName(); + _o.status = this.status(); + _o.createdAtMs = this.createdAtMs(); + _o.decidedAtMs = this.decidedAtMs(); +} +} + +export class ApplicationSummaryT implements flatbuffers.IGeneratedObject { +constructor( + public applicationId: string|Uint8Array|null = null, + public gameId: string|Uint8Array|null = null, + public applicantUserId: string|Uint8Array|null = null, + public raceName: string|Uint8Array|null = null, + public status: string|Uint8Array|null = null, + public createdAtMs: bigint = BigInt('0'), + public decidedAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const applicationId = (this.applicationId !== null ? builder.createString(this.applicationId!) : 0); + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const applicantUserId = (this.applicantUserId !== null ? builder.createString(this.applicantUserId!) : 0); + const raceName = (this.raceName !== null ? builder.createString(this.raceName!) : 0); + const status = (this.status !== null ? builder.createString(this.status!) : 0); + + return ApplicationSummary.createApplicationSummary(builder, + applicationId, + gameId, + applicantUserId, + raceName, + status, + this.createdAtMs, + this.decidedAtMs + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/error-body.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/error-body.ts new file mode 100644 index 0000000..7cab4d9 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/error-body.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class ErrorBody implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ErrorBody { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsErrorBody(bb:flatbuffers.ByteBuffer, obj?:ErrorBody):ErrorBody { + return (obj || new ErrorBody()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsErrorBody(bb:flatbuffers.ByteBuffer, obj?:ErrorBody):ErrorBody { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ErrorBody()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +code():string|null +code(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +code(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +message():string|null +message(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +message(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startErrorBody(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addCode(builder:flatbuffers.Builder, codeOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, codeOffset, 0); +} + +static addMessage(builder:flatbuffers.Builder, messageOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, messageOffset, 0); +} + +static endErrorBody(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createErrorBody(builder:flatbuffers.Builder, codeOffset:flatbuffers.Offset, messageOffset:flatbuffers.Offset):flatbuffers.Offset { + ErrorBody.startErrorBody(builder); + ErrorBody.addCode(builder, codeOffset); + ErrorBody.addMessage(builder, messageOffset); + return ErrorBody.endErrorBody(builder); +} + +unpack(): ErrorBodyT { + return new ErrorBodyT( + this.code(), + this.message() + ); +} + + +unpackTo(_o: ErrorBodyT): void { + _o.code = this.code(); + _o.message = this.message(); +} +} + +export class ErrorBodyT implements flatbuffers.IGeneratedObject { +constructor( + public code: string|Uint8Array|null = null, + public message: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const code = (this.code !== null ? builder.createString(this.code!) : 0); + const message = (this.message !== null ? builder.createString(this.message!) : 0); + + return ErrorBody.createErrorBody(builder, + code, + message + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/error-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/error-response.ts new file mode 100644 index 0000000..5e99abb --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/error-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ErrorBody, ErrorBodyT } from './error-body.js'; + + +export class ErrorResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ErrorResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsErrorResponse(bb:flatbuffers.ByteBuffer, obj?:ErrorResponse):ErrorResponse { + return (obj || new ErrorResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsErrorResponse(bb:flatbuffers.ByteBuffer, obj?:ErrorResponse):ErrorResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ErrorResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +error(obj?:ErrorBody):ErrorBody|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new ErrorBody()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startErrorResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addError(builder:flatbuffers.Builder, errorOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, errorOffset, 0); +} + +static endErrorResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createErrorResponse(builder:flatbuffers.Builder, errorOffset:flatbuffers.Offset):flatbuffers.Offset { + ErrorResponse.startErrorResponse(builder); + ErrorResponse.addError(builder, errorOffset); + return ErrorResponse.endErrorResponse(builder); +} + +unpack(): ErrorResponseT { + return new ErrorResponseT( + (this.error() !== null ? this.error()!.unpack() : null) + ); +} + + +unpackTo(_o: ErrorResponseT): void { + _o.error = (this.error() !== null ? this.error()!.unpack() : null); +} +} + +export class ErrorResponseT implements flatbuffers.IGeneratedObject { +constructor( + public error: ErrorBodyT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const error = (this.error !== null ? this.error!.pack(builder) : 0); + + return ErrorResponse.createErrorResponse(builder, + error + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/game-create-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/game-create-request.ts new file mode 100644 index 0000000..8f5f8ac --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/game-create-request.ts @@ -0,0 +1,199 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class GameCreateRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):GameCreateRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsGameCreateRequest(bb:flatbuffers.ByteBuffer, obj?:GameCreateRequest):GameCreateRequest { + return (obj || new GameCreateRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsGameCreateRequest(bb:flatbuffers.ByteBuffer, obj?:GameCreateRequest):GameCreateRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new GameCreateRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameName():string|null +gameName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +description():string|null +description(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +description(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +minPlayers():number { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +maxPlayers():number { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +startGapHours():number { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +startGapPlayers():number { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +enrollmentEndsAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +turnSchedule():string|null +turnSchedule(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +turnSchedule(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 18); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +targetEngineVersion():string|null +targetEngineVersion(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +targetEngineVersion(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 20); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startGameCreateRequest(builder:flatbuffers.Builder) { + builder.startObject(9); +} + +static addGameName(builder:flatbuffers.Builder, gameNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameNameOffset, 0); +} + +static addDescription(builder:flatbuffers.Builder, descriptionOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, descriptionOffset, 0); +} + +static addMinPlayers(builder:flatbuffers.Builder, minPlayers:number) { + builder.addFieldInt32(2, minPlayers, 0); +} + +static addMaxPlayers(builder:flatbuffers.Builder, maxPlayers:number) { + builder.addFieldInt32(3, maxPlayers, 0); +} + +static addStartGapHours(builder:flatbuffers.Builder, startGapHours:number) { + builder.addFieldInt32(4, startGapHours, 0); +} + +static addStartGapPlayers(builder:flatbuffers.Builder, startGapPlayers:number) { + builder.addFieldInt32(5, startGapPlayers, 0); +} + +static addEnrollmentEndsAtMs(builder:flatbuffers.Builder, enrollmentEndsAtMs:bigint) { + builder.addFieldInt64(6, enrollmentEndsAtMs, BigInt('0')); +} + +static addTurnSchedule(builder:flatbuffers.Builder, turnScheduleOffset:flatbuffers.Offset) { + builder.addFieldOffset(7, turnScheduleOffset, 0); +} + +static addTargetEngineVersion(builder:flatbuffers.Builder, targetEngineVersionOffset:flatbuffers.Offset) { + builder.addFieldOffset(8, targetEngineVersionOffset, 0); +} + +static endGameCreateRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createGameCreateRequest(builder:flatbuffers.Builder, gameNameOffset:flatbuffers.Offset, descriptionOffset:flatbuffers.Offset, minPlayers:number, maxPlayers:number, startGapHours:number, startGapPlayers:number, enrollmentEndsAtMs:bigint, turnScheduleOffset:flatbuffers.Offset, targetEngineVersionOffset:flatbuffers.Offset):flatbuffers.Offset { + GameCreateRequest.startGameCreateRequest(builder); + GameCreateRequest.addGameName(builder, gameNameOffset); + GameCreateRequest.addDescription(builder, descriptionOffset); + GameCreateRequest.addMinPlayers(builder, minPlayers); + GameCreateRequest.addMaxPlayers(builder, maxPlayers); + GameCreateRequest.addStartGapHours(builder, startGapHours); + GameCreateRequest.addStartGapPlayers(builder, startGapPlayers); + GameCreateRequest.addEnrollmentEndsAtMs(builder, enrollmentEndsAtMs); + GameCreateRequest.addTurnSchedule(builder, turnScheduleOffset); + GameCreateRequest.addTargetEngineVersion(builder, targetEngineVersionOffset); + return GameCreateRequest.endGameCreateRequest(builder); +} + +unpack(): GameCreateRequestT { + return new GameCreateRequestT( + this.gameName(), + this.description(), + this.minPlayers(), + this.maxPlayers(), + this.startGapHours(), + this.startGapPlayers(), + this.enrollmentEndsAtMs(), + this.turnSchedule(), + this.targetEngineVersion() + ); +} + + +unpackTo(_o: GameCreateRequestT): void { + _o.gameName = this.gameName(); + _o.description = this.description(); + _o.minPlayers = this.minPlayers(); + _o.maxPlayers = this.maxPlayers(); + _o.startGapHours = this.startGapHours(); + _o.startGapPlayers = this.startGapPlayers(); + _o.enrollmentEndsAtMs = this.enrollmentEndsAtMs(); + _o.turnSchedule = this.turnSchedule(); + _o.targetEngineVersion = this.targetEngineVersion(); +} +} + +export class GameCreateRequestT implements flatbuffers.IGeneratedObject { +constructor( + public gameName: string|Uint8Array|null = null, + public description: string|Uint8Array|null = null, + public minPlayers: number = 0, + public maxPlayers: number = 0, + public startGapHours: number = 0, + public startGapPlayers: number = 0, + public enrollmentEndsAtMs: bigint = BigInt('0'), + public turnSchedule: string|Uint8Array|null = null, + public targetEngineVersion: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameName = (this.gameName !== null ? builder.createString(this.gameName!) : 0); + const description = (this.description !== null ? builder.createString(this.description!) : 0); + const turnSchedule = (this.turnSchedule !== null ? builder.createString(this.turnSchedule!) : 0); + const targetEngineVersion = (this.targetEngineVersion !== null ? builder.createString(this.targetEngineVersion!) : 0); + + return GameCreateRequest.createGameCreateRequest(builder, + gameName, + description, + this.minPlayers, + this.maxPlayers, + this.startGapHours, + this.startGapPlayers, + this.enrollmentEndsAtMs, + turnSchedule, + targetEngineVersion + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/game-create-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/game-create-response.ts new file mode 100644 index 0000000..5a4b951 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/game-create-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { GameSummary, GameSummaryT } from './game-summary.js'; + + +export class GameCreateResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):GameCreateResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsGameCreateResponse(bb:flatbuffers.ByteBuffer, obj?:GameCreateResponse):GameCreateResponse { + return (obj || new GameCreateResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsGameCreateResponse(bb:flatbuffers.ByteBuffer, obj?:GameCreateResponse):GameCreateResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new GameCreateResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +game(obj?:GameSummary):GameSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new GameSummary()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startGameCreateResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addGame(builder:flatbuffers.Builder, gameOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameOffset, 0); +} + +static endGameCreateResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createGameCreateResponse(builder:flatbuffers.Builder, gameOffset:flatbuffers.Offset):flatbuffers.Offset { + GameCreateResponse.startGameCreateResponse(builder); + GameCreateResponse.addGame(builder, gameOffset); + return GameCreateResponse.endGameCreateResponse(builder); +} + +unpack(): GameCreateResponseT { + return new GameCreateResponseT( + (this.game() !== null ? this.game()!.unpack() : null) + ); +} + + +unpackTo(_o: GameCreateResponseT): void { + _o.game = (this.game() !== null ? this.game()!.unpack() : null); +} +} + +export class GameCreateResponseT implements flatbuffers.IGeneratedObject { +constructor( + public game: GameSummaryT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const game = (this.game !== null ? this.game!.pack(builder) : 0); + + return GameCreateResponse.createGameCreateResponse(builder, + game + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/game-summary.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/game-summary.ts new file mode 100644 index 0000000..3fa482a --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/game-summary.ts @@ -0,0 +1,216 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class GameSummary implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):GameSummary { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsGameSummary(bb:flatbuffers.ByteBuffer, obj?:GameSummary):GameSummary { + return (obj || new GameSummary()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsGameSummary(bb:flatbuffers.ByteBuffer, obj?:GameSummary):GameSummary { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new GameSummary()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +gameName():string|null +gameName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +gameType():string|null +gameType(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameType(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +status():string|null +status(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +status(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +ownerUserId():string|null +ownerUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +ownerUserId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +minPlayers():number { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +maxPlayers():number { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +enrollmentEndsAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 18); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +createdAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 20); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +updatedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 22); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startGameSummary(builder:flatbuffers.Builder) { + builder.startObject(10); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameIdOffset, 0); +} + +static addGameName(builder:flatbuffers.Builder, gameNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, gameNameOffset, 0); +} + +static addGameType(builder:flatbuffers.Builder, gameTypeOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, gameTypeOffset, 0); +} + +static addStatus(builder:flatbuffers.Builder, statusOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, statusOffset, 0); +} + +static addOwnerUserId(builder:flatbuffers.Builder, ownerUserIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(4, ownerUserIdOffset, 0); +} + +static addMinPlayers(builder:flatbuffers.Builder, minPlayers:number) { + builder.addFieldInt32(5, minPlayers, 0); +} + +static addMaxPlayers(builder:flatbuffers.Builder, maxPlayers:number) { + builder.addFieldInt32(6, maxPlayers, 0); +} + +static addEnrollmentEndsAtMs(builder:flatbuffers.Builder, enrollmentEndsAtMs:bigint) { + builder.addFieldInt64(7, enrollmentEndsAtMs, BigInt('0')); +} + +static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) { + builder.addFieldInt64(8, createdAtMs, BigInt('0')); +} + +static addUpdatedAtMs(builder:flatbuffers.Builder, updatedAtMs:bigint) { + builder.addFieldInt64(9, updatedAtMs, BigInt('0')); +} + +static endGameSummary(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createGameSummary(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, gameNameOffset:flatbuffers.Offset, gameTypeOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset, ownerUserIdOffset:flatbuffers.Offset, minPlayers:number, maxPlayers:number, enrollmentEndsAtMs:bigint, createdAtMs:bigint, updatedAtMs:bigint):flatbuffers.Offset { + GameSummary.startGameSummary(builder); + GameSummary.addGameId(builder, gameIdOffset); + GameSummary.addGameName(builder, gameNameOffset); + GameSummary.addGameType(builder, gameTypeOffset); + GameSummary.addStatus(builder, statusOffset); + GameSummary.addOwnerUserId(builder, ownerUserIdOffset); + GameSummary.addMinPlayers(builder, minPlayers); + GameSummary.addMaxPlayers(builder, maxPlayers); + GameSummary.addEnrollmentEndsAtMs(builder, enrollmentEndsAtMs); + GameSummary.addCreatedAtMs(builder, createdAtMs); + GameSummary.addUpdatedAtMs(builder, updatedAtMs); + return GameSummary.endGameSummary(builder); +} + +unpack(): GameSummaryT { + return new GameSummaryT( + this.gameId(), + this.gameName(), + this.gameType(), + this.status(), + this.ownerUserId(), + this.minPlayers(), + this.maxPlayers(), + this.enrollmentEndsAtMs(), + this.createdAtMs(), + this.updatedAtMs() + ); +} + + +unpackTo(_o: GameSummaryT): void { + _o.gameId = this.gameId(); + _o.gameName = this.gameName(); + _o.gameType = this.gameType(); + _o.status = this.status(); + _o.ownerUserId = this.ownerUserId(); + _o.minPlayers = this.minPlayers(); + _o.maxPlayers = this.maxPlayers(); + _o.enrollmentEndsAtMs = this.enrollmentEndsAtMs(); + _o.createdAtMs = this.createdAtMs(); + _o.updatedAtMs = this.updatedAtMs(); +} +} + +export class GameSummaryT implements flatbuffers.IGeneratedObject { +constructor( + public gameId: string|Uint8Array|null = null, + public gameName: string|Uint8Array|null = null, + public gameType: string|Uint8Array|null = null, + public status: string|Uint8Array|null = null, + public ownerUserId: string|Uint8Array|null = null, + public minPlayers: number = 0, + public maxPlayers: number = 0, + public enrollmentEndsAtMs: bigint = BigInt('0'), + public createdAtMs: bigint = BigInt('0'), + public updatedAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const gameName = (this.gameName !== null ? builder.createString(this.gameName!) : 0); + const gameType = (this.gameType !== null ? builder.createString(this.gameType!) : 0); + const status = (this.status !== null ? builder.createString(this.status!) : 0); + const ownerUserId = (this.ownerUserId !== null ? builder.createString(this.ownerUserId!) : 0); + + return GameSummary.createGameSummary(builder, + gameId, + gameName, + gameType, + status, + ownerUserId, + this.minPlayers, + this.maxPlayers, + this.enrollmentEndsAtMs, + this.createdAtMs, + this.updatedAtMs + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-request.ts new file mode 100644 index 0000000..5bbf71c --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-request.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class InviteDeclineRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):InviteDeclineRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsInviteDeclineRequest(bb:flatbuffers.ByteBuffer, obj?:InviteDeclineRequest):InviteDeclineRequest { + return (obj || new InviteDeclineRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsInviteDeclineRequest(bb:flatbuffers.ByteBuffer, obj?:InviteDeclineRequest):InviteDeclineRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new InviteDeclineRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +inviteId():string|null +inviteId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +inviteId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startInviteDeclineRequest(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameIdOffset, 0); +} + +static addInviteId(builder:flatbuffers.Builder, inviteIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, inviteIdOffset, 0); +} + +static endInviteDeclineRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createInviteDeclineRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, inviteIdOffset:flatbuffers.Offset):flatbuffers.Offset { + InviteDeclineRequest.startInviteDeclineRequest(builder); + InviteDeclineRequest.addGameId(builder, gameIdOffset); + InviteDeclineRequest.addInviteId(builder, inviteIdOffset); + return InviteDeclineRequest.endInviteDeclineRequest(builder); +} + +unpack(): InviteDeclineRequestT { + return new InviteDeclineRequestT( + this.gameId(), + this.inviteId() + ); +} + + +unpackTo(_o: InviteDeclineRequestT): void { + _o.gameId = this.gameId(); + _o.inviteId = this.inviteId(); +} +} + +export class InviteDeclineRequestT implements flatbuffers.IGeneratedObject { +constructor( + public gameId: string|Uint8Array|null = null, + public inviteId: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const inviteId = (this.inviteId !== null ? builder.createString(this.inviteId!) : 0); + + return InviteDeclineRequest.createInviteDeclineRequest(builder, + gameId, + inviteId + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-response.ts new file mode 100644 index 0000000..29f914b --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-decline-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { InviteSummary, InviteSummaryT } from './invite-summary.js'; + + +export class InviteDeclineResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):InviteDeclineResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsInviteDeclineResponse(bb:flatbuffers.ByteBuffer, obj?:InviteDeclineResponse):InviteDeclineResponse { + return (obj || new InviteDeclineResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsInviteDeclineResponse(bb:flatbuffers.ByteBuffer, obj?:InviteDeclineResponse):InviteDeclineResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new InviteDeclineResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +invite(obj?:InviteSummary):InviteSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new InviteSummary()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startInviteDeclineResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addInvite(builder:flatbuffers.Builder, inviteOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, inviteOffset, 0); +} + +static endInviteDeclineResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createInviteDeclineResponse(builder:flatbuffers.Builder, inviteOffset:flatbuffers.Offset):flatbuffers.Offset { + InviteDeclineResponse.startInviteDeclineResponse(builder); + InviteDeclineResponse.addInvite(builder, inviteOffset); + return InviteDeclineResponse.endInviteDeclineResponse(builder); +} + +unpack(): InviteDeclineResponseT { + return new InviteDeclineResponseT( + (this.invite() !== null ? this.invite()!.unpack() : null) + ); +} + + +unpackTo(_o: InviteDeclineResponseT): void { + _o.invite = (this.invite() !== null ? this.invite()!.unpack() : null); +} +} + +export class InviteDeclineResponseT implements flatbuffers.IGeneratedObject { +constructor( + public invite: InviteSummaryT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const invite = (this.invite !== null ? this.invite!.pack(builder) : 0); + + return InviteDeclineResponse.createInviteDeclineResponse(builder, + invite + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-request.ts new file mode 100644 index 0000000..8ee5004 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-request.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class InviteRedeemRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):InviteRedeemRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsInviteRedeemRequest(bb:flatbuffers.ByteBuffer, obj?:InviteRedeemRequest):InviteRedeemRequest { + return (obj || new InviteRedeemRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsInviteRedeemRequest(bb:flatbuffers.ByteBuffer, obj?:InviteRedeemRequest):InviteRedeemRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new InviteRedeemRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +inviteId():string|null +inviteId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +inviteId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startInviteRedeemRequest(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameIdOffset, 0); +} + +static addInviteId(builder:flatbuffers.Builder, inviteIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, inviteIdOffset, 0); +} + +static endInviteRedeemRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createInviteRedeemRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, inviteIdOffset:flatbuffers.Offset):flatbuffers.Offset { + InviteRedeemRequest.startInviteRedeemRequest(builder); + InviteRedeemRequest.addGameId(builder, gameIdOffset); + InviteRedeemRequest.addInviteId(builder, inviteIdOffset); + return InviteRedeemRequest.endInviteRedeemRequest(builder); +} + +unpack(): InviteRedeemRequestT { + return new InviteRedeemRequestT( + this.gameId(), + this.inviteId() + ); +} + + +unpackTo(_o: InviteRedeemRequestT): void { + _o.gameId = this.gameId(); + _o.inviteId = this.inviteId(); +} +} + +export class InviteRedeemRequestT implements flatbuffers.IGeneratedObject { +constructor( + public gameId: string|Uint8Array|null = null, + public inviteId: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const inviteId = (this.inviteId !== null ? builder.createString(this.inviteId!) : 0); + + return InviteRedeemRequest.createInviteRedeemRequest(builder, + gameId, + inviteId + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-response.ts new file mode 100644 index 0000000..c33c329 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-redeem-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { InviteSummary, InviteSummaryT } from './invite-summary.js'; + + +export class InviteRedeemResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):InviteRedeemResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsInviteRedeemResponse(bb:flatbuffers.ByteBuffer, obj?:InviteRedeemResponse):InviteRedeemResponse { + return (obj || new InviteRedeemResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsInviteRedeemResponse(bb:flatbuffers.ByteBuffer, obj?:InviteRedeemResponse):InviteRedeemResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new InviteRedeemResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +invite(obj?:InviteSummary):InviteSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new InviteSummary()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startInviteRedeemResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addInvite(builder:flatbuffers.Builder, inviteOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, inviteOffset, 0); +} + +static endInviteRedeemResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createInviteRedeemResponse(builder:flatbuffers.Builder, inviteOffset:flatbuffers.Offset):flatbuffers.Offset { + InviteRedeemResponse.startInviteRedeemResponse(builder); + InviteRedeemResponse.addInvite(builder, inviteOffset); + return InviteRedeemResponse.endInviteRedeemResponse(builder); +} + +unpack(): InviteRedeemResponseT { + return new InviteRedeemResponseT( + (this.invite() !== null ? this.invite()!.unpack() : null) + ); +} + + +unpackTo(_o: InviteRedeemResponseT): void { + _o.invite = (this.invite() !== null ? this.invite()!.unpack() : null); +} +} + +export class InviteRedeemResponseT implements flatbuffers.IGeneratedObject { +constructor( + public invite: InviteSummaryT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const invite = (this.invite !== null ? this.invite!.pack(builder) : 0); + + return InviteRedeemResponse.createInviteRedeemResponse(builder, + invite + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/invite-summary.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-summary.ts new file mode 100644 index 0000000..2924462 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/invite-summary.ts @@ -0,0 +1,222 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class InviteSummary implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):InviteSummary { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsInviteSummary(bb:flatbuffers.ByteBuffer, obj?:InviteSummary):InviteSummary { + return (obj || new InviteSummary()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsInviteSummary(bb:flatbuffers.ByteBuffer, obj?:InviteSummary):InviteSummary { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new InviteSummary()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +inviteId():string|null +inviteId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +inviteId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +inviterUserId():string|null +inviterUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +inviterUserId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +invitedUserId():string|null +invitedUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +invitedUserId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +code():string|null +code(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +code(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +raceName():string|null +raceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +raceName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +status():string|null +status(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +status(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +createdAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 18); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +expiresAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 20); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +decidedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 22); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startInviteSummary(builder:flatbuffers.Builder) { + builder.startObject(10); +} + +static addInviteId(builder:flatbuffers.Builder, inviteIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, inviteIdOffset, 0); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, gameIdOffset, 0); +} + +static addInviterUserId(builder:flatbuffers.Builder, inviterUserIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, inviterUserIdOffset, 0); +} + +static addInvitedUserId(builder:flatbuffers.Builder, invitedUserIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, invitedUserIdOffset, 0); +} + +static addCode(builder:flatbuffers.Builder, codeOffset:flatbuffers.Offset) { + builder.addFieldOffset(4, codeOffset, 0); +} + +static addRaceName(builder:flatbuffers.Builder, raceNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(5, raceNameOffset, 0); +} + +static addStatus(builder:flatbuffers.Builder, statusOffset:flatbuffers.Offset) { + builder.addFieldOffset(6, statusOffset, 0); +} + +static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) { + builder.addFieldInt64(7, createdAtMs, BigInt('0')); +} + +static addExpiresAtMs(builder:flatbuffers.Builder, expiresAtMs:bigint) { + builder.addFieldInt64(8, expiresAtMs, BigInt('0')); +} + +static addDecidedAtMs(builder:flatbuffers.Builder, decidedAtMs:bigint) { + builder.addFieldInt64(9, decidedAtMs, BigInt('0')); +} + +static endInviteSummary(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createInviteSummary(builder:flatbuffers.Builder, inviteIdOffset:flatbuffers.Offset, gameIdOffset:flatbuffers.Offset, inviterUserIdOffset:flatbuffers.Offset, invitedUserIdOffset:flatbuffers.Offset, codeOffset:flatbuffers.Offset, raceNameOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset, createdAtMs:bigint, expiresAtMs:bigint, decidedAtMs:bigint):flatbuffers.Offset { + InviteSummary.startInviteSummary(builder); + InviteSummary.addInviteId(builder, inviteIdOffset); + InviteSummary.addGameId(builder, gameIdOffset); + InviteSummary.addInviterUserId(builder, inviterUserIdOffset); + InviteSummary.addInvitedUserId(builder, invitedUserIdOffset); + InviteSummary.addCode(builder, codeOffset); + InviteSummary.addRaceName(builder, raceNameOffset); + InviteSummary.addStatus(builder, statusOffset); + InviteSummary.addCreatedAtMs(builder, createdAtMs); + InviteSummary.addExpiresAtMs(builder, expiresAtMs); + InviteSummary.addDecidedAtMs(builder, decidedAtMs); + return InviteSummary.endInviteSummary(builder); +} + +unpack(): InviteSummaryT { + return new InviteSummaryT( + this.inviteId(), + this.gameId(), + this.inviterUserId(), + this.invitedUserId(), + this.code(), + this.raceName(), + this.status(), + this.createdAtMs(), + this.expiresAtMs(), + this.decidedAtMs() + ); +} + + +unpackTo(_o: InviteSummaryT): void { + _o.inviteId = this.inviteId(); + _o.gameId = this.gameId(); + _o.inviterUserId = this.inviterUserId(); + _o.invitedUserId = this.invitedUserId(); + _o.code = this.code(); + _o.raceName = this.raceName(); + _o.status = this.status(); + _o.createdAtMs = this.createdAtMs(); + _o.expiresAtMs = this.expiresAtMs(); + _o.decidedAtMs = this.decidedAtMs(); +} +} + +export class InviteSummaryT implements flatbuffers.IGeneratedObject { +constructor( + public inviteId: string|Uint8Array|null = null, + public gameId: string|Uint8Array|null = null, + public inviterUserId: string|Uint8Array|null = null, + public invitedUserId: string|Uint8Array|null = null, + public code: string|Uint8Array|null = null, + public raceName: string|Uint8Array|null = null, + public status: string|Uint8Array|null = null, + public createdAtMs: bigint = BigInt('0'), + public expiresAtMs: bigint = BigInt('0'), + public decidedAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const inviteId = (this.inviteId !== null ? builder.createString(this.inviteId!) : 0); + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const inviterUserId = (this.inviterUserId !== null ? builder.createString(this.inviterUserId!) : 0); + const invitedUserId = (this.invitedUserId !== null ? builder.createString(this.invitedUserId!) : 0); + const code = (this.code !== null ? builder.createString(this.code!) : 0); + const raceName = (this.raceName !== null ? builder.createString(this.raceName!) : 0); + const status = (this.status !== null ? builder.createString(this.status!) : 0); + + return InviteSummary.createInviteSummary(builder, + inviteId, + gameId, + inviterUserId, + invitedUserId, + code, + raceName, + status, + this.createdAtMs, + this.expiresAtMs, + this.decidedAtMs + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-request.ts new file mode 100644 index 0000000..16fc57c --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-request.ts @@ -0,0 +1,56 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class MyApplicationsListRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):MyApplicationsListRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsMyApplicationsListRequest(bb:flatbuffers.ByteBuffer, obj?:MyApplicationsListRequest):MyApplicationsListRequest { + return (obj || new MyApplicationsListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsMyApplicationsListRequest(bb:flatbuffers.ByteBuffer, obj?:MyApplicationsListRequest):MyApplicationsListRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new MyApplicationsListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static startMyApplicationsListRequest(builder:flatbuffers.Builder) { + builder.startObject(0); +} + +static endMyApplicationsListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createMyApplicationsListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + MyApplicationsListRequest.startMyApplicationsListRequest(builder); + return MyApplicationsListRequest.endMyApplicationsListRequest(builder); +} + +unpack(): MyApplicationsListRequestT { + return new MyApplicationsListRequestT(); +} + + +unpackTo(_o: MyApplicationsListRequestT): void {} +} + +export class MyApplicationsListRequestT implements flatbuffers.IGeneratedObject { +constructor(){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return MyApplicationsListRequest.createMyApplicationsListRequest(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-response.ts new file mode 100644 index 0000000..d2be296 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/my-applications-list-response.ts @@ -0,0 +1,94 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ApplicationSummary, ApplicationSummaryT } from './application-summary.js'; + + +export class MyApplicationsListResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):MyApplicationsListResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsMyApplicationsListResponse(bb:flatbuffers.ByteBuffer, obj?:MyApplicationsListResponse):MyApplicationsListResponse { + return (obj || new MyApplicationsListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsMyApplicationsListResponse(bb:flatbuffers.ByteBuffer, obj?:MyApplicationsListResponse):MyApplicationsListResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new MyApplicationsListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +items(index: number, obj?:ApplicationSummary):ApplicationSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new ApplicationSummary()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +itemsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +static startMyApplicationsListResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addItems(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, itemsOffset, 0); +} + +static createItemsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startItemsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static endMyApplicationsListResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createMyApplicationsListResponse(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset):flatbuffers.Offset { + MyApplicationsListResponse.startMyApplicationsListResponse(builder); + MyApplicationsListResponse.addItems(builder, itemsOffset); + return MyApplicationsListResponse.endMyApplicationsListResponse(builder); +} + +unpack(): MyApplicationsListResponseT { + return new MyApplicationsListResponseT( + this.bb!.createObjList(this.items.bind(this), this.itemsLength()) + ); +} + + +unpackTo(_o: MyApplicationsListResponseT): void { + _o.items = this.bb!.createObjList(this.items.bind(this), this.itemsLength()); +} +} + +export class MyApplicationsListResponseT implements flatbuffers.IGeneratedObject { +constructor( + public items: (ApplicationSummaryT)[] = [] +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const items = MyApplicationsListResponse.createItemsVector(builder, builder.createObjectOffsetList(this.items)); + + return MyApplicationsListResponse.createMyApplicationsListResponse(builder, + items + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-request.ts new file mode 100644 index 0000000..9c62b3f --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-request.ts @@ -0,0 +1,56 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class MyGamesListRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):MyGamesListRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsMyGamesListRequest(bb:flatbuffers.ByteBuffer, obj?:MyGamesListRequest):MyGamesListRequest { + return (obj || new MyGamesListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsMyGamesListRequest(bb:flatbuffers.ByteBuffer, obj?:MyGamesListRequest):MyGamesListRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new MyGamesListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static startMyGamesListRequest(builder:flatbuffers.Builder) { + builder.startObject(0); +} + +static endMyGamesListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createMyGamesListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + MyGamesListRequest.startMyGamesListRequest(builder); + return MyGamesListRequest.endMyGamesListRequest(builder); +} + +unpack(): MyGamesListRequestT { + return new MyGamesListRequestT(); +} + + +unpackTo(_o: MyGamesListRequestT): void {} +} + +export class MyGamesListRequestT implements flatbuffers.IGeneratedObject { +constructor(){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return MyGamesListRequest.createMyGamesListRequest(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-response.ts new file mode 100644 index 0000000..ece5c6f --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/my-games-list-response.ts @@ -0,0 +1,102 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { GameSummary, GameSummaryT } from './game-summary.js'; + + +export class MyGamesListResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):MyGamesListResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsMyGamesListResponse(bb:flatbuffers.ByteBuffer, obj?:MyGamesListResponse):MyGamesListResponse { + return (obj || new MyGamesListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsMyGamesListResponse(bb:flatbuffers.ByteBuffer, obj?:MyGamesListResponse):MyGamesListResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new MyGamesListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +items(index: number, obj?:GameSummary):GameSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new GameSummary()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +itemsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +static startMyGamesListResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addItems(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, itemsOffset, 0); +} + +static createItemsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startItemsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static endMyGamesListResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static finishMyGamesListResponseBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) { + builder.finish(offset); +} + +static finishSizePrefixedMyGamesListResponseBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) { + builder.finish(offset, undefined, true); +} + +static createMyGamesListResponse(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset):flatbuffers.Offset { + MyGamesListResponse.startMyGamesListResponse(builder); + MyGamesListResponse.addItems(builder, itemsOffset); + return MyGamesListResponse.endMyGamesListResponse(builder); +} + +unpack(): MyGamesListResponseT { + return new MyGamesListResponseT( + this.bb!.createObjList(this.items.bind(this), this.itemsLength()) + ); +} + + +unpackTo(_o: MyGamesListResponseT): void { + _o.items = this.bb!.createObjList(this.items.bind(this), this.itemsLength()); +} +} + +export class MyGamesListResponseT implements flatbuffers.IGeneratedObject { +constructor( + public items: (GameSummaryT)[] = [] +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const items = MyGamesListResponse.createItemsVector(builder, builder.createObjectOffsetList(this.items)); + + return MyGamesListResponse.createMyGamesListResponse(builder, + items + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-request.ts new file mode 100644 index 0000000..bedca82 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-request.ts @@ -0,0 +1,56 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class MyInvitesListRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):MyInvitesListRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsMyInvitesListRequest(bb:flatbuffers.ByteBuffer, obj?:MyInvitesListRequest):MyInvitesListRequest { + return (obj || new MyInvitesListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsMyInvitesListRequest(bb:flatbuffers.ByteBuffer, obj?:MyInvitesListRequest):MyInvitesListRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new MyInvitesListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static startMyInvitesListRequest(builder:flatbuffers.Builder) { + builder.startObject(0); +} + +static endMyInvitesListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createMyInvitesListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + MyInvitesListRequest.startMyInvitesListRequest(builder); + return MyInvitesListRequest.endMyInvitesListRequest(builder); +} + +unpack(): MyInvitesListRequestT { + return new MyInvitesListRequestT(); +} + + +unpackTo(_o: MyInvitesListRequestT): void {} +} + +export class MyInvitesListRequestT implements flatbuffers.IGeneratedObject { +constructor(){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return MyInvitesListRequest.createMyInvitesListRequest(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-response.ts new file mode 100644 index 0000000..42fde82 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/my-invites-list-response.ts @@ -0,0 +1,94 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { InviteSummary, InviteSummaryT } from './invite-summary.js'; + + +export class MyInvitesListResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):MyInvitesListResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsMyInvitesListResponse(bb:flatbuffers.ByteBuffer, obj?:MyInvitesListResponse):MyInvitesListResponse { + return (obj || new MyInvitesListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsMyInvitesListResponse(bb:flatbuffers.ByteBuffer, obj?:MyInvitesListResponse):MyInvitesListResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new MyInvitesListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +items(index: number, obj?:InviteSummary):InviteSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new InviteSummary()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +itemsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +static startMyInvitesListResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addItems(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, itemsOffset, 0); +} + +static createItemsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startItemsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static endMyInvitesListResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createMyInvitesListResponse(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset):flatbuffers.Offset { + MyInvitesListResponse.startMyInvitesListResponse(builder); + MyInvitesListResponse.addItems(builder, itemsOffset); + return MyInvitesListResponse.endMyInvitesListResponse(builder); +} + +unpack(): MyInvitesListResponseT { + return new MyInvitesListResponseT( + this.bb!.createObjList(this.items.bind(this), this.itemsLength()) + ); +} + + +unpackTo(_o: MyInvitesListResponseT): void { + _o.items = this.bb!.createObjList(this.items.bind(this), this.itemsLength()); +} +} + +export class MyInvitesListResponseT implements flatbuffers.IGeneratedObject { +constructor( + public items: (InviteSummaryT)[] = [] +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const items = MyInvitesListResponse.createItemsVector(builder, builder.createObjectOffsetList(this.items)); + + return MyInvitesListResponse.createMyInvitesListResponse(builder, + items + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-request.ts new file mode 100644 index 0000000..b8b2923 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-request.ts @@ -0,0 +1,78 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class OpenEnrollmentRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):OpenEnrollmentRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsOpenEnrollmentRequest(bb:flatbuffers.ByteBuffer, obj?:OpenEnrollmentRequest):OpenEnrollmentRequest { + return (obj || new OpenEnrollmentRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsOpenEnrollmentRequest(bb:flatbuffers.ByteBuffer, obj?:OpenEnrollmentRequest):OpenEnrollmentRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new OpenEnrollmentRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startOpenEnrollmentRequest(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameIdOffset, 0); +} + +static endOpenEnrollmentRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createOpenEnrollmentRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset):flatbuffers.Offset { + OpenEnrollmentRequest.startOpenEnrollmentRequest(builder); + OpenEnrollmentRequest.addGameId(builder, gameIdOffset); + return OpenEnrollmentRequest.endOpenEnrollmentRequest(builder); +} + +unpack(): OpenEnrollmentRequestT { + return new OpenEnrollmentRequestT( + this.gameId() + ); +} + + +unpackTo(_o: OpenEnrollmentRequestT): void { + _o.gameId = this.gameId(); +} +} + +export class OpenEnrollmentRequestT implements flatbuffers.IGeneratedObject { +constructor( + public gameId: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + + return OpenEnrollmentRequest.createOpenEnrollmentRequest(builder, + gameId + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-response.ts new file mode 100644 index 0000000..1cd74cb --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/open-enrollment-response.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class OpenEnrollmentResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):OpenEnrollmentResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsOpenEnrollmentResponse(bb:flatbuffers.ByteBuffer, obj?:OpenEnrollmentResponse):OpenEnrollmentResponse { + return (obj || new OpenEnrollmentResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsOpenEnrollmentResponse(bb:flatbuffers.ByteBuffer, obj?:OpenEnrollmentResponse):OpenEnrollmentResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new OpenEnrollmentResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +gameId():string|null +gameId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +gameId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +status():string|null +status(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +status(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startOpenEnrollmentResponse(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, gameIdOffset, 0); +} + +static addStatus(builder:flatbuffers.Builder, statusOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, statusOffset, 0); +} + +static endOpenEnrollmentResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createOpenEnrollmentResponse(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset):flatbuffers.Offset { + OpenEnrollmentResponse.startOpenEnrollmentResponse(builder); + OpenEnrollmentResponse.addGameId(builder, gameIdOffset); + OpenEnrollmentResponse.addStatus(builder, statusOffset); + return OpenEnrollmentResponse.endOpenEnrollmentResponse(builder); +} + +unpack(): OpenEnrollmentResponseT { + return new OpenEnrollmentResponseT( + this.gameId(), + this.status() + ); +} + + +unpackTo(_o: OpenEnrollmentResponseT): void { + _o.gameId = this.gameId(); + _o.status = this.status(); +} +} + +export class OpenEnrollmentResponseT implements flatbuffers.IGeneratedObject { +constructor( + public gameId: string|Uint8Array|null = null, + public status: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0); + const status = (this.status !== null ? builder.createString(this.status!) : 0); + + return OpenEnrollmentResponse.createOpenEnrollmentResponse(builder, + gameId, + status + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-request.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-request.ts new file mode 100644 index 0000000..5e3aef5 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-request.ts @@ -0,0 +1,88 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class PublicGamesListRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):PublicGamesListRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsPublicGamesListRequest(bb:flatbuffers.ByteBuffer, obj?:PublicGamesListRequest):PublicGamesListRequest { + return (obj || new PublicGamesListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsPublicGamesListRequest(bb:flatbuffers.ByteBuffer, obj?:PublicGamesListRequest):PublicGamesListRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new PublicGamesListRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +page():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +pageSize():number { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +static startPublicGamesListRequest(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addPage(builder:flatbuffers.Builder, page:number) { + builder.addFieldInt32(0, page, 0); +} + +static addPageSize(builder:flatbuffers.Builder, pageSize:number) { + builder.addFieldInt32(1, pageSize, 0); +} + +static endPublicGamesListRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createPublicGamesListRequest(builder:flatbuffers.Builder, page:number, pageSize:number):flatbuffers.Offset { + PublicGamesListRequest.startPublicGamesListRequest(builder); + PublicGamesListRequest.addPage(builder, page); + PublicGamesListRequest.addPageSize(builder, pageSize); + return PublicGamesListRequest.endPublicGamesListRequest(builder); +} + +unpack(): PublicGamesListRequestT { + return new PublicGamesListRequestT( + this.page(), + this.pageSize() + ); +} + + +unpackTo(_o: PublicGamesListRequestT): void { + _o.page = this.page(); + _o.pageSize = this.pageSize(); +} +} + +export class PublicGamesListRequestT implements flatbuffers.IGeneratedObject { +constructor( + public page: number = 0, + public pageSize: number = 0 +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return PublicGamesListRequest.createPublicGamesListRequest(builder, + this.page, + this.pageSize + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-response.ts b/ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-response.ts new file mode 100644 index 0000000..2b49679 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/lobby/public-games-list-response.ts @@ -0,0 +1,136 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { GameSummary, GameSummaryT } from './game-summary.js'; + + +export class PublicGamesListResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):PublicGamesListResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsPublicGamesListResponse(bb:flatbuffers.ByteBuffer, obj?:PublicGamesListResponse):PublicGamesListResponse { + return (obj || new PublicGamesListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsPublicGamesListResponse(bb:flatbuffers.ByteBuffer, obj?:PublicGamesListResponse):PublicGamesListResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new PublicGamesListResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +items(index: number, obj?:GameSummary):GameSummary|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new GameSummary()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +itemsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +page():number { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +pageSize():number { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +total():number { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +static startPublicGamesListResponse(builder:flatbuffers.Builder) { + builder.startObject(4); +} + +static addItems(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, itemsOffset, 0); +} + +static createItemsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startItemsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static addPage(builder:flatbuffers.Builder, page:number) { + builder.addFieldInt32(1, page, 0); +} + +static addPageSize(builder:flatbuffers.Builder, pageSize:number) { + builder.addFieldInt32(2, pageSize, 0); +} + +static addTotal(builder:flatbuffers.Builder, total:number) { + builder.addFieldInt32(3, total, 0); +} + +static endPublicGamesListResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createPublicGamesListResponse(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset, page:number, pageSize:number, total:number):flatbuffers.Offset { + PublicGamesListResponse.startPublicGamesListResponse(builder); + PublicGamesListResponse.addItems(builder, itemsOffset); + PublicGamesListResponse.addPage(builder, page); + PublicGamesListResponse.addPageSize(builder, pageSize); + PublicGamesListResponse.addTotal(builder, total); + return PublicGamesListResponse.endPublicGamesListResponse(builder); +} + +unpack(): PublicGamesListResponseT { + return new PublicGamesListResponseT( + this.bb!.createObjList(this.items.bind(this), this.itemsLength()), + this.page(), + this.pageSize(), + this.total() + ); +} + + +unpackTo(_o: PublicGamesListResponseT): void { + _o.items = this.bb!.createObjList(this.items.bind(this), this.itemsLength()); + _o.page = this.page(); + _o.pageSize = this.pageSize(); + _o.total = this.total(); +} +} + +export class PublicGamesListResponseT implements flatbuffers.IGeneratedObject { +constructor( + public items: (GameSummaryT)[] = [], + public page: number = 0, + public pageSize: number = 0, + public total: number = 0 +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const items = PublicGamesListResponse.createItemsVector(builder, builder.createObjectOffsetList(this.items)); + + return PublicGamesListResponse.createPublicGamesListResponse(builder, + items, + this.page, + this.pageSize, + this.total + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user.ts b/ui/frontend/src/proto/galaxy/fbs/user.ts new file mode 100644 index 0000000..8a0ba05 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user.ts @@ -0,0 +1,23 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +export { AccountResponse, AccountResponseT } from './user/account-response.js'; +export { AccountView, AccountViewT } from './user/account-view.js'; +export { ActiveLimit, ActiveLimitT } from './user/active-limit.js'; +export { ActiveSanction, ActiveSanctionT } from './user/active-sanction.js'; +export { ActorRef, ActorRefT } from './user/actor-ref.js'; +export { DeviceSessionRevocationSummaryView, DeviceSessionRevocationSummaryViewT } from './user/device-session-revocation-summary-view.js'; +export { DeviceSessionView, DeviceSessionViewT } from './user/device-session-view.js'; +export { EntitlementSnapshot, EntitlementSnapshotT } from './user/entitlement-snapshot.js'; +export { ErrorBody, ErrorBodyT } from './user/error-body.js'; +export { ErrorResponse, ErrorResponseT } from './user/error-response.js'; +export { GetMyAccountRequest, GetMyAccountRequestT } from './user/get-my-account-request.js'; +export { ListMySessionsRequest, ListMySessionsRequestT } from './user/list-my-sessions-request.js'; +export { ListMySessionsResponse, ListMySessionsResponseT } from './user/list-my-sessions-response.js'; +export { RevokeAllMySessionsRequest, RevokeAllMySessionsRequestT } from './user/revoke-all-my-sessions-request.js'; +export { RevokeAllMySessionsResponse, RevokeAllMySessionsResponseT } from './user/revoke-all-my-sessions-response.js'; +export { RevokeMySessionRequest, RevokeMySessionRequestT } from './user/revoke-my-session-request.js'; +export { RevokeMySessionResponse, RevokeMySessionResponseT } from './user/revoke-my-session-response.js'; +export { UpdateMyProfileRequest, UpdateMyProfileRequestT } from './user/update-my-profile-request.js'; +export { UpdateMySettingsRequest, UpdateMySettingsRequestT } from './user/update-my-settings-request.js'; diff --git a/ui/frontend/src/proto/galaxy/fbs/user/account-response.ts b/ui/frontend/src/proto/galaxy/fbs/user/account-response.ts new file mode 100644 index 0000000..938756c --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/account-response.ts @@ -0,0 +1,85 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { AccountView, AccountViewT } from './account-view.js'; + + +export class AccountResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):AccountResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsAccountResponse(bb:flatbuffers.ByteBuffer, obj?:AccountResponse):AccountResponse { + return (obj || new AccountResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsAccountResponse(bb:flatbuffers.ByteBuffer, obj?:AccountResponse):AccountResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new AccountResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +account(obj?:AccountView):AccountView|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new AccountView()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startAccountResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addAccount(builder:flatbuffers.Builder, accountOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, accountOffset, 0); +} + +static endAccountResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static finishAccountResponseBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) { + builder.finish(offset); +} + +static finishSizePrefixedAccountResponseBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) { + builder.finish(offset, undefined, true); +} + +static createAccountResponse(builder:flatbuffers.Builder, accountOffset:flatbuffers.Offset):flatbuffers.Offset { + AccountResponse.startAccountResponse(builder); + AccountResponse.addAccount(builder, accountOffset); + return AccountResponse.endAccountResponse(builder); +} + +unpack(): AccountResponseT { + return new AccountResponseT( + (this.account() !== null ? this.account()!.unpack() : null) + ); +} + + +unpackTo(_o: AccountResponseT): void { + _o.account = (this.account() !== null ? this.account()!.unpack() : null); +} +} + +export class AccountResponseT implements flatbuffers.IGeneratedObject { +constructor( + public account: AccountViewT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const account = (this.account !== null ? this.account!.pack(builder) : 0); + + return AccountResponse.createAccountResponse(builder, + account + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/account-view.ts b/ui/frontend/src/proto/galaxy/fbs/user/account-view.ts new file mode 100644 index 0000000..e992d38 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/account-view.ts @@ -0,0 +1,275 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ActiveLimit, ActiveLimitT } from './active-limit.js'; +import { ActiveSanction, ActiveSanctionT } from './active-sanction.js'; +import { EntitlementSnapshot, EntitlementSnapshotT } from './entitlement-snapshot.js'; + + +export class AccountView implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):AccountView { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsAccountView(bb:flatbuffers.ByteBuffer, obj?:AccountView):AccountView { + return (obj || new AccountView()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsAccountView(bb:flatbuffers.ByteBuffer, obj?:AccountView):AccountView { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new AccountView()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +userId():string|null +userId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +userId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +email():string|null +email(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +email(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +userName():string|null +userName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +userName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +displayName():string|null +displayName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +displayName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +preferredLanguage():string|null +preferredLanguage(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +preferredLanguage(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +timeZone():string|null +timeZone(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +timeZone(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +declaredCountry():string|null +declaredCountry(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +declaredCountry(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +entitlement(obj?:EntitlementSnapshot):EntitlementSnapshot|null { + const offset = this.bb!.__offset(this.bb_pos, 18); + return offset ? (obj || new EntitlementSnapshot()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +activeSanctions(index: number, obj?:ActiveSanction):ActiveSanction|null { + const offset = this.bb!.__offset(this.bb_pos, 20); + return offset ? (obj || new ActiveSanction()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +activeSanctionsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 20); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +activeLimits(index: number, obj?:ActiveLimit):ActiveLimit|null { + const offset = this.bb!.__offset(this.bb_pos, 22); + return offset ? (obj || new ActiveLimit()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +activeLimitsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 22); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +createdAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 24); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +updatedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 26); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startAccountView(builder:flatbuffers.Builder) { + builder.startObject(12); +} + +static addUserId(builder:flatbuffers.Builder, userIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, userIdOffset, 0); +} + +static addEmail(builder:flatbuffers.Builder, emailOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, emailOffset, 0); +} + +static addUserName(builder:flatbuffers.Builder, userNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, userNameOffset, 0); +} + +static addDisplayName(builder:flatbuffers.Builder, displayNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, displayNameOffset, 0); +} + +static addPreferredLanguage(builder:flatbuffers.Builder, preferredLanguageOffset:flatbuffers.Offset) { + builder.addFieldOffset(4, preferredLanguageOffset, 0); +} + +static addTimeZone(builder:flatbuffers.Builder, timeZoneOffset:flatbuffers.Offset) { + builder.addFieldOffset(5, timeZoneOffset, 0); +} + +static addDeclaredCountry(builder:flatbuffers.Builder, declaredCountryOffset:flatbuffers.Offset) { + builder.addFieldOffset(6, declaredCountryOffset, 0); +} + +static addEntitlement(builder:flatbuffers.Builder, entitlementOffset:flatbuffers.Offset) { + builder.addFieldOffset(7, entitlementOffset, 0); +} + +static addActiveSanctions(builder:flatbuffers.Builder, activeSanctionsOffset:flatbuffers.Offset) { + builder.addFieldOffset(8, activeSanctionsOffset, 0); +} + +static createActiveSanctionsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startActiveSanctionsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static addActiveLimits(builder:flatbuffers.Builder, activeLimitsOffset:flatbuffers.Offset) { + builder.addFieldOffset(9, activeLimitsOffset, 0); +} + +static createActiveLimitsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startActiveLimitsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) { + builder.addFieldInt64(10, createdAtMs, BigInt('0')); +} + +static addUpdatedAtMs(builder:flatbuffers.Builder, updatedAtMs:bigint) { + builder.addFieldInt64(11, updatedAtMs, BigInt('0')); +} + +static endAccountView(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + + +unpack(): AccountViewT { + return new AccountViewT( + this.userId(), + this.email(), + this.userName(), + this.displayName(), + this.preferredLanguage(), + this.timeZone(), + this.declaredCountry(), + (this.entitlement() !== null ? this.entitlement()!.unpack() : null), + this.bb!.createObjList(this.activeSanctions.bind(this), this.activeSanctionsLength()), + this.bb!.createObjList(this.activeLimits.bind(this), this.activeLimitsLength()), + this.createdAtMs(), + this.updatedAtMs() + ); +} + + +unpackTo(_o: AccountViewT): void { + _o.userId = this.userId(); + _o.email = this.email(); + _o.userName = this.userName(); + _o.displayName = this.displayName(); + _o.preferredLanguage = this.preferredLanguage(); + _o.timeZone = this.timeZone(); + _o.declaredCountry = this.declaredCountry(); + _o.entitlement = (this.entitlement() !== null ? this.entitlement()!.unpack() : null); + _o.activeSanctions = this.bb!.createObjList(this.activeSanctions.bind(this), this.activeSanctionsLength()); + _o.activeLimits = this.bb!.createObjList(this.activeLimits.bind(this), this.activeLimitsLength()); + _o.createdAtMs = this.createdAtMs(); + _o.updatedAtMs = this.updatedAtMs(); +} +} + +export class AccountViewT implements flatbuffers.IGeneratedObject { +constructor( + public userId: string|Uint8Array|null = null, + public email: string|Uint8Array|null = null, + public userName: string|Uint8Array|null = null, + public displayName: string|Uint8Array|null = null, + public preferredLanguage: string|Uint8Array|null = null, + public timeZone: string|Uint8Array|null = null, + public declaredCountry: string|Uint8Array|null = null, + public entitlement: EntitlementSnapshotT|null = null, + public activeSanctions: (ActiveSanctionT)[] = [], + public activeLimits: (ActiveLimitT)[] = [], + public createdAtMs: bigint = BigInt('0'), + public updatedAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const userId = (this.userId !== null ? builder.createString(this.userId!) : 0); + const email = (this.email !== null ? builder.createString(this.email!) : 0); + const userName = (this.userName !== null ? builder.createString(this.userName!) : 0); + const displayName = (this.displayName !== null ? builder.createString(this.displayName!) : 0); + const preferredLanguage = (this.preferredLanguage !== null ? builder.createString(this.preferredLanguage!) : 0); + const timeZone = (this.timeZone !== null ? builder.createString(this.timeZone!) : 0); + const declaredCountry = (this.declaredCountry !== null ? builder.createString(this.declaredCountry!) : 0); + const entitlement = (this.entitlement !== null ? this.entitlement!.pack(builder) : 0); + const activeSanctions = AccountView.createActiveSanctionsVector(builder, builder.createObjectOffsetList(this.activeSanctions)); + const activeLimits = AccountView.createActiveLimitsVector(builder, builder.createObjectOffsetList(this.activeLimits)); + + AccountView.startAccountView(builder); + AccountView.addUserId(builder, userId); + AccountView.addEmail(builder, email); + AccountView.addUserName(builder, userName); + AccountView.addDisplayName(builder, displayName); + AccountView.addPreferredLanguage(builder, preferredLanguage); + AccountView.addTimeZone(builder, timeZone); + AccountView.addDeclaredCountry(builder, declaredCountry); + AccountView.addEntitlement(builder, entitlement); + AccountView.addActiveSanctions(builder, activeSanctions); + AccountView.addActiveLimits(builder, activeLimits); + AccountView.addCreatedAtMs(builder, this.createdAtMs); + AccountView.addUpdatedAtMs(builder, this.updatedAtMs); + + return AccountView.endAccountView(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/active-limit.ts b/ui/frontend/src/proto/galaxy/fbs/user/active-limit.ts new file mode 100644 index 0000000..775fc37 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/active-limit.ts @@ -0,0 +1,144 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ActorRef, ActorRefT } from './actor-ref.js'; + + +export class ActiveLimit implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ActiveLimit { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsActiveLimit(bb:flatbuffers.ByteBuffer, obj?:ActiveLimit):ActiveLimit { + return (obj || new ActiveLimit()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsActiveLimit(bb:flatbuffers.ByteBuffer, obj?:ActiveLimit):ActiveLimit { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ActiveLimit()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +limitCode():string|null +limitCode(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +limitCode(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +value():bigint { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +reasonCode():string|null +reasonCode(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +reasonCode(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +actor(obj?:ActorRef):ActorRef|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? (obj || new ActorRef()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +appliedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +expiresAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startActiveLimit(builder:flatbuffers.Builder) { + builder.startObject(6); +} + +static addLimitCode(builder:flatbuffers.Builder, limitCodeOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, limitCodeOffset, 0); +} + +static addValue(builder:flatbuffers.Builder, value:bigint) { + builder.addFieldInt64(1, value, BigInt('0')); +} + +static addReasonCode(builder:flatbuffers.Builder, reasonCodeOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, reasonCodeOffset, 0); +} + +static addActor(builder:flatbuffers.Builder, actorOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, actorOffset, 0); +} + +static addAppliedAtMs(builder:flatbuffers.Builder, appliedAtMs:bigint) { + builder.addFieldInt64(4, appliedAtMs, BigInt('0')); +} + +static addExpiresAtMs(builder:flatbuffers.Builder, expiresAtMs:bigint) { + builder.addFieldInt64(5, expiresAtMs, BigInt('0')); +} + +static endActiveLimit(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + + +unpack(): ActiveLimitT { + return new ActiveLimitT( + this.limitCode(), + this.value(), + this.reasonCode(), + (this.actor() !== null ? this.actor()!.unpack() : null), + this.appliedAtMs(), + this.expiresAtMs() + ); +} + + +unpackTo(_o: ActiveLimitT): void { + _o.limitCode = this.limitCode(); + _o.value = this.value(); + _o.reasonCode = this.reasonCode(); + _o.actor = (this.actor() !== null ? this.actor()!.unpack() : null); + _o.appliedAtMs = this.appliedAtMs(); + _o.expiresAtMs = this.expiresAtMs(); +} +} + +export class ActiveLimitT implements flatbuffers.IGeneratedObject { +constructor( + public limitCode: string|Uint8Array|null = null, + public value: bigint = BigInt('0'), + public reasonCode: string|Uint8Array|null = null, + public actor: ActorRefT|null = null, + public appliedAtMs: bigint = BigInt('0'), + public expiresAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const limitCode = (this.limitCode !== null ? builder.createString(this.limitCode!) : 0); + const reasonCode = (this.reasonCode !== null ? builder.createString(this.reasonCode!) : 0); + const actor = (this.actor !== null ? this.actor!.pack(builder) : 0); + + ActiveLimit.startActiveLimit(builder); + ActiveLimit.addLimitCode(builder, limitCode); + ActiveLimit.addValue(builder, this.value); + ActiveLimit.addReasonCode(builder, reasonCode); + ActiveLimit.addActor(builder, actor); + ActiveLimit.addAppliedAtMs(builder, this.appliedAtMs); + ActiveLimit.addExpiresAtMs(builder, this.expiresAtMs); + + return ActiveLimit.endActiveLimit(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/active-sanction.ts b/ui/frontend/src/proto/galaxy/fbs/user/active-sanction.ts new file mode 100644 index 0000000..136db50 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/active-sanction.ts @@ -0,0 +1,147 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ActorRef, ActorRefT } from './actor-ref.js'; + + +export class ActiveSanction implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ActiveSanction { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsActiveSanction(bb:flatbuffers.ByteBuffer, obj?:ActiveSanction):ActiveSanction { + return (obj || new ActiveSanction()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsActiveSanction(bb:flatbuffers.ByteBuffer, obj?:ActiveSanction):ActiveSanction { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ActiveSanction()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +sanctionCode():string|null +sanctionCode(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +sanctionCode(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +scope():string|null +scope(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +scope(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +reasonCode():string|null +reasonCode(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +reasonCode(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +actor(obj?:ActorRef):ActorRef|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? (obj || new ActorRef()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +appliedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +expiresAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startActiveSanction(builder:flatbuffers.Builder) { + builder.startObject(6); +} + +static addSanctionCode(builder:flatbuffers.Builder, sanctionCodeOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, sanctionCodeOffset, 0); +} + +static addScope(builder:flatbuffers.Builder, scopeOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, scopeOffset, 0); +} + +static addReasonCode(builder:flatbuffers.Builder, reasonCodeOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, reasonCodeOffset, 0); +} + +static addActor(builder:flatbuffers.Builder, actorOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, actorOffset, 0); +} + +static addAppliedAtMs(builder:flatbuffers.Builder, appliedAtMs:bigint) { + builder.addFieldInt64(4, appliedAtMs, BigInt('0')); +} + +static addExpiresAtMs(builder:flatbuffers.Builder, expiresAtMs:bigint) { + builder.addFieldInt64(5, expiresAtMs, BigInt('0')); +} + +static endActiveSanction(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + + +unpack(): ActiveSanctionT { + return new ActiveSanctionT( + this.sanctionCode(), + this.scope(), + this.reasonCode(), + (this.actor() !== null ? this.actor()!.unpack() : null), + this.appliedAtMs(), + this.expiresAtMs() + ); +} + + +unpackTo(_o: ActiveSanctionT): void { + _o.sanctionCode = this.sanctionCode(); + _o.scope = this.scope(); + _o.reasonCode = this.reasonCode(); + _o.actor = (this.actor() !== null ? this.actor()!.unpack() : null); + _o.appliedAtMs = this.appliedAtMs(); + _o.expiresAtMs = this.expiresAtMs(); +} +} + +export class ActiveSanctionT implements flatbuffers.IGeneratedObject { +constructor( + public sanctionCode: string|Uint8Array|null = null, + public scope: string|Uint8Array|null = null, + public reasonCode: string|Uint8Array|null = null, + public actor: ActorRefT|null = null, + public appliedAtMs: bigint = BigInt('0'), + public expiresAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const sanctionCode = (this.sanctionCode !== null ? builder.createString(this.sanctionCode!) : 0); + const scope = (this.scope !== null ? builder.createString(this.scope!) : 0); + const reasonCode = (this.reasonCode !== null ? builder.createString(this.reasonCode!) : 0); + const actor = (this.actor !== null ? this.actor!.pack(builder) : 0); + + ActiveSanction.startActiveSanction(builder); + ActiveSanction.addSanctionCode(builder, sanctionCode); + ActiveSanction.addScope(builder, scope); + ActiveSanction.addReasonCode(builder, reasonCode); + ActiveSanction.addActor(builder, actor); + ActiveSanction.addAppliedAtMs(builder, this.appliedAtMs); + ActiveSanction.addExpiresAtMs(builder, this.expiresAtMs); + + return ActiveSanction.endActiveSanction(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/actor-ref.ts b/ui/frontend/src/proto/galaxy/fbs/user/actor-ref.ts new file mode 100644 index 0000000..cbeabe1 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/actor-ref.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class ActorRef implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ActorRef { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsActorRef(bb:flatbuffers.ByteBuffer, obj?:ActorRef):ActorRef { + return (obj || new ActorRef()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsActorRef(bb:flatbuffers.ByteBuffer, obj?:ActorRef):ActorRef { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ActorRef()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +type():string|null +type(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +type(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +id():string|null +id(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +id(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startActorRef(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addType(builder:flatbuffers.Builder, typeOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, typeOffset, 0); +} + +static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, idOffset, 0); +} + +static endActorRef(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createActorRef(builder:flatbuffers.Builder, typeOffset:flatbuffers.Offset, idOffset:flatbuffers.Offset):flatbuffers.Offset { + ActorRef.startActorRef(builder); + ActorRef.addType(builder, typeOffset); + ActorRef.addId(builder, idOffset); + return ActorRef.endActorRef(builder); +} + +unpack(): ActorRefT { + return new ActorRefT( + this.type(), + this.id() + ); +} + + +unpackTo(_o: ActorRefT): void { + _o.type = this.type(); + _o.id = this.id(); +} +} + +export class ActorRefT implements flatbuffers.IGeneratedObject { +constructor( + public type: string|Uint8Array|null = null, + public id: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const type = (this.type !== null ? builder.createString(this.type!) : 0); + const id = (this.id !== null ? builder.createString(this.id!) : 0); + + return ActorRef.createActorRef(builder, + type, + id + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/device-session-revocation-summary-view.ts b/ui/frontend/src/proto/galaxy/fbs/user/device-session-revocation-summary-view.ts new file mode 100644 index 0000000..b4c8da7 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/device-session-revocation-summary-view.ts @@ -0,0 +1,92 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class DeviceSessionRevocationSummaryView implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):DeviceSessionRevocationSummaryView { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsDeviceSessionRevocationSummaryView(bb:flatbuffers.ByteBuffer, obj?:DeviceSessionRevocationSummaryView):DeviceSessionRevocationSummaryView { + return (obj || new DeviceSessionRevocationSummaryView()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsDeviceSessionRevocationSummaryView(bb:flatbuffers.ByteBuffer, obj?:DeviceSessionRevocationSummaryView):DeviceSessionRevocationSummaryView { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new DeviceSessionRevocationSummaryView()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +userId():string|null +userId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +userId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +revokedCount():number { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +static startDeviceSessionRevocationSummaryView(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addUserId(builder:flatbuffers.Builder, userIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, userIdOffset, 0); +} + +static addRevokedCount(builder:flatbuffers.Builder, revokedCount:number) { + builder.addFieldInt32(1, revokedCount, 0); +} + +static endDeviceSessionRevocationSummaryView(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createDeviceSessionRevocationSummaryView(builder:flatbuffers.Builder, userIdOffset:flatbuffers.Offset, revokedCount:number):flatbuffers.Offset { + DeviceSessionRevocationSummaryView.startDeviceSessionRevocationSummaryView(builder); + DeviceSessionRevocationSummaryView.addUserId(builder, userIdOffset); + DeviceSessionRevocationSummaryView.addRevokedCount(builder, revokedCount); + return DeviceSessionRevocationSummaryView.endDeviceSessionRevocationSummaryView(builder); +} + +unpack(): DeviceSessionRevocationSummaryViewT { + return new DeviceSessionRevocationSummaryViewT( + this.userId(), + this.revokedCount() + ); +} + + +unpackTo(_o: DeviceSessionRevocationSummaryViewT): void { + _o.userId = this.userId(); + _o.revokedCount = this.revokedCount(); +} +} + +export class DeviceSessionRevocationSummaryViewT implements flatbuffers.IGeneratedObject { +constructor( + public userId: string|Uint8Array|null = null, + public revokedCount: number = 0 +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const userId = (this.userId !== null ? builder.createString(this.userId!) : 0); + + return DeviceSessionRevocationSummaryView.createDeviceSessionRevocationSummaryView(builder, + userId, + this.revokedCount + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/device-session-view.ts b/ui/frontend/src/proto/galaxy/fbs/user/device-session-view.ts new file mode 100644 index 0000000..4021829 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/device-session-view.ts @@ -0,0 +1,171 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class DeviceSessionView implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):DeviceSessionView { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsDeviceSessionView(bb:flatbuffers.ByteBuffer, obj?:DeviceSessionView):DeviceSessionView { + return (obj || new DeviceSessionView()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsDeviceSessionView(bb:flatbuffers.ByteBuffer, obj?:DeviceSessionView):DeviceSessionView { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new DeviceSessionView()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +deviceSessionId():string|null +deviceSessionId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +deviceSessionId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +userId():string|null +userId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +userId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +status():string|null +status(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +status(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +clientPublicKey():string|null +clientPublicKey(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +clientPublicKey(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +createdAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +revokedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +lastSeenAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startDeviceSessionView(builder:flatbuffers.Builder) { + builder.startObject(7); +} + +static addDeviceSessionId(builder:flatbuffers.Builder, deviceSessionIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, deviceSessionIdOffset, 0); +} + +static addUserId(builder:flatbuffers.Builder, userIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, userIdOffset, 0); +} + +static addStatus(builder:flatbuffers.Builder, statusOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, statusOffset, 0); +} + +static addClientPublicKey(builder:flatbuffers.Builder, clientPublicKeyOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, clientPublicKeyOffset, 0); +} + +static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) { + builder.addFieldInt64(4, createdAtMs, BigInt('0')); +} + +static addRevokedAtMs(builder:flatbuffers.Builder, revokedAtMs:bigint) { + builder.addFieldInt64(5, revokedAtMs, BigInt('0')); +} + +static addLastSeenAtMs(builder:flatbuffers.Builder, lastSeenAtMs:bigint) { + builder.addFieldInt64(6, lastSeenAtMs, BigInt('0')); +} + +static endDeviceSessionView(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createDeviceSessionView(builder:flatbuffers.Builder, deviceSessionIdOffset:flatbuffers.Offset, userIdOffset:flatbuffers.Offset, statusOffset:flatbuffers.Offset, clientPublicKeyOffset:flatbuffers.Offset, createdAtMs:bigint, revokedAtMs:bigint, lastSeenAtMs:bigint):flatbuffers.Offset { + DeviceSessionView.startDeviceSessionView(builder); + DeviceSessionView.addDeviceSessionId(builder, deviceSessionIdOffset); + DeviceSessionView.addUserId(builder, userIdOffset); + DeviceSessionView.addStatus(builder, statusOffset); + DeviceSessionView.addClientPublicKey(builder, clientPublicKeyOffset); + DeviceSessionView.addCreatedAtMs(builder, createdAtMs); + DeviceSessionView.addRevokedAtMs(builder, revokedAtMs); + DeviceSessionView.addLastSeenAtMs(builder, lastSeenAtMs); + return DeviceSessionView.endDeviceSessionView(builder); +} + +unpack(): DeviceSessionViewT { + return new DeviceSessionViewT( + this.deviceSessionId(), + this.userId(), + this.status(), + this.clientPublicKey(), + this.createdAtMs(), + this.revokedAtMs(), + this.lastSeenAtMs() + ); +} + + +unpackTo(_o: DeviceSessionViewT): void { + _o.deviceSessionId = this.deviceSessionId(); + _o.userId = this.userId(); + _o.status = this.status(); + _o.clientPublicKey = this.clientPublicKey(); + _o.createdAtMs = this.createdAtMs(); + _o.revokedAtMs = this.revokedAtMs(); + _o.lastSeenAtMs = this.lastSeenAtMs(); +} +} + +export class DeviceSessionViewT implements flatbuffers.IGeneratedObject { +constructor( + public deviceSessionId: string|Uint8Array|null = null, + public userId: string|Uint8Array|null = null, + public status: string|Uint8Array|null = null, + public clientPublicKey: string|Uint8Array|null = null, + public createdAtMs: bigint = BigInt('0'), + public revokedAtMs: bigint = BigInt('0'), + public lastSeenAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const deviceSessionId = (this.deviceSessionId !== null ? builder.createString(this.deviceSessionId!) : 0); + const userId = (this.userId !== null ? builder.createString(this.userId!) : 0); + const status = (this.status !== null ? builder.createString(this.status!) : 0); + const clientPublicKey = (this.clientPublicKey !== null ? builder.createString(this.clientPublicKey!) : 0); + + return DeviceSessionView.createDeviceSessionView(builder, + deviceSessionId, + userId, + status, + clientPublicKey, + this.createdAtMs, + this.revokedAtMs, + this.lastSeenAtMs + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/entitlement-snapshot.ts b/ui/frontend/src/proto/galaxy/fbs/user/entitlement-snapshot.ts new file mode 100644 index 0000000..edb63bd --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/entitlement-snapshot.ts @@ -0,0 +1,173 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ActorRef, ActorRefT } from './actor-ref.js'; + + +export class EntitlementSnapshot implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):EntitlementSnapshot { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsEntitlementSnapshot(bb:flatbuffers.ByteBuffer, obj?:EntitlementSnapshot):EntitlementSnapshot { + return (obj || new EntitlementSnapshot()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsEntitlementSnapshot(bb:flatbuffers.ByteBuffer, obj?:EntitlementSnapshot):EntitlementSnapshot { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new EntitlementSnapshot()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +planCode():string|null +planCode(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +planCode(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +isPaid():boolean { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false; +} + +source():string|null +source(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +source(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +actor(obj?:ActorRef):ActorRef|null { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? (obj || new ActorRef()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +reasonCode():string|null +reasonCode(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +reasonCode(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +startsAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +endsAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +updatedAtMs():bigint { + const offset = this.bb!.__offset(this.bb_pos, 18); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +static startEntitlementSnapshot(builder:flatbuffers.Builder) { + builder.startObject(8); +} + +static addPlanCode(builder:flatbuffers.Builder, planCodeOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, planCodeOffset, 0); +} + +static addIsPaid(builder:flatbuffers.Builder, isPaid:boolean) { + builder.addFieldInt8(1, +isPaid, +false); +} + +static addSource(builder:flatbuffers.Builder, sourceOffset:flatbuffers.Offset) { + builder.addFieldOffset(2, sourceOffset, 0); +} + +static addActor(builder:flatbuffers.Builder, actorOffset:flatbuffers.Offset) { + builder.addFieldOffset(3, actorOffset, 0); +} + +static addReasonCode(builder:flatbuffers.Builder, reasonCodeOffset:flatbuffers.Offset) { + builder.addFieldOffset(4, reasonCodeOffset, 0); +} + +static addStartsAtMs(builder:flatbuffers.Builder, startsAtMs:bigint) { + builder.addFieldInt64(5, startsAtMs, BigInt('0')); +} + +static addEndsAtMs(builder:flatbuffers.Builder, endsAtMs:bigint) { + builder.addFieldInt64(6, endsAtMs, BigInt('0')); +} + +static addUpdatedAtMs(builder:flatbuffers.Builder, updatedAtMs:bigint) { + builder.addFieldInt64(7, updatedAtMs, BigInt('0')); +} + +static endEntitlementSnapshot(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + + +unpack(): EntitlementSnapshotT { + return new EntitlementSnapshotT( + this.planCode(), + this.isPaid(), + this.source(), + (this.actor() !== null ? this.actor()!.unpack() : null), + this.reasonCode(), + this.startsAtMs(), + this.endsAtMs(), + this.updatedAtMs() + ); +} + + +unpackTo(_o: EntitlementSnapshotT): void { + _o.planCode = this.planCode(); + _o.isPaid = this.isPaid(); + _o.source = this.source(); + _o.actor = (this.actor() !== null ? this.actor()!.unpack() : null); + _o.reasonCode = this.reasonCode(); + _o.startsAtMs = this.startsAtMs(); + _o.endsAtMs = this.endsAtMs(); + _o.updatedAtMs = this.updatedAtMs(); +} +} + +export class EntitlementSnapshotT implements flatbuffers.IGeneratedObject { +constructor( + public planCode: string|Uint8Array|null = null, + public isPaid: boolean = false, + public source: string|Uint8Array|null = null, + public actor: ActorRefT|null = null, + public reasonCode: string|Uint8Array|null = null, + public startsAtMs: bigint = BigInt('0'), + public endsAtMs: bigint = BigInt('0'), + public updatedAtMs: bigint = BigInt('0') +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const planCode = (this.planCode !== null ? builder.createString(this.planCode!) : 0); + const source = (this.source !== null ? builder.createString(this.source!) : 0); + const actor = (this.actor !== null ? this.actor!.pack(builder) : 0); + const reasonCode = (this.reasonCode !== null ? builder.createString(this.reasonCode!) : 0); + + EntitlementSnapshot.startEntitlementSnapshot(builder); + EntitlementSnapshot.addPlanCode(builder, planCode); + EntitlementSnapshot.addIsPaid(builder, this.isPaid); + EntitlementSnapshot.addSource(builder, source); + EntitlementSnapshot.addActor(builder, actor); + EntitlementSnapshot.addReasonCode(builder, reasonCode); + EntitlementSnapshot.addStartsAtMs(builder, this.startsAtMs); + EntitlementSnapshot.addEndsAtMs(builder, this.endsAtMs); + EntitlementSnapshot.addUpdatedAtMs(builder, this.updatedAtMs); + + return EntitlementSnapshot.endEntitlementSnapshot(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/error-body.ts b/ui/frontend/src/proto/galaxy/fbs/user/error-body.ts new file mode 100644 index 0000000..7cab4d9 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/error-body.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class ErrorBody implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ErrorBody { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsErrorBody(bb:flatbuffers.ByteBuffer, obj?:ErrorBody):ErrorBody { + return (obj || new ErrorBody()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsErrorBody(bb:flatbuffers.ByteBuffer, obj?:ErrorBody):ErrorBody { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ErrorBody()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +code():string|null +code(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +code(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +message():string|null +message(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +message(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startErrorBody(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addCode(builder:flatbuffers.Builder, codeOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, codeOffset, 0); +} + +static addMessage(builder:flatbuffers.Builder, messageOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, messageOffset, 0); +} + +static endErrorBody(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createErrorBody(builder:flatbuffers.Builder, codeOffset:flatbuffers.Offset, messageOffset:flatbuffers.Offset):flatbuffers.Offset { + ErrorBody.startErrorBody(builder); + ErrorBody.addCode(builder, codeOffset); + ErrorBody.addMessage(builder, messageOffset); + return ErrorBody.endErrorBody(builder); +} + +unpack(): ErrorBodyT { + return new ErrorBodyT( + this.code(), + this.message() + ); +} + + +unpackTo(_o: ErrorBodyT): void { + _o.code = this.code(); + _o.message = this.message(); +} +} + +export class ErrorBodyT implements flatbuffers.IGeneratedObject { +constructor( + public code: string|Uint8Array|null = null, + public message: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const code = (this.code !== null ? builder.createString(this.code!) : 0); + const message = (this.message !== null ? builder.createString(this.message!) : 0); + + return ErrorBody.createErrorBody(builder, + code, + message + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/error-response.ts b/ui/frontend/src/proto/galaxy/fbs/user/error-response.ts new file mode 100644 index 0000000..5e99abb --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/error-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { ErrorBody, ErrorBodyT } from './error-body.js'; + + +export class ErrorResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ErrorResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsErrorResponse(bb:flatbuffers.ByteBuffer, obj?:ErrorResponse):ErrorResponse { + return (obj || new ErrorResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsErrorResponse(bb:flatbuffers.ByteBuffer, obj?:ErrorResponse):ErrorResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ErrorResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +error(obj?:ErrorBody):ErrorBody|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new ErrorBody()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startErrorResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addError(builder:flatbuffers.Builder, errorOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, errorOffset, 0); +} + +static endErrorResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createErrorResponse(builder:flatbuffers.Builder, errorOffset:flatbuffers.Offset):flatbuffers.Offset { + ErrorResponse.startErrorResponse(builder); + ErrorResponse.addError(builder, errorOffset); + return ErrorResponse.endErrorResponse(builder); +} + +unpack(): ErrorResponseT { + return new ErrorResponseT( + (this.error() !== null ? this.error()!.unpack() : null) + ); +} + + +unpackTo(_o: ErrorResponseT): void { + _o.error = (this.error() !== null ? this.error()!.unpack() : null); +} +} + +export class ErrorResponseT implements flatbuffers.IGeneratedObject { +constructor( + public error: ErrorBodyT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const error = (this.error !== null ? this.error!.pack(builder) : 0); + + return ErrorResponse.createErrorResponse(builder, + error + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/get-my-account-request.ts b/ui/frontend/src/proto/galaxy/fbs/user/get-my-account-request.ts new file mode 100644 index 0000000..4795105 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/get-my-account-request.ts @@ -0,0 +1,56 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class GetMyAccountRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):GetMyAccountRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsGetMyAccountRequest(bb:flatbuffers.ByteBuffer, obj?:GetMyAccountRequest):GetMyAccountRequest { + return (obj || new GetMyAccountRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsGetMyAccountRequest(bb:flatbuffers.ByteBuffer, obj?:GetMyAccountRequest):GetMyAccountRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new GetMyAccountRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static startGetMyAccountRequest(builder:flatbuffers.Builder) { + builder.startObject(0); +} + +static endGetMyAccountRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createGetMyAccountRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + GetMyAccountRequest.startGetMyAccountRequest(builder); + return GetMyAccountRequest.endGetMyAccountRequest(builder); +} + +unpack(): GetMyAccountRequestT { + return new GetMyAccountRequestT(); +} + + +unpackTo(_o: GetMyAccountRequestT): void {} +} + +export class GetMyAccountRequestT implements flatbuffers.IGeneratedObject { +constructor(){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return GetMyAccountRequest.createGetMyAccountRequest(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-request.ts b/ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-request.ts new file mode 100644 index 0000000..5f7b098 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-request.ts @@ -0,0 +1,56 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class ListMySessionsRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ListMySessionsRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsListMySessionsRequest(bb:flatbuffers.ByteBuffer, obj?:ListMySessionsRequest):ListMySessionsRequest { + return (obj || new ListMySessionsRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsListMySessionsRequest(bb:flatbuffers.ByteBuffer, obj?:ListMySessionsRequest):ListMySessionsRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ListMySessionsRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static startListMySessionsRequest(builder:flatbuffers.Builder) { + builder.startObject(0); +} + +static endListMySessionsRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createListMySessionsRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + ListMySessionsRequest.startListMySessionsRequest(builder); + return ListMySessionsRequest.endListMySessionsRequest(builder); +} + +unpack(): ListMySessionsRequestT { + return new ListMySessionsRequestT(); +} + + +unpackTo(_o: ListMySessionsRequestT): void {} +} + +export class ListMySessionsRequestT implements flatbuffers.IGeneratedObject { +constructor(){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return ListMySessionsRequest.createListMySessionsRequest(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-response.ts b/ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-response.ts new file mode 100644 index 0000000..b274719 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/list-my-sessions-response.ts @@ -0,0 +1,94 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { DeviceSessionView, DeviceSessionViewT } from './device-session-view.js'; + + +export class ListMySessionsResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ListMySessionsResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsListMySessionsResponse(bb:flatbuffers.ByteBuffer, obj?:ListMySessionsResponse):ListMySessionsResponse { + return (obj || new ListMySessionsResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsListMySessionsResponse(bb:flatbuffers.ByteBuffer, obj?:ListMySessionsResponse):ListMySessionsResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ListMySessionsResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +items(index: number, obj?:DeviceSessionView):DeviceSessionView|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new DeviceSessionView()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null; +} + +itemsLength():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0; +} + +static startListMySessionsResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addItems(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, itemsOffset, 0); +} + +static createItemsVector(builder:flatbuffers.Builder, data:flatbuffers.Offset[]):flatbuffers.Offset { + builder.startVector(4, data.length, 4); + for (let i = data.length - 1; i >= 0; i--) { + builder.addOffset(data[i]!); + } + return builder.endVector(); +} + +static startItemsVector(builder:flatbuffers.Builder, numElems:number) { + builder.startVector(4, numElems, 4); +} + +static endListMySessionsResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createListMySessionsResponse(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset):flatbuffers.Offset { + ListMySessionsResponse.startListMySessionsResponse(builder); + ListMySessionsResponse.addItems(builder, itemsOffset); + return ListMySessionsResponse.endListMySessionsResponse(builder); +} + +unpack(): ListMySessionsResponseT { + return new ListMySessionsResponseT( + this.bb!.createObjList(this.items.bind(this), this.itemsLength()) + ); +} + + +unpackTo(_o: ListMySessionsResponseT): void { + _o.items = this.bb!.createObjList(this.items.bind(this), this.itemsLength()); +} +} + +export class ListMySessionsResponseT implements flatbuffers.IGeneratedObject { +constructor( + public items: (DeviceSessionViewT)[] = [] +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const items = ListMySessionsResponse.createItemsVector(builder, builder.createObjectOffsetList(this.items)); + + return ListMySessionsResponse.createListMySessionsResponse(builder, + items + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-request.ts b/ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-request.ts new file mode 100644 index 0000000..3a5a5f6 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-request.ts @@ -0,0 +1,56 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class RevokeAllMySessionsRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):RevokeAllMySessionsRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsRevokeAllMySessionsRequest(bb:flatbuffers.ByteBuffer, obj?:RevokeAllMySessionsRequest):RevokeAllMySessionsRequest { + return (obj || new RevokeAllMySessionsRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsRevokeAllMySessionsRequest(bb:flatbuffers.ByteBuffer, obj?:RevokeAllMySessionsRequest):RevokeAllMySessionsRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new RevokeAllMySessionsRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static startRevokeAllMySessionsRequest(builder:flatbuffers.Builder) { + builder.startObject(0); +} + +static endRevokeAllMySessionsRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createRevokeAllMySessionsRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + RevokeAllMySessionsRequest.startRevokeAllMySessionsRequest(builder); + return RevokeAllMySessionsRequest.endRevokeAllMySessionsRequest(builder); +} + +unpack(): RevokeAllMySessionsRequestT { + return new RevokeAllMySessionsRequestT(); +} + + +unpackTo(_o: RevokeAllMySessionsRequestT): void {} +} + +export class RevokeAllMySessionsRequestT implements flatbuffers.IGeneratedObject { +constructor(){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return RevokeAllMySessionsRequest.createRevokeAllMySessionsRequest(builder); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-response.ts b/ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-response.ts new file mode 100644 index 0000000..ce5bf84 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/revoke-all-my-sessions-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { DeviceSessionRevocationSummaryView, DeviceSessionRevocationSummaryViewT } from './device-session-revocation-summary-view.js'; + + +export class RevokeAllMySessionsResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):RevokeAllMySessionsResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsRevokeAllMySessionsResponse(bb:flatbuffers.ByteBuffer, obj?:RevokeAllMySessionsResponse):RevokeAllMySessionsResponse { + return (obj || new RevokeAllMySessionsResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsRevokeAllMySessionsResponse(bb:flatbuffers.ByteBuffer, obj?:RevokeAllMySessionsResponse):RevokeAllMySessionsResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new RevokeAllMySessionsResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +summary(obj?:DeviceSessionRevocationSummaryView):DeviceSessionRevocationSummaryView|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new DeviceSessionRevocationSummaryView()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startRevokeAllMySessionsResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addSummary(builder:flatbuffers.Builder, summaryOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, summaryOffset, 0); +} + +static endRevokeAllMySessionsResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createRevokeAllMySessionsResponse(builder:flatbuffers.Builder, summaryOffset:flatbuffers.Offset):flatbuffers.Offset { + RevokeAllMySessionsResponse.startRevokeAllMySessionsResponse(builder); + RevokeAllMySessionsResponse.addSummary(builder, summaryOffset); + return RevokeAllMySessionsResponse.endRevokeAllMySessionsResponse(builder); +} + +unpack(): RevokeAllMySessionsResponseT { + return new RevokeAllMySessionsResponseT( + (this.summary() !== null ? this.summary()!.unpack() : null) + ); +} + + +unpackTo(_o: RevokeAllMySessionsResponseT): void { + _o.summary = (this.summary() !== null ? this.summary()!.unpack() : null); +} +} + +export class RevokeAllMySessionsResponseT implements flatbuffers.IGeneratedObject { +constructor( + public summary: DeviceSessionRevocationSummaryViewT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const summary = (this.summary !== null ? this.summary!.pack(builder) : 0); + + return RevokeAllMySessionsResponse.createRevokeAllMySessionsResponse(builder, + summary + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-request.ts b/ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-request.ts new file mode 100644 index 0000000..1281fcc --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-request.ts @@ -0,0 +1,78 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class RevokeMySessionRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):RevokeMySessionRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsRevokeMySessionRequest(bb:flatbuffers.ByteBuffer, obj?:RevokeMySessionRequest):RevokeMySessionRequest { + return (obj || new RevokeMySessionRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsRevokeMySessionRequest(bb:flatbuffers.ByteBuffer, obj?:RevokeMySessionRequest):RevokeMySessionRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new RevokeMySessionRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +deviceSessionId():string|null +deviceSessionId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +deviceSessionId(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startRevokeMySessionRequest(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addDeviceSessionId(builder:flatbuffers.Builder, deviceSessionIdOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, deviceSessionIdOffset, 0); +} + +static endRevokeMySessionRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createRevokeMySessionRequest(builder:flatbuffers.Builder, deviceSessionIdOffset:flatbuffers.Offset):flatbuffers.Offset { + RevokeMySessionRequest.startRevokeMySessionRequest(builder); + RevokeMySessionRequest.addDeviceSessionId(builder, deviceSessionIdOffset); + return RevokeMySessionRequest.endRevokeMySessionRequest(builder); +} + +unpack(): RevokeMySessionRequestT { + return new RevokeMySessionRequestT( + this.deviceSessionId() + ); +} + + +unpackTo(_o: RevokeMySessionRequestT): void { + _o.deviceSessionId = this.deviceSessionId(); +} +} + +export class RevokeMySessionRequestT implements flatbuffers.IGeneratedObject { +constructor( + public deviceSessionId: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const deviceSessionId = (this.deviceSessionId !== null ? builder.createString(this.deviceSessionId!) : 0); + + return RevokeMySessionRequest.createRevokeMySessionRequest(builder, + deviceSessionId + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-response.ts b/ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-response.ts new file mode 100644 index 0000000..25dc653 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/revoke-my-session-response.ts @@ -0,0 +1,77 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { DeviceSessionView, DeviceSessionViewT } from './device-session-view.js'; + + +export class RevokeMySessionResponse implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):RevokeMySessionResponse { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsRevokeMySessionResponse(bb:flatbuffers.ByteBuffer, obj?:RevokeMySessionResponse):RevokeMySessionResponse { + return (obj || new RevokeMySessionResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsRevokeMySessionResponse(bb:flatbuffers.ByteBuffer, obj?:RevokeMySessionResponse):RevokeMySessionResponse { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new RevokeMySessionResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +session(obj?:DeviceSessionView):DeviceSessionView|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? (obj || new DeviceSessionView()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; +} + +static startRevokeMySessionResponse(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addSession(builder:flatbuffers.Builder, sessionOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, sessionOffset, 0); +} + +static endRevokeMySessionResponse(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createRevokeMySessionResponse(builder:flatbuffers.Builder, sessionOffset:flatbuffers.Offset):flatbuffers.Offset { + RevokeMySessionResponse.startRevokeMySessionResponse(builder); + RevokeMySessionResponse.addSession(builder, sessionOffset); + return RevokeMySessionResponse.endRevokeMySessionResponse(builder); +} + +unpack(): RevokeMySessionResponseT { + return new RevokeMySessionResponseT( + (this.session() !== null ? this.session()!.unpack() : null) + ); +} + + +unpackTo(_o: RevokeMySessionResponseT): void { + _o.session = (this.session() !== null ? this.session()!.unpack() : null); +} +} + +export class RevokeMySessionResponseT implements flatbuffers.IGeneratedObject { +constructor( + public session: DeviceSessionViewT|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const session = (this.session !== null ? this.session!.pack(builder) : 0); + + return RevokeMySessionResponse.createRevokeMySessionResponse(builder, + session + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/update-my-profile-request.ts b/ui/frontend/src/proto/galaxy/fbs/user/update-my-profile-request.ts new file mode 100644 index 0000000..284f7e7 --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/update-my-profile-request.ts @@ -0,0 +1,78 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class UpdateMyProfileRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):UpdateMyProfileRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsUpdateMyProfileRequest(bb:flatbuffers.ByteBuffer, obj?:UpdateMyProfileRequest):UpdateMyProfileRequest { + return (obj || new UpdateMyProfileRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsUpdateMyProfileRequest(bb:flatbuffers.ByteBuffer, obj?:UpdateMyProfileRequest):UpdateMyProfileRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new UpdateMyProfileRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +displayName():string|null +displayName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +displayName(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startUpdateMyProfileRequest(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addDisplayName(builder:flatbuffers.Builder, displayNameOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, displayNameOffset, 0); +} + +static endUpdateMyProfileRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createUpdateMyProfileRequest(builder:flatbuffers.Builder, displayNameOffset:flatbuffers.Offset):flatbuffers.Offset { + UpdateMyProfileRequest.startUpdateMyProfileRequest(builder); + UpdateMyProfileRequest.addDisplayName(builder, displayNameOffset); + return UpdateMyProfileRequest.endUpdateMyProfileRequest(builder); +} + +unpack(): UpdateMyProfileRequestT { + return new UpdateMyProfileRequestT( + this.displayName() + ); +} + + +unpackTo(_o: UpdateMyProfileRequestT): void { + _o.displayName = this.displayName(); +} +} + +export class UpdateMyProfileRequestT implements flatbuffers.IGeneratedObject { +constructor( + public displayName: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const displayName = (this.displayName !== null ? builder.createString(this.displayName!) : 0); + + return UpdateMyProfileRequest.createUpdateMyProfileRequest(builder, + displayName + ); +} +} diff --git a/ui/frontend/src/proto/galaxy/fbs/user/update-my-settings-request.ts b/ui/frontend/src/proto/galaxy/fbs/user/update-my-settings-request.ts new file mode 100644 index 0000000..66ab75a --- /dev/null +++ b/ui/frontend/src/proto/galaxy/fbs/user/update-my-settings-request.ts @@ -0,0 +1,95 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + + + +export class UpdateMySettingsRequest implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):UpdateMySettingsRequest { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsUpdateMySettingsRequest(bb:flatbuffers.ByteBuffer, obj?:UpdateMySettingsRequest):UpdateMySettingsRequest { + return (obj || new UpdateMySettingsRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsUpdateMySettingsRequest(bb:flatbuffers.ByteBuffer, obj?:UpdateMySettingsRequest):UpdateMySettingsRequest { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new UpdateMySettingsRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +preferredLanguage():string|null +preferredLanguage(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +preferredLanguage(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +timeZone():string|null +timeZone(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null +timeZone(optionalEncoding?:any):string|Uint8Array|null { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; +} + +static startUpdateMySettingsRequest(builder:flatbuffers.Builder) { + builder.startObject(2); +} + +static addPreferredLanguage(builder:flatbuffers.Builder, preferredLanguageOffset:flatbuffers.Offset) { + builder.addFieldOffset(0, preferredLanguageOffset, 0); +} + +static addTimeZone(builder:flatbuffers.Builder, timeZoneOffset:flatbuffers.Offset) { + builder.addFieldOffset(1, timeZoneOffset, 0); +} + +static endUpdateMySettingsRequest(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createUpdateMySettingsRequest(builder:flatbuffers.Builder, preferredLanguageOffset:flatbuffers.Offset, timeZoneOffset:flatbuffers.Offset):flatbuffers.Offset { + UpdateMySettingsRequest.startUpdateMySettingsRequest(builder); + UpdateMySettingsRequest.addPreferredLanguage(builder, preferredLanguageOffset); + UpdateMySettingsRequest.addTimeZone(builder, timeZoneOffset); + return UpdateMySettingsRequest.endUpdateMySettingsRequest(builder); +} + +unpack(): UpdateMySettingsRequestT { + return new UpdateMySettingsRequestT( + this.preferredLanguage(), + this.timeZone() + ); +} + + +unpackTo(_o: UpdateMySettingsRequestT): void { + _o.preferredLanguage = this.preferredLanguage(); + _o.timeZone = this.timeZone(); +} +} + +export class UpdateMySettingsRequestT implements flatbuffers.IGeneratedObject { +constructor( + public preferredLanguage: string|Uint8Array|null = null, + public timeZone: string|Uint8Array|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + const preferredLanguage = (this.preferredLanguage !== null ? builder.createString(this.preferredLanguage!) : 0); + const timeZone = (this.timeZone !== null ? builder.createString(this.timeZone!) : 0); + + return UpdateMySettingsRequest.createUpdateMySettingsRequest(builder, + preferredLanguage, + timeZone + ); +} +} diff --git a/ui/frontend/src/routes/lobby/+page.svelte b/ui/frontend/src/routes/lobby/+page.svelte index 690b521..8abe7af 100644 --- a/ui/frontend/src/routes/lobby/+page.svelte +++ b/ui/frontend/src/routes/lobby/+page.svelte @@ -1,45 +1,206 @@
-

{i18n.t("lobby.title")}

-

- {i18n.t("lobby.device_session_id_label")}: - {session.deviceSessionId ?? ""} -

- {#if accountLoading} -

{i18n.t("lobby.account_loading")}

- {:else if displayName !== null} -

- {i18n.t("lobby.greeting", { name: displayName })} +

+

{i18n.t("lobby.title")}

+

+ {i18n.t("lobby.device_session_id_label")}: + {session.deviceSessionId ?? ""}

- {:else if accountError !== null} -

{accountError}

+ {#if displayName !== null} +

+ {i18n.t("lobby.greeting", { name: displayName })} +

+ {/if} + +
+ + {#if configError !== null} +

{configError}

+ {:else if lobbyError !== null} +

{lobbyError}

{/if} - + +
+ +
+ +
+

{i18n.t("lobby.section.my_games")}

+ {#if listsLoading} +

{i18n.t("lobby.list_loading")}

+ {:else if myGames.length === 0} +

{i18n.t("lobby.my_games.empty")}

+ {:else} +
    + {#each myGames as game (game.gameId)} +
  • + +
  • + {/each} +
+ {/if} +
+ +
+

{i18n.t("lobby.section.invitations")}

+ {#if listsLoading} +

{i18n.t("lobby.list_loading")}

+ {:else if invitations.length === 0} +

{i18n.t("lobby.invitations.empty")}

+ {:else} +
    + {#each invitations as invite (invite.inviteId)} +
  • + {invite.raceName} + {invite.gameId} +
    + + +
    +
  • + {/each} +
+ {/if} +
+ +
+

{i18n.t("lobby.section.applications")}

+ {#if listsLoading} +

{i18n.t("lobby.list_loading")}

+ {:else if applications.length === 0} +

{i18n.t("lobby.applications.empty")}

+ {:else} +
    + {#each applications as app (app.applicationId)} +
  • + {app.raceName} + {app.gameId} + + {applicationStatusLabel(app.status)} + +
  • + {/each} +
+ {/if} +
+ +
+

{i18n.t("lobby.section.public_games")}

+ {#if listsLoading} +

{i18n.t("lobby.list_loading")}

+ {:else if publicGames.length === 0} +

{i18n.t("lobby.public_games.empty")}

+ {:else} +
    + {#each publicGames as game (game.gameId)} +
  • + {game.gameName} + {game.status} + {game.minPlayers}–{game.maxPlayers} players + {#if openApplicationFor === game.gameId} +
    { + event.preventDefault(); + submitApplicationFor(game.gameId); + }} + data-testid="lobby-application-form" + > + + {#if raceNameError !== null} +

    + {raceNameError} +

    + {/if} +
    + + +
    +
    + {:else} + + {/if} +
  • + {/each} +
+ {/if} +
diff --git a/ui/frontend/src/routes/lobby/create/+page.svelte b/ui/frontend/src/routes/lobby/create/+page.svelte new file mode 100644 index 0000000..9c131a0 --- /dev/null +++ b/ui/frontend/src/routes/lobby/create/+page.svelte @@ -0,0 +1,292 @@ + + +
+

{i18n.t("lobby.create.title")}

+ {#if configError !== null} +

{configError}

+ {/if} +
{ + event.preventDefault(); + submit(); + }} + data-testid="lobby-create-form" + > + + + + +
+ {i18n.t("lobby.create.advanced")} + + + + + +
+ {#if formError !== null} +

{formError}

+ {/if} +
+ + +
+
+
+ + diff --git a/ui/frontend/src/routes/lobby/create/+page.ts b/ui/frontend/src/routes/lobby/create/+page.ts new file mode 100644 index 0000000..83addb7 --- /dev/null +++ b/ui/frontend/src/routes/lobby/create/+page.ts @@ -0,0 +1,2 @@ +export const ssr = false; +export const prerender = false; diff --git a/ui/frontend/tests/e2e/auth-flow.spec.ts b/ui/frontend/tests/e2e/auth-flow.spec.ts index ad3831b..94c8b89 100644 --- a/ui/frontend/tests/e2e/auth-flow.spec.ts +++ b/ui/frontend/tests/e2e/auth-flow.spec.ts @@ -16,6 +16,13 @@ import { fromJson, type JsonValue } from "@bufbuild/protobuf"; import { expect, test, type Page } from "@playwright/test"; import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb"; import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response"; +import { + buildAccountResponsePayload, + buildMyApplicationsListPayload, + buildMyGamesListPayload, + buildMyInvitesListPayload, + buildPublicGamesListPayload, +} from "./fixtures/lobby-fbs"; interface MockSetup { pendingSubscribes: Array<() => void>; @@ -58,19 +65,36 @@ async function mockGatewayHappyPath( ExecuteCommandRequestSchema, JSON.parse(reqText) as JsonValue, ); - const accountJson = JSON.stringify({ - account: { - user_id: "user-1", - email: "pilot@example.com", - user_name: "player-test", - display_name: displayName, - }, - }); + let payload: Uint8Array; + switch (req.messageType) { + case "user.account.get": + payload = buildAccountResponsePayload({ + userId: "user-1", + email: "pilot@example.com", + userName: "player-test", + displayName, + }); + break; + case "lobby.my.games.list": + payload = buildMyGamesListPayload([]); + break; + case "lobby.public.games.list": + payload = buildPublicGamesListPayload([]); + break; + case "lobby.my.invites.list": + payload = buildMyInvitesListPayload([]); + break; + case "lobby.my.applications.list": + payload = buildMyApplicationsListPayload([]); + break; + default: + payload = new Uint8Array(); + } const responseJson = await forgeExecuteCommandResponseJson({ requestId: req.requestId, timestampMs: BigInt(Date.now()), resultCode: "ok", - payloadBytes: new TextEncoder().encode(accountJson), + payloadBytes: payload, }); await route.fulfill({ status: 200, @@ -122,9 +146,14 @@ async function mockGatewayHappyPath( async function completeLogin(page: Page): Promise { await page.goto("/"); await expect(page).toHaveURL(/\/login$/); + // Inputs render `readonly` initially as a Safari autofill-suppression + // workaround; the attribute drops on first focus. Click first so the + // onfocus handler runs before fill checks editability. + await page.getByTestId("login-email-input").click(); await page.getByTestId("login-email-input").fill("pilot@example.com"); await page.getByTestId("login-email-submit").click(); await expect(page.getByTestId("login-code-input")).toBeVisible(); + await page.getByTestId("login-code-input").click(); await page.getByTestId("login-code-input").fill("123456"); await page.getByTestId("login-code-submit").click(); await expect(page).toHaveURL(/\/lobby$/); @@ -213,6 +242,7 @@ test.describe("Phase 7 — auth flow", () => { "отправить код", ); + await page.getByTestId("login-email-input").click(); await page.getByTestId("login-email-input").fill("pilot@example.com"); await page.getByTestId("login-email-submit").click(); await expect(page.getByTestId("login-code-input")).toBeVisible(); diff --git a/ui/frontend/tests/e2e/fixtures/lobby-fbs.ts b/ui/frontend/tests/e2e/fixtures/lobby-fbs.ts new file mode 100644 index 0000000..692f0b1 --- /dev/null +++ b/ui/frontend/tests/e2e/fixtures/lobby-fbs.ts @@ -0,0 +1,258 @@ +// Helpers that build FlatBuffers payloads for the lobby Playwright +// suite. Mirrors what `pkg/transcoder/lobby.go` produces in production, +// so the forged response goes through the same TS decoder the lobby +// page uses. + +import { Builder } from "flatbuffers"; + +import { + AccountResponse, + AccountView, + EntitlementSnapshot, +} from "../../../src/proto/galaxy/fbs/user"; +import { + ApplicationSubmitResponse, + ApplicationSummary, + GameCreateResponse, + GameSummary, + InviteDeclineResponse, + InviteRedeemResponse, + InviteSummary, + MyApplicationsListResponse, + MyGamesListResponse, + MyInvitesListResponse, + PublicGamesListResponse, +} from "../../../src/proto/galaxy/fbs/lobby"; + +export interface GameFixture { + gameId: string; + gameName: string; + gameType: string; + status: string; + ownerUserId?: string; + minPlayers?: number; + maxPlayers?: number; + enrollmentEndsAtMs?: bigint; + createdAtMs?: bigint; + updatedAtMs?: bigint; +} + +export interface ApplicationFixture { + applicationId: string; + gameId: string; + applicantUserId: string; + raceName: string; + status: string; + createdAtMs?: bigint; + decidedAtMs?: bigint; +} + +export interface InviteFixture { + inviteId: string; + gameId: string; + inviterUserId: string; + invitedUserId?: string; + code?: string; + raceName: string; + status: string; + createdAtMs?: bigint; + expiresAtMs?: bigint; + decidedAtMs?: bigint; +} + +const DEFAULT_TIME_MS = 1_780_000_000_000n; + +function encodeGame(builder: Builder, game: GameFixture): number { + const gameId = builder.createString(game.gameId); + const gameName = builder.createString(game.gameName); + const gameType = builder.createString(game.gameType); + const status = builder.createString(game.status); + const ownerUserId = builder.createString(game.ownerUserId ?? ""); + GameSummary.startGameSummary(builder); + GameSummary.addGameId(builder, gameId); + GameSummary.addGameName(builder, gameName); + GameSummary.addGameType(builder, gameType); + GameSummary.addStatus(builder, status); + GameSummary.addOwnerUserId(builder, ownerUserId); + GameSummary.addMinPlayers(builder, game.minPlayers ?? 2); + GameSummary.addMaxPlayers(builder, game.maxPlayers ?? 8); + GameSummary.addEnrollmentEndsAtMs(builder, game.enrollmentEndsAtMs ?? DEFAULT_TIME_MS); + GameSummary.addCreatedAtMs(builder, game.createdAtMs ?? DEFAULT_TIME_MS); + GameSummary.addUpdatedAtMs(builder, game.updatedAtMs ?? DEFAULT_TIME_MS); + return GameSummary.endGameSummary(builder); +} + +function encodeApplication(builder: Builder, app: ApplicationFixture): number { + const applicationId = builder.createString(app.applicationId); + const gameId = builder.createString(app.gameId); + const applicantUserId = builder.createString(app.applicantUserId); + const raceName = builder.createString(app.raceName); + const status = builder.createString(app.status); + ApplicationSummary.startApplicationSummary(builder); + ApplicationSummary.addApplicationId(builder, applicationId); + ApplicationSummary.addGameId(builder, gameId); + ApplicationSummary.addApplicantUserId(builder, applicantUserId); + ApplicationSummary.addRaceName(builder, raceName); + ApplicationSummary.addStatus(builder, status); + ApplicationSummary.addCreatedAtMs(builder, app.createdAtMs ?? DEFAULT_TIME_MS); + ApplicationSummary.addDecidedAtMs(builder, app.decidedAtMs ?? 0n); + return ApplicationSummary.endApplicationSummary(builder); +} + +function encodeInvite(builder: Builder, invite: InviteFixture): number { + const inviteId = builder.createString(invite.inviteId); + const gameId = builder.createString(invite.gameId); + const inviterUserId = builder.createString(invite.inviterUserId); + const invitedUserId = builder.createString(invite.invitedUserId ?? ""); + const code = builder.createString(invite.code ?? ""); + const raceName = builder.createString(invite.raceName); + const status = builder.createString(invite.status); + InviteSummary.startInviteSummary(builder); + InviteSummary.addInviteId(builder, inviteId); + InviteSummary.addGameId(builder, gameId); + InviteSummary.addInviterUserId(builder, inviterUserId); + InviteSummary.addInvitedUserId(builder, invitedUserId); + InviteSummary.addCode(builder, code); + InviteSummary.addRaceName(builder, raceName); + InviteSummary.addStatus(builder, status); + InviteSummary.addCreatedAtMs(builder, invite.createdAtMs ?? DEFAULT_TIME_MS); + InviteSummary.addExpiresAtMs(builder, invite.expiresAtMs ?? DEFAULT_TIME_MS); + InviteSummary.addDecidedAtMs(builder, invite.decidedAtMs ?? 0n); + return InviteSummary.endInviteSummary(builder); +} + +export function buildMyGamesListPayload(games: GameFixture[]): Uint8Array { + const builder = new Builder(256); + const offsets = games.map((g) => encodeGame(builder, g)); + const items = MyGamesListResponse.createItemsVector(builder, offsets); + MyGamesListResponse.startMyGamesListResponse(builder); + MyGamesListResponse.addItems(builder, items); + builder.finish(MyGamesListResponse.endMyGamesListResponse(builder)); + return builder.asUint8Array(); +} + +export function buildPublicGamesListPayload( + games: GameFixture[], + page = 1, + pageSize = 50, +): Uint8Array { + const builder = new Builder(256); + const offsets = games.map((g) => encodeGame(builder, g)); + const items = PublicGamesListResponse.createItemsVector(builder, offsets); + PublicGamesListResponse.startPublicGamesListResponse(builder); + PublicGamesListResponse.addItems(builder, items); + PublicGamesListResponse.addPage(builder, page); + PublicGamesListResponse.addPageSize(builder, pageSize); + PublicGamesListResponse.addTotal(builder, games.length); + builder.finish(PublicGamesListResponse.endPublicGamesListResponse(builder)); + return builder.asUint8Array(); +} + +export function buildMyApplicationsListPayload( + applications: ApplicationFixture[], +): Uint8Array { + const builder = new Builder(256); + const offsets = applications.map((a) => encodeApplication(builder, a)); + const items = MyApplicationsListResponse.createItemsVector(builder, offsets); + MyApplicationsListResponse.startMyApplicationsListResponse(builder); + MyApplicationsListResponse.addItems(builder, items); + builder.finish(MyApplicationsListResponse.endMyApplicationsListResponse(builder)); + return builder.asUint8Array(); +} + +export function buildMyInvitesListPayload(invites: InviteFixture[]): Uint8Array { + const builder = new Builder(256); + const offsets = invites.map((i) => encodeInvite(builder, i)); + const items = MyInvitesListResponse.createItemsVector(builder, offsets); + MyInvitesListResponse.startMyInvitesListResponse(builder); + MyInvitesListResponse.addItems(builder, items); + builder.finish(MyInvitesListResponse.endMyInvitesListResponse(builder)); + return builder.asUint8Array(); +} + +export function buildGameCreateResponsePayload(game: GameFixture): Uint8Array { + const builder = new Builder(256); + const summary = encodeGame(builder, game); + GameCreateResponse.startGameCreateResponse(builder); + GameCreateResponse.addGame(builder, summary); + builder.finish(GameCreateResponse.endGameCreateResponse(builder)); + return builder.asUint8Array(); +} + +export function buildApplicationSubmitResponsePayload( + application: ApplicationFixture, +): Uint8Array { + const builder = new Builder(128); + const app = encodeApplication(builder, application); + ApplicationSubmitResponse.startApplicationSubmitResponse(builder); + ApplicationSubmitResponse.addApplication(builder, app); + builder.finish(ApplicationSubmitResponse.endApplicationSubmitResponse(builder)); + return builder.asUint8Array(); +} + +export function buildInviteRedeemResponsePayload(invite: InviteFixture): Uint8Array { + const builder = new Builder(128); + const summary = encodeInvite(builder, invite); + InviteRedeemResponse.startInviteRedeemResponse(builder); + InviteRedeemResponse.addInvite(builder, summary); + builder.finish(InviteRedeemResponse.endInviteRedeemResponse(builder)); + return builder.asUint8Array(); +} + +export function buildInviteDeclineResponsePayload(invite: InviteFixture): Uint8Array { + const builder = new Builder(128); + const summary = encodeInvite(builder, invite); + InviteDeclineResponse.startInviteDeclineResponse(builder); + InviteDeclineResponse.addInvite(builder, summary); + builder.finish(InviteDeclineResponse.endInviteDeclineResponse(builder)); + return builder.asUint8Array(); +} + +export interface AccountFixture { + userId: string; + email: string; + userName: string; + displayName: string; +} + +export function buildAccountResponsePayload(account: AccountFixture): Uint8Array { + const builder = new Builder(256); + + const planCode = builder.createString("free"); + const source = builder.createString("internal"); + const reasonCode = builder.createString(""); + EntitlementSnapshot.startEntitlementSnapshot(builder); + EntitlementSnapshot.addPlanCode(builder, planCode); + EntitlementSnapshot.addIsPaid(builder, false); + EntitlementSnapshot.addSource(builder, source); + EntitlementSnapshot.addReasonCode(builder, reasonCode); + EntitlementSnapshot.addStartsAtMs(builder, 0n); + EntitlementSnapshot.addEndsAtMs(builder, 0n); + EntitlementSnapshot.addUpdatedAtMs(builder, 0n); + const entitlement = EntitlementSnapshot.endEntitlementSnapshot(builder); + + const userId = builder.createString(account.userId); + const email = builder.createString(account.email); + const userName = builder.createString(account.userName); + const displayName = builder.createString(account.displayName); + const preferredLanguage = builder.createString("en"); + const timeZone = builder.createString("UTC"); + const declaredCountry = builder.createString(""); + AccountView.startAccountView(builder); + AccountView.addUserId(builder, userId); + AccountView.addEmail(builder, email); + AccountView.addUserName(builder, userName); + AccountView.addDisplayName(builder, displayName); + AccountView.addPreferredLanguage(builder, preferredLanguage); + AccountView.addTimeZone(builder, timeZone); + AccountView.addDeclaredCountry(builder, declaredCountry); + AccountView.addEntitlement(builder, entitlement); + AccountView.addCreatedAtMs(builder, 0n); + AccountView.addUpdatedAtMs(builder, 0n); + const view = AccountView.endAccountView(builder); + + AccountResponse.startAccountResponse(builder); + AccountResponse.addAccount(builder, view); + builder.finish(AccountResponse.endAccountResponse(builder)); + return builder.asUint8Array(); +} diff --git a/ui/frontend/tests/e2e/lobby-flow.spec.ts b/ui/frontend/tests/e2e/lobby-flow.spec.ts new file mode 100644 index 0000000..2f4b809 --- /dev/null +++ b/ui/frontend/tests/e2e/lobby-flow.spec.ts @@ -0,0 +1,340 @@ +// Phase 8 lobby end-to-end coverage. The gateway is mocked through +// `page.route(...)` like in the Phase 7 spec; this spec dispatches by +// `messageType` so each lobby command can return its own forged +// FlatBuffers payload. The flows under test: +// +// 1) Land on /lobby with empty lists; create a private game; verify +// the new game appears in My Games after the redirect. +// 2) Submit an application to a public game; verify the application +// shows up in My Applications. +// 3) Accept an invitation; verify the invite card disappears. + +import { fromJson, type JsonValue } from "@bufbuild/protobuf"; +import { expect, test, type Page } from "@playwright/test"; +import { ByteBuffer } from "flatbuffers"; +import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb"; +import { GameCreateRequest } from "../../src/proto/galaxy/fbs/lobby"; +import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response"; +import { + buildAccountResponsePayload, + buildApplicationSubmitResponsePayload, + buildGameCreateResponsePayload, + buildInviteRedeemResponsePayload, + buildMyApplicationsListPayload, + buildMyGamesListPayload, + buildMyInvitesListPayload, + buildPublicGamesListPayload, + type ApplicationFixture, + type GameFixture, + type InviteFixture, +} from "./fixtures/lobby-fbs"; + +interface LobbyState { + myGames: GameFixture[]; + publicGames: GameFixture[]; + invitations: InviteFixture[]; + applications: ApplicationFixture[]; +} + +interface LobbyMocks { + state: LobbyState; + pendingSubscribes: Array<() => void>; + createGameCalls: GameFixture[]; + applicationSubmitCalls: Array<{ gameId: string; raceName: string }>; + inviteRedeemCalls: Array<{ gameId: string; inviteId: string }>; +} + +async function mockGateway(page: Page, initial: Partial = {}): Promise { + const mocks: LobbyMocks = { + state: { + myGames: initial.myGames ?? [], + publicGames: initial.publicGames ?? [], + invitations: initial.invitations ?? [], + applications: initial.applications ?? [], + }, + pendingSubscribes: [], + createGameCalls: [], + applicationSubmitCalls: [], + inviteRedeemCalls: [], + }; + + await page.route("**/api/v1/public/auth/send-email-code", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ challenge_id: "ch-test-1" }), + }); + }); + + await page.route("**/api/v1/public/auth/confirm-email-code", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ device_session_id: "dev-test-1" }), + }); + }); + + await page.route("**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand", async (route) => { + const reqText = route.request().postData(); + if (reqText === null) { + await route.fulfill({ status: 400 }); + return; + } + const req = fromJson( + ExecuteCommandRequestSchema, + JSON.parse(reqText) as JsonValue, + ); + + let resultCode = "ok"; + let payload: Uint8Array; + switch (req.messageType) { + case "user.account.get": + payload = buildAccountResponsePayload({ + userId: "user-1", + email: "pilot@example.com", + userName: "pilot", + displayName: "Pilot", + }); + break; + case "lobby.my.games.list": + payload = buildMyGamesListPayload(mocks.state.myGames); + break; + case "lobby.public.games.list": + payload = buildPublicGamesListPayload(mocks.state.publicGames); + break; + case "lobby.my.invites.list": + payload = buildMyInvitesListPayload(mocks.state.invitations); + break; + case "lobby.my.applications.list": + payload = buildMyApplicationsListPayload(mocks.state.applications); + break; + case "lobby.game.create": { + const decoded = GameCreateRequest.getRootAsGameCreateRequest( + new ByteBuffer(req.payloadBytes), + ); + const created: GameFixture = { + gameId: "private-newly-created", + gameName: decoded.gameName() ?? "", + gameType: "private", + status: "draft", + ownerUserId: "user-1", + minPlayers: decoded.minPlayers(), + maxPlayers: decoded.maxPlayers(), + enrollmentEndsAtMs: decoded.enrollmentEndsAtMs(), + createdAtMs: BigInt(Date.now()), + updatedAtMs: BigInt(Date.now()), + }; + mocks.createGameCalls.push(created); + mocks.state.myGames = [...mocks.state.myGames, created]; + payload = buildGameCreateResponsePayload(created); + break; + } + case "lobby.application.submit": { + const builder = req.payloadBytes; + const submitReq = await import("../../src/proto/galaxy/fbs/lobby"); + const decoded = submitReq.ApplicationSubmitRequest.getRootAsApplicationSubmitRequest( + new ByteBuffer(builder), + ); + const application: ApplicationFixture = { + applicationId: `app-${mocks.applicationSubmitCalls.length + 1}`, + gameId: decoded.gameId() ?? "", + applicantUserId: "user-1", + raceName: decoded.raceName() ?? "", + status: "pending", + createdAtMs: BigInt(Date.now()), + }; + mocks.applicationSubmitCalls.push({ + gameId: application.gameId, + raceName: application.raceName, + }); + mocks.state.applications = [application, ...mocks.state.applications]; + payload = buildApplicationSubmitResponsePayload(application); + break; + } + case "lobby.invite.redeem": { + const redeemMod = await import("../../src/proto/galaxy/fbs/lobby"); + const decoded = redeemMod.InviteRedeemRequest.getRootAsInviteRedeemRequest( + new ByteBuffer(req.payloadBytes), + ); + const gameId = decoded.gameId() ?? ""; + const inviteId = decoded.inviteId() ?? ""; + mocks.inviteRedeemCalls.push({ gameId, inviteId }); + const original = mocks.state.invitations.find((i) => i.inviteId === inviteId); + const invite: InviteFixture = { + ...(original ?? { + inviteId, + gameId, + inviterUserId: "user-host", + invitedUserId: "user-1", + raceName: "", + }), + status: "accepted", + decidedAtMs: BigInt(Date.now()), + }; + mocks.state.invitations = mocks.state.invitations.filter( + (i) => i.inviteId !== inviteId, + ); + const newGame: GameFixture = { + gameId, + gameName: "Invited Game", + gameType: "private", + status: "enrollment_open", + ownerUserId: "user-host", + minPlayers: 2, + maxPlayers: 8, + enrollmentEndsAtMs: BigInt(Date.now() + 1_000_000), + }; + mocks.state.myGames = [...mocks.state.myGames, newGame]; + payload = buildInviteRedeemResponsePayload(invite); + break; + } + default: + resultCode = "internal_error"; + payload = new Uint8Array(); + break; + } + + const responseJson = await forgeExecuteCommandResponseJson({ + requestId: req.requestId, + timestampMs: BigInt(Date.now()), + resultCode, + payloadBytes: payload, + }); + await route.fulfill({ + status: 200, + contentType: "application/json", + body: responseJson, + }); + }); + + await page.route( + "**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents", + async (route) => { + const action = await new Promise<"endOfStream" | "abort">((resolve) => { + mocks.pendingSubscribes.push(() => resolve("endOfStream")); + }); + if (action === "abort") { + await route.abort(); + return; + } + const body = new TextEncoder().encode("{}"); + const frame = new Uint8Array(5 + body.length); + frame[0] = 0x02; + new DataView(frame.buffer).setUint32(1, body.length, false); + frame.set(body, 5); + await route.fulfill({ + status: 200, + contentType: "application/connect+json", + body: Buffer.from(frame), + }); + }, + ); + + return mocks; +} + +async function completeLogin(page: Page): Promise { + await page.goto("/"); + await expect(page).toHaveURL(/\/login$/); + // The login page renders the inputs `readonly` as a Safari + // autofill-suppression workaround; the readonly attribute is + // dropped on first focus. Playwright's `fill()` checks editability + // before its own focus call, so emulate the user gesture explicitly: + // click the input (focus → readonly drops), then fill. + await page.getByTestId("login-email-input").click(); + await page.getByTestId("login-email-input").fill("pilot@example.com"); + await page.getByTestId("login-email-submit").click(); + await expect(page.getByTestId("login-code-input")).toBeVisible(); + await page.getByTestId("login-code-input").click(); + await page.getByTestId("login-code-input").fill("123456"); + await page.getByTestId("login-code-submit").click(); + await expect(page).toHaveURL(/\/lobby$/); +} + +test.describe("Phase 8 — lobby flow", () => { + test("create-game flow lands the new game in My Games", async ({ page }) => { + const mocks = await mockGateway(page); + await completeLogin(page); + + await expect(page.getByTestId("lobby-my-games-empty")).toBeVisible(); + await expect(page.getByTestId("lobby-public-games-empty")).toBeVisible(); + + await page.getByTestId("lobby-create-button").click(); + await expect(page).toHaveURL(/\/lobby\/create$/); + + await page.getByTestId("lobby-create-game-name").click(); + await page.getByTestId("lobby-create-game-name").fill("First Contact"); + await page.getByTestId("lobby-create-turn-schedule").click(); + await page.getByTestId("lobby-create-turn-schedule").fill("0 0 * * *"); + await page + .getByTestId("lobby-create-enrollment-ends-at") + .fill("2026-06-01T12:00"); + await page.getByTestId("lobby-create-submit").click(); + + await expect(page).toHaveURL(/\/lobby$/); + await expect(page.getByTestId("lobby-my-game-card")).toContainText("First Contact"); + expect(mocks.createGameCalls.length).toBe(1); + expect(mocks.createGameCalls[0]!.gameName).toBe("First Contact"); + + mocks.pendingSubscribes.forEach((resolve) => resolve()); + }); + + test("submitting an application produces a pending applications card", async ({ + page, + }) => { + const mocks = await mockGateway(page, { + publicGames: [ + { + gameId: "public-1", + gameName: "Open Lobby", + gameType: "public", + status: "enrollment_open", + }, + ], + }); + await completeLogin(page); + + await expect(page.getByTestId("lobby-public-game-apply")).toBeVisible(); + await page.getByTestId("lobby-public-game-apply").click(); + await page + .getByTestId("lobby-application-race-name") + .fill("Vegan Federation"); + await page.getByTestId("lobby-application-submit").click(); + + await expect(page.getByTestId("lobby-application-card")).toBeVisible(); + expect(mocks.applicationSubmitCalls).toEqual([ + { gameId: "public-1", raceName: "Vegan Federation" }, + ]); + + mocks.pendingSubscribes.forEach((resolve) => resolve()); + }); + + test("accepting an invitation removes it and adds the game to My Games", async ({ + page, + }) => { + const mocks = await mockGateway(page, { + invitations: [ + { + inviteId: "invite-1", + gameId: "private-1", + inviterUserId: "user-host", + invitedUserId: "user-1", + raceName: "Vegan Federation", + status: "pending", + }, + ], + }); + await completeLogin(page); + + await expect(page.getByTestId("lobby-invite-accept")).toBeVisible(); + await page.getByTestId("lobby-invite-accept").click(); + + await expect(page.getByTestId("lobby-invite-accept")).toBeHidden(); + await expect(page.getByTestId("lobby-my-game-card")).toContainText("Invited Game"); + expect(mocks.inviteRedeemCalls).toEqual([ + { gameId: "private-1", inviteId: "invite-1" }, + ]); + + mocks.pendingSubscribes.forEach((resolve) => resolve()); + }); +}); diff --git a/ui/frontend/tests/galaxy-client.test.ts b/ui/frontend/tests/galaxy-client.test.ts index ae8184e..f9e7845 100644 --- a/ui/frontend/tests/galaxy-client.test.ts +++ b/ui/frontend/tests/galaxy-client.test.ts @@ -83,7 +83,8 @@ describe("GalaxyClient.executeCommand", () => { new TextEncoder().encode("client-payload"), ); - expect(Array.from(out)).toEqual(Array.from(responsePayload)); + expect(out.resultCode).toBe("ok"); + expect(Array.from(out.payloadBytes)).toEqual(Array.from(responsePayload)); expect(signer).toHaveBeenCalledWith(canonicalBytes); expect(sha256).toHaveBeenCalledTimes(1); expect(core.signRequest).toHaveBeenCalledTimes(1); diff --git a/ui/frontend/tests/lobby-api.test.ts b/ui/frontend/tests/lobby-api.test.ts new file mode 100644 index 0000000..eedbc58 --- /dev/null +++ b/ui/frontend/tests/lobby-api.test.ts @@ -0,0 +1,335 @@ +// Unit tests for the typed lobby.ts wrappers. They invoke the +// wrappers against a minimal stub of `GalaxyClient.executeCommand` +// that captures the message type and FlatBuffers request payload, +// then returns a forged FlatBuffers response payload built with the +// generated TS bindings. No network, no signing — the test confirms +// the encoder/decoder shape matches the gateway contract and that +// non-`ok` result codes are surfaced as a `LobbyError`. + +import { Builder, ByteBuffer } from "flatbuffers"; +import { describe, expect, test, vi } from "vitest"; + +import { + LobbyError, + createGame, + declineInvite, + listMyApplications, + listMyGames, + listMyInvites, + listPublicGames, + redeemInvite, + submitApplication, +} from "../src/api/lobby"; +import { + ApplicationSubmitResponse, + ApplicationSummary, + ErrorBody, + ErrorResponse, + GameCreateResponse, + GameSummary, + InviteDeclineResponse, + InviteRedeemResponse, + InviteSummary, + MyApplicationsListResponse, + MyGamesListResponse, + MyInvitesListResponse, + PublicGamesListResponse, +} from "../src/proto/galaxy/fbs/lobby"; +import type { GalaxyClient } from "../src/api/galaxy-client"; +import { + GameCreateRequest, + PublicGamesListRequest, + ApplicationSubmitRequest, + InviteRedeemRequest, + InviteDeclineRequest, +} from "../src/proto/galaxy/fbs/lobby"; + +interface Captured { + messageType: string; + payload: Uint8Array; +} + +function makeStub( + respondWith: (c: Captured) => { resultCode?: string; payloadBytes: Uint8Array }, +): { + client: GalaxyClient; + captured: Captured[]; +} { + const captured: Captured[] = []; + const stub = { + executeCommand: vi.fn(async (messageType: string, payload: Uint8Array) => { + const c = { messageType, payload }; + captured.push(c); + const result = respondWith(c); + return { + resultCode: result.resultCode ?? "ok", + payloadBytes: result.payloadBytes, + }; + }), + } as unknown as GalaxyClient; + return { client: stub, captured }; +} + +function encodeGameSummary(builder: Builder): number { + const gameId = builder.createString("g-1"); + const gameName = builder.createString("Test Game"); + const gameType = builder.createString("private"); + const status = builder.createString("draft"); + const ownerUserId = builder.createString("user-1"); + GameSummary.startGameSummary(builder); + GameSummary.addGameId(builder, gameId); + GameSummary.addGameName(builder, gameName); + GameSummary.addGameType(builder, gameType); + GameSummary.addStatus(builder, status); + GameSummary.addOwnerUserId(builder, ownerUserId); + GameSummary.addMinPlayers(builder, 2); + GameSummary.addMaxPlayers(builder, 8); + GameSummary.addEnrollmentEndsAtMs(builder, 1_780_000_000_000n); + GameSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + GameSummary.addUpdatedAtMs(builder, 1_770_000_000_000n); + return GameSummary.endGameSummary(builder); +} + +function encodeApplicationSummary(builder: Builder, status: string): number { + const applicationId = builder.createString("app-1"); + const gameId = builder.createString("g-1"); + const applicantUserId = builder.createString("user-1"); + const raceName = builder.createString("Vegan Federation"); + const statusOff = builder.createString(status); + ApplicationSummary.startApplicationSummary(builder); + ApplicationSummary.addApplicationId(builder, applicationId); + ApplicationSummary.addGameId(builder, gameId); + ApplicationSummary.addApplicantUserId(builder, applicantUserId); + ApplicationSummary.addRaceName(builder, raceName); + ApplicationSummary.addStatus(builder, statusOff); + ApplicationSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + ApplicationSummary.addDecidedAtMs(builder, status === "pending" ? 0n : 1_770_010_000_000n); + return ApplicationSummary.endApplicationSummary(builder); +} + +function encodeInviteSummary(builder: Builder, status: string): number { + const inviteId = builder.createString("invite-1"); + const gameId = builder.createString("g-1"); + const inviter = builder.createString("user-host"); + const invited = builder.createString("user-1"); + const code = builder.createString(""); + const race = builder.createString("Vegan Federation"); + const statusOff = builder.createString(status); + InviteSummary.startInviteSummary(builder); + InviteSummary.addInviteId(builder, inviteId); + InviteSummary.addGameId(builder, gameId); + InviteSummary.addInviterUserId(builder, inviter); + InviteSummary.addInvitedUserId(builder, invited); + InviteSummary.addCode(builder, code); + InviteSummary.addRaceName(builder, race); + InviteSummary.addStatus(builder, statusOff); + InviteSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + InviteSummary.addExpiresAtMs(builder, 1_780_000_000_000n); + InviteSummary.addDecidedAtMs(builder, status === "pending" ? 0n : 1_770_010_000_000n); + return InviteSummary.endInviteSummary(builder); +} + +describe("lobby.ts wrappers", () => { + test("listMyGames decodes the response and reports the message type", async () => { + const { client, captured } = makeStub(() => { + const builder = new Builder(256); + const item = encodeGameSummary(builder); + const items = MyGamesListResponse.createItemsVector(builder, [item]); + MyGamesListResponse.startMyGamesListResponse(builder); + MyGamesListResponse.addItems(builder, items); + builder.finish(MyGamesListResponse.endMyGamesListResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + + const games = await listMyGames(client); + expect(captured[0]!.messageType).toBe("lobby.my.games.list"); + expect(games.length).toBe(1); + expect(games[0]!.gameId).toBe("g-1"); + expect(games[0]!.minPlayers).toBe(2); + }); + + test("listPublicGames passes pagination and decodes pageSize/total", async () => { + const { client, captured } = makeStub(() => { + const builder = new Builder(256); + const item = encodeGameSummary(builder); + const items = PublicGamesListResponse.createItemsVector(builder, [item]); + PublicGamesListResponse.startPublicGamesListResponse(builder); + PublicGamesListResponse.addItems(builder, items); + PublicGamesListResponse.addPage(builder, 2); + PublicGamesListResponse.addPageSize(builder, 25); + PublicGamesListResponse.addTotal(builder, 51); + builder.finish(PublicGamesListResponse.endPublicGamesListResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + + const page = await listPublicGames(client, { page: 2, pageSize: 25 }); + expect(captured[0]!.messageType).toBe("lobby.public.games.list"); + const decodedRequest = PublicGamesListRequest.getRootAsPublicGamesListRequest( + new ByteBuffer(captured[0]!.payload), + ); + expect(decodedRequest.page()).toBe(2); + expect(decodedRequest.pageSize()).toBe(25); + + expect(page.items.length).toBe(1); + expect(page.page).toBe(2); + expect(page.pageSize).toBe(25); + expect(page.total).toBe(51); + }); + + test("listMyApplications decodes pending and decided records", async () => { + const { client } = makeStub(() => { + const builder = new Builder(256); + const pending = encodeApplicationSummary(builder, "pending"); + const approved = encodeApplicationSummary(builder, "approved"); + const items = MyApplicationsListResponse.createItemsVector(builder, [pending, approved]); + MyApplicationsListResponse.startMyApplicationsListResponse(builder); + MyApplicationsListResponse.addItems(builder, items); + builder.finish(MyApplicationsListResponse.endMyApplicationsListResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + + const applications = await listMyApplications(client); + expect(applications.length).toBe(2); + expect(applications[0]!.status).toBe("pending"); + expect(applications[0]!.decidedAt).toBeNull(); + expect(applications[1]!.status).toBe("approved"); + expect(applications[1]!.decidedAt).not.toBeNull(); + }); + + test("listMyInvites decodes user-bound invites", async () => { + const { client } = makeStub(() => { + const builder = new Builder(256); + const invite = encodeInviteSummary(builder, "pending"); + const items = MyInvitesListResponse.createItemsVector(builder, [invite]); + MyInvitesListResponse.startMyInvitesListResponse(builder); + MyInvitesListResponse.addItems(builder, items); + builder.finish(MyInvitesListResponse.endMyInvitesListResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + + const invites = await listMyInvites(client); + expect(invites.length).toBe(1); + expect(invites[0]!.invitedUserId).toBe("user-1"); + expect(invites[0]!.status).toBe("pending"); + expect(invites[0]!.decidedAt).toBeNull(); + }); + + test("createGame encodes every field and decodes the returned summary", async () => { + const { client, captured } = makeStub(() => { + const builder = new Builder(256); + const game = encodeGameSummary(builder); + GameCreateResponse.startGameCreateResponse(builder); + GameCreateResponse.addGame(builder, game); + builder.finish(GameCreateResponse.endGameCreateResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + + const enrollment = new Date(1_780_000_000_000); + const result = await createGame(client, { + gameName: "First Contact", + description: "", + minPlayers: 2, + maxPlayers: 8, + startGapHours: 24, + startGapPlayers: 2, + enrollmentEndsAt: enrollment, + turnSchedule: "0 0 * * *", + targetEngineVersion: "v1", + }); + + expect(captured[0]!.messageType).toBe("lobby.game.create"); + const request = GameCreateRequest.getRootAsGameCreateRequest( + new ByteBuffer(captured[0]!.payload), + ); + expect(request.gameName()).toBe("First Contact"); + expect(request.turnSchedule()).toBe("0 0 * * *"); + expect(request.targetEngineVersion()).toBe("v1"); + expect(request.minPlayers()).toBe(2); + expect(request.maxPlayers()).toBe(8); + expect(request.enrollmentEndsAtMs()).toBe(BigInt(enrollment.getTime())); + + expect(result.gameId).toBe("g-1"); + }); + + test("submitApplication encodes game_id and race_name", async () => { + const { client, captured } = makeStub(() => { + const builder = new Builder(128); + const app = encodeApplicationSummary(builder, "pending"); + ApplicationSubmitResponse.startApplicationSubmitResponse(builder); + ApplicationSubmitResponse.addApplication(builder, app); + builder.finish(ApplicationSubmitResponse.endApplicationSubmitResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + + const submitted = await submitApplication(client, "public-1", "Vegan Federation"); + expect(captured[0]!.messageType).toBe("lobby.application.submit"); + const decoded = ApplicationSubmitRequest.getRootAsApplicationSubmitRequest( + new ByteBuffer(captured[0]!.payload), + ); + expect(decoded.gameId()).toBe("public-1"); + expect(decoded.raceName()).toBe("Vegan Federation"); + expect(submitted.applicationId).toBe("app-1"); + }); + + test("redeemInvite and declineInvite hit their respective message types", async () => { + const stubRedeem = makeStub(() => { + const builder = new Builder(128); + const invite = encodeInviteSummary(builder, "accepted"); + InviteRedeemResponse.startInviteRedeemResponse(builder); + InviteRedeemResponse.addInvite(builder, invite); + builder.finish(InviteRedeemResponse.endInviteRedeemResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + const redeemed = await redeemInvite(stubRedeem.client, "private-1", "invite-1"); + expect(stubRedeem.captured[0]!.messageType).toBe("lobby.invite.redeem"); + const redeemReq = InviteRedeemRequest.getRootAsInviteRedeemRequest( + new ByteBuffer(stubRedeem.captured[0]!.payload), + ); + expect(redeemReq.gameId()).toBe("private-1"); + expect(redeemReq.inviteId()).toBe("invite-1"); + expect(redeemed.status).toBe("accepted"); + + const stubDecline = makeStub(() => { + const builder = new Builder(128); + const invite = encodeInviteSummary(builder, "declined"); + InviteDeclineResponse.startInviteDeclineResponse(builder); + InviteDeclineResponse.addInvite(builder, invite); + builder.finish(InviteDeclineResponse.endInviteDeclineResponse(builder)); + return { payloadBytes: builder.asUint8Array() }; + }); + const declined = await declineInvite(stubDecline.client, "private-1", "invite-1"); + expect(stubDecline.captured[0]!.messageType).toBe("lobby.invite.decline"); + const declineReq = InviteDeclineRequest.getRootAsInviteDeclineRequest( + new ByteBuffer(stubDecline.captured[0]!.payload), + ); + expect(declineReq.gameId()).toBe("private-1"); + expect(declineReq.inviteId()).toBe("invite-1"); + expect(declined.status).toBe("declined"); + }); + + test("non-ok result codes are surfaced as a LobbyError with code and message", async () => { + const { client } = makeStub(() => { + const builder = new Builder(128); + const code = builder.createString("conflict"); + const message = builder.createString("game is not in enrollment_open"); + ErrorBody.startErrorBody(builder); + ErrorBody.addCode(builder, code); + ErrorBody.addMessage(builder, message); + const errorOff = ErrorBody.endErrorBody(builder); + ErrorResponse.startErrorResponse(builder); + ErrorResponse.addError(builder, errorOff); + builder.finish(ErrorResponse.endErrorResponse(builder)); + return { resultCode: "conflict", payloadBytes: builder.asUint8Array() }; + }); + + await expect(submitApplication(client, "public-1", "race")).rejects.toThrow(LobbyError); + try { + await submitApplication(client, "public-1", "race"); + } catch (err) { + const lobbyError = err as LobbyError; + expect(lobbyError.code).toBe("conflict"); + expect(lobbyError.message).toBe("game is not in enrollment_open"); + expect(lobbyError.resultCode).toBe("conflict"); + } + }); +}); diff --git a/ui/frontend/tests/lobby-create.test.ts b/ui/frontend/tests/lobby-create.test.ts new file mode 100644 index 0000000..5aabcc9 --- /dev/null +++ b/ui/frontend/tests/lobby-create.test.ts @@ -0,0 +1,195 @@ +// Component tests for the create-game form. The lobby API is mocked +// at module level; the GalaxyClient is replaced with a stub that does +// nothing (the test only asserts the createGame wrapper is invoked +// with the right shape). + +import "fake-indexeddb/auto"; +import { fireEvent, render, waitFor } from "@testing-library/svelte"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; +import type { IDBPDatabase } from "idb"; + +import { i18n } from "../src/lib/i18n/index.svelte"; +import { session } from "../src/lib/session-store.svelte"; +import { type GalaxyDB, openGalaxyDB } from "../src/platform/store/idb"; +import { IDBCache } from "../src/platform/store/idb-cache"; +import { WebCryptoKeyStore } from "../src/platform/store/webcrypto-keystore"; + +const gotoSpy = vi.fn<(url: string) => Promise>(async () => {}); +vi.mock("$app/navigation", () => ({ + goto: (url: string) => gotoSpy(url), +})); + +const createGameSpy = vi.fn(); +vi.mock("../src/api/lobby", async () => { + const actual = await vi.importActual( + "../src/api/lobby", + ); + return { + ...actual, + createGame: (...args: unknown[]) => createGameSpy(...args), + }; +}); + +vi.mock("../src/lib/env", () => ({ + GATEWAY_BASE_URL: "http://gateway.test", + GATEWAY_RESPONSE_PUBLIC_KEY: new Uint8Array(32).fill(0x55), +})); + +vi.mock("../src/api/connect", () => ({ + createEdgeGatewayClient: vi.fn(() => ({})), +})); + +vi.mock("../src/api/galaxy-client", () => { + class FakeGalaxyClient { + executeCommand = vi.fn(async () => ({ + resultCode: "ok", + payloadBytes: new Uint8Array(), + })); + } + return { GalaxyClient: FakeGalaxyClient }; +}); + +vi.mock("../src/platform/core/index", () => ({ + loadCore: async () => ({ + signRequest: () => new Uint8Array(), + verifyResponse: () => true, + verifyEvent: () => true, + verifyPayloadHash: () => true, + }), +})); + +let db: IDBPDatabase; +let dbName: string; + +beforeEach(async () => { + dbName = `galaxy-ui-test-${crypto.randomUUID()}`; + db = await openGalaxyDB(dbName); + const store = { + keyStore: new WebCryptoKeyStore(db), + cache: new IDBCache(db), + }; + session.resetForTests(); + session.setStoreLoaderForTests(async () => store); + await session.init(); + await session.signIn("device-1"); + i18n.resetForTests("en"); + createGameSpy.mockReset(); + gotoSpy.mockReset(); +}); + +afterEach(async () => { + session.resetForTests(); + i18n.resetForTests("en"); + db.close(); + await new Promise((resolve) => { + const req = indexedDB.deleteDatabase(dbName); + req.onsuccess = () => resolve(); + req.onerror = () => resolve(); + req.onblocked = () => resolve(); + }); +}); + +async function importCreatePage(): Promise { + return import("../src/routes/lobby/create/+page.svelte"); +} + +describe("lobby/create page", () => { + test("submitting a valid form invokes createGame with the entered values and navigates back", async () => { + createGameSpy.mockResolvedValue({ + gameId: "private-new", + gameName: "First Contact", + gameType: "private", + status: "draft", + ownerUserId: "user-1", + minPlayers: 2, + maxPlayers: 8, + enrollmentEndsAt: new Date(), + createdAt: new Date(), + updatedAt: new Date(), + }); + + const Page = (await importCreatePage()).default; + const ui = render(Page); + + await waitFor(() => + expect(ui.getByTestId("lobby-create-form")).toBeInTheDocument(), + ); + + await fireEvent.input(ui.getByTestId("lobby-create-game-name"), { + target: { value: "First Contact" }, + }); + await fireEvent.input(ui.getByTestId("lobby-create-description"), { + target: { value: "" }, + }); + await fireEvent.input(ui.getByTestId("lobby-create-turn-schedule"), { + target: { value: "0 0 * * *" }, + }); + await fireEvent.input(ui.getByTestId("lobby-create-enrollment-ends-at"), { + target: { value: "2026-06-01T12:00" }, + }); + + await fireEvent.click(ui.getByTestId("lobby-create-submit")); + + await waitFor(() => { + expect(createGameSpy).toHaveBeenCalledTimes(1); + const call = createGameSpy.mock.calls[0]!; + const input = call[1] as Record; + expect(input.gameName).toBe("First Contact"); + expect(input.turnSchedule).toBe("0 0 * * *"); + expect(input.minPlayers).toBe(2); + expect(input.maxPlayers).toBe(8); + expect(input.startGapHours).toBe(24); + expect(input.startGapPlayers).toBe(2); + expect(input.targetEngineVersion).toBe("v1"); + expect(input.enrollmentEndsAt).toBeInstanceOf(Date); + expect(gotoSpy).toHaveBeenCalledWith("/lobby"); + }); + }); + + test("submitting with an empty game name surfaces a validation error and does not call the API", async () => { + const Page = (await importCreatePage()).default; + const ui = render(Page); + + await waitFor(() => + expect(ui.getByTestId("lobby-create-form")).toBeInTheDocument(), + ); + + // turn_schedule starts populated with the default; clear game_name to trigger the error + await fireEvent.input(ui.getByTestId("lobby-create-game-name"), { + target: { value: " " }, + }); + await fireEvent.input(ui.getByTestId("lobby-create-enrollment-ends-at"), { + target: { value: "2026-06-01T12:00" }, + }); + await fireEvent.click(ui.getByTestId("lobby-create-submit")); + + await waitFor(() => { + expect(ui.getByTestId("lobby-create-error")).toHaveTextContent( + "game name must not be empty", + ); + expect(createGameSpy).not.toHaveBeenCalled(); + }); + }); + + test("cancel button navigates back to /lobby without calling the API", async () => { + const Page = (await importCreatePage()).default; + const ui = render(Page); + + await waitFor(() => + expect(ui.getByTestId("lobby-create-cancel")).toBeInTheDocument(), + ); + await fireEvent.click(ui.getByTestId("lobby-create-cancel")); + + await waitFor(() => { + expect(gotoSpy).toHaveBeenCalledWith("/lobby"); + expect(createGameSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/frontend/tests/lobby-fbs.test.ts b/ui/frontend/tests/lobby-fbs.test.ts new file mode 100644 index 0000000..e2d9b2c --- /dev/null +++ b/ui/frontend/tests/lobby-fbs.test.ts @@ -0,0 +1,494 @@ +// Round-trip tests for the generated TS FlatBuffers bindings under +// `src/proto/galaxy/fbs/lobby/`. These guard against codegen drift — +// if the wire schema and the bindings disagree, the round-trip fails +// instead of letting a broken binding ship silently. + +import { Builder, ByteBuffer } from "flatbuffers"; +import { describe, expect, test } from "vitest"; + +import { + ApplicationSubmitRequest, + ApplicationSubmitResponse, + ApplicationSummary, + ErrorBody, + ErrorResponse, + GameCreateRequest, + GameCreateResponse, + GameSummary, + InviteDeclineRequest, + InviteDeclineResponse, + InviteRedeemRequest, + InviteRedeemResponse, + InviteSummary, + MyApplicationsListRequest, + MyApplicationsListResponse, + MyGamesListRequest, + MyGamesListResponse, + MyInvitesListRequest, + MyInvitesListResponse, + OpenEnrollmentRequest, + OpenEnrollmentResponse, + PublicGamesListRequest, + PublicGamesListResponse, +} from "../src/proto/galaxy/fbs/lobby"; + +interface GameSummaryFixture { + gameId: string; + gameName: string; + gameType: string; + status: string; + ownerUserId: string; + minPlayers: number; + maxPlayers: number; + enrollmentEndsAtMs: bigint; + createdAtMs: bigint; + updatedAtMs: bigint; +} + +const PRIVATE_GAME: GameSummaryFixture = { + gameId: "game-private-7c8f", + gameName: "First Contact", + gameType: "private", + status: "draft", + ownerUserId: "user-9912", + minPlayers: 2, + maxPlayers: 8, + enrollmentEndsAtMs: 1_780_000_000_000n, + createdAtMs: 1_770_000_000_000n, + updatedAtMs: 1_770_000_300_000n, +}; + +const PUBLIC_GAME: GameSummaryFixture = { + gameId: "game-public-aabb", + gameName: "Open Lobby", + gameType: "public", + status: "enrollment_open", + ownerUserId: "", + minPlayers: 4, + maxPlayers: 12, + enrollmentEndsAtMs: 1_780_500_000_000n, + createdAtMs: 1_770_500_000_000n, + updatedAtMs: 1_770_600_000_000n, +}; + +function encodeGameSummary(builder: Builder, value: GameSummaryFixture): number { + const gameId = builder.createString(value.gameId); + const gameName = builder.createString(value.gameName); + const gameType = builder.createString(value.gameType); + const status = builder.createString(value.status); + const ownerUserId = builder.createString(value.ownerUserId); + GameSummary.startGameSummary(builder); + GameSummary.addGameId(builder, gameId); + GameSummary.addGameName(builder, gameName); + GameSummary.addGameType(builder, gameType); + GameSummary.addStatus(builder, status); + GameSummary.addOwnerUserId(builder, ownerUserId); + GameSummary.addMinPlayers(builder, value.minPlayers); + GameSummary.addMaxPlayers(builder, value.maxPlayers); + GameSummary.addEnrollmentEndsAtMs(builder, value.enrollmentEndsAtMs); + GameSummary.addCreatedAtMs(builder, value.createdAtMs); + GameSummary.addUpdatedAtMs(builder, value.updatedAtMs); + return GameSummary.endGameSummary(builder); +} + +function expectGameSummary(actual: GameSummary | null, want: GameSummaryFixture): void { + expect(actual).not.toBeNull(); + const got = actual!; + expect(got.gameId()).toBe(want.gameId); + expect(got.gameName()).toBe(want.gameName); + expect(got.gameType()).toBe(want.gameType); + expect(got.status()).toBe(want.status); + expect(got.ownerUserId()).toBe(want.ownerUserId); + expect(got.minPlayers()).toBe(want.minPlayers); + expect(got.maxPlayers()).toBe(want.maxPlayers); + expect(got.enrollmentEndsAtMs()).toBe(want.enrollmentEndsAtMs); + expect(got.createdAtMs()).toBe(want.createdAtMs); + expect(got.updatedAtMs()).toBe(want.updatedAtMs); +} + +describe("lobby FlatBuffers TS bindings", () => { + test("MyGamesListRequest round-trips an empty body", () => { + const builder = new Builder(32); + MyGamesListRequest.startMyGamesListRequest(builder); + builder.finish(MyGamesListRequest.endMyGamesListRequest(builder)); + const bytes = builder.asUint8Array(); + const decoded = MyGamesListRequest.getRootAsMyGamesListRequest(new ByteBuffer(bytes)); + expect(decoded).toBeDefined(); + }); + + test("MyGamesListResponse encodes and decodes multiple summaries", () => { + const builder = new Builder(512); + const item0 = encodeGameSummary(builder, PRIVATE_GAME); + const item1 = encodeGameSummary(builder, PUBLIC_GAME); + const items = MyGamesListResponse.createItemsVector(builder, [item0, item1]); + MyGamesListResponse.startMyGamesListResponse(builder); + MyGamesListResponse.addItems(builder, items); + builder.finish(MyGamesListResponse.endMyGamesListResponse(builder)); + + const bytes = builder.asUint8Array(); + const decoded = MyGamesListResponse.getRootAsMyGamesListResponse(new ByteBuffer(bytes)); + expect(decoded.itemsLength()).toBe(2); + expectGameSummary(decoded.items(0), PRIVATE_GAME); + expectGameSummary(decoded.items(1), PUBLIC_GAME); + }); + + test("PublicGamesListResponse preserves pagination metadata", () => { + const builder = new Builder(256); + const item = encodeGameSummary(builder, PUBLIC_GAME); + const items = PublicGamesListResponse.createItemsVector(builder, [item]); + PublicGamesListResponse.startPublicGamesListResponse(builder); + PublicGamesListResponse.addItems(builder, items); + PublicGamesListResponse.addPage(builder, 3); + PublicGamesListResponse.addPageSize(builder, 25); + PublicGamesListResponse.addTotal(builder, 51); + builder.finish(PublicGamesListResponse.endPublicGamesListResponse(builder)); + const bytes = builder.asUint8Array(); + const decoded = PublicGamesListResponse.getRootAsPublicGamesListResponse( + new ByteBuffer(bytes), + ); + expect(decoded.itemsLength()).toBe(1); + expectGameSummary(decoded.items(0), PUBLIC_GAME); + expect(decoded.page()).toBe(3); + expect(decoded.pageSize()).toBe(25); + expect(decoded.total()).toBe(51); + }); + + test("PublicGamesListRequest round-trips page numbers", () => { + const builder = new Builder(32); + PublicGamesListRequest.startPublicGamesListRequest(builder); + PublicGamesListRequest.addPage(builder, 2); + PublicGamesListRequest.addPageSize(builder, 10); + builder.finish(PublicGamesListRequest.endPublicGamesListRequest(builder)); + const decoded = PublicGamesListRequest.getRootAsPublicGamesListRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.page()).toBe(2); + expect(decoded.pageSize()).toBe(10); + }); + + test("ApplicationSummary preserves pending and decided records", () => { + const builder = new Builder(256); + + const pendingId = builder.createString("app-1"); + const pendingGameId = builder.createString("public-1"); + const pendingApplicant = builder.createString("user-1"); + const pendingRace = builder.createString("Vegan Federation"); + const pendingStatus = builder.createString("pending"); + ApplicationSummary.startApplicationSummary(builder); + ApplicationSummary.addApplicationId(builder, pendingId); + ApplicationSummary.addGameId(builder, pendingGameId); + ApplicationSummary.addApplicantUserId(builder, pendingApplicant); + ApplicationSummary.addRaceName(builder, pendingRace); + ApplicationSummary.addStatus(builder, pendingStatus); + ApplicationSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + ApplicationSummary.addDecidedAtMs(builder, 0n); + const pending = ApplicationSummary.endApplicationSummary(builder); + + const approvedId = builder.createString("app-2"); + const approvedGameId = builder.createString("public-2"); + const approvedApplicant = builder.createString("user-1"); + const approvedRace = builder.createString("Lithic Compact"); + const approvedStatus = builder.createString("approved"); + ApplicationSummary.startApplicationSummary(builder); + ApplicationSummary.addApplicationId(builder, approvedId); + ApplicationSummary.addGameId(builder, approvedGameId); + ApplicationSummary.addApplicantUserId(builder, approvedApplicant); + ApplicationSummary.addRaceName(builder, approvedRace); + ApplicationSummary.addStatus(builder, approvedStatus); + ApplicationSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + ApplicationSummary.addDecidedAtMs(builder, 1_770_010_000_000n); + const approved = ApplicationSummary.endApplicationSummary(builder); + + const items = MyApplicationsListResponse.createItemsVector(builder, [pending, approved]); + MyApplicationsListResponse.startMyApplicationsListResponse(builder); + MyApplicationsListResponse.addItems(builder, items); + builder.finish(MyApplicationsListResponse.endMyApplicationsListResponse(builder)); + + const decoded = MyApplicationsListResponse.getRootAsMyApplicationsListResponse( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.itemsLength()).toBe(2); + const first = decoded.items(0)!; + expect(first.status()).toBe("pending"); + expect(first.decidedAtMs()).toBe(0n); + const second = decoded.items(1)!; + expect(second.status()).toBe("approved"); + expect(second.decidedAtMs()).toBe(1_770_010_000_000n); + }); + + test("MyApplicationsListRequest round-trips an empty body", () => { + const builder = new Builder(32); + MyApplicationsListRequest.startMyApplicationsListRequest(builder); + builder.finish(MyApplicationsListRequest.endMyApplicationsListRequest(builder)); + const decoded = MyApplicationsListRequest.getRootAsMyApplicationsListRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded).toBeDefined(); + }); + + test("InviteSummary preserves invited_user_id and code fields", () => { + const builder = new Builder(256); + + const userBoundId = builder.createString("invite-user-bound"); + const userBoundGame = builder.createString("private-1"); + const userBoundInviter = builder.createString("user-host"); + const userBoundInvited = builder.createString("user-1"); + const userBoundCode = builder.createString(""); + const userBoundRace = builder.createString("Vegan Federation"); + const userBoundStatus = builder.createString("pending"); + InviteSummary.startInviteSummary(builder); + InviteSummary.addInviteId(builder, userBoundId); + InviteSummary.addGameId(builder, userBoundGame); + InviteSummary.addInviterUserId(builder, userBoundInviter); + InviteSummary.addInvitedUserId(builder, userBoundInvited); + InviteSummary.addCode(builder, userBoundCode); + InviteSummary.addRaceName(builder, userBoundRace); + InviteSummary.addStatus(builder, userBoundStatus); + InviteSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + InviteSummary.addExpiresAtMs(builder, 1_780_000_000_000n); + InviteSummary.addDecidedAtMs(builder, 0n); + const userBound = InviteSummary.endInviteSummary(builder); + + const codeBasedId = builder.createString("invite-code-based"); + const codeBasedGame = builder.createString("private-2"); + const codeBasedInviter = builder.createString("user-host"); + const codeBasedInvited = builder.createString(""); + const codeBasedCode = builder.createString("ABCDEF12"); + const codeBasedRace = builder.createString("Lithic Compact"); + const codeBasedStatus = builder.createString("pending"); + InviteSummary.startInviteSummary(builder); + InviteSummary.addInviteId(builder, codeBasedId); + InviteSummary.addGameId(builder, codeBasedGame); + InviteSummary.addInviterUserId(builder, codeBasedInviter); + InviteSummary.addInvitedUserId(builder, codeBasedInvited); + InviteSummary.addCode(builder, codeBasedCode); + InviteSummary.addRaceName(builder, codeBasedRace); + InviteSummary.addStatus(builder, codeBasedStatus); + InviteSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + InviteSummary.addExpiresAtMs(builder, 1_780_000_000_000n); + InviteSummary.addDecidedAtMs(builder, 0n); + const codeBased = InviteSummary.endInviteSummary(builder); + + const items = MyInvitesListResponse.createItemsVector(builder, [userBound, codeBased]); + MyInvitesListResponse.startMyInvitesListResponse(builder); + MyInvitesListResponse.addItems(builder, items); + builder.finish(MyInvitesListResponse.endMyInvitesListResponse(builder)); + + const decoded = MyInvitesListResponse.getRootAsMyInvitesListResponse( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.itemsLength()).toBe(2); + const first = decoded.items(0)!; + expect(first.invitedUserId()).toBe("user-1"); + expect(first.code()).toBe(""); + const second = decoded.items(1)!; + expect(second.invitedUserId()).toBe(""); + expect(second.code()).toBe("ABCDEF12"); + }); + + test("MyInvitesListRequest round-trips an empty body", () => { + const builder = new Builder(32); + MyInvitesListRequest.startMyInvitesListRequest(builder); + builder.finish(MyInvitesListRequest.endMyInvitesListRequest(builder)); + const decoded = MyInvitesListRequest.getRootAsMyInvitesListRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded).toBeDefined(); + }); + + test("OpenEnrollmentRequest and Response round-trip", () => { + const builder = new Builder(64); + const gameId = builder.createString("game-private-7c8f"); + OpenEnrollmentRequest.startOpenEnrollmentRequest(builder); + OpenEnrollmentRequest.addGameId(builder, gameId); + builder.finish(OpenEnrollmentRequest.endOpenEnrollmentRequest(builder)); + const reqDecoded = OpenEnrollmentRequest.getRootAsOpenEnrollmentRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(reqDecoded.gameId()).toBe("game-private-7c8f"); + + const respBuilder = new Builder(64); + const respGameId = respBuilder.createString("game-private-7c8f"); + const status = respBuilder.createString("enrollment_open"); + OpenEnrollmentResponse.startOpenEnrollmentResponse(respBuilder); + OpenEnrollmentResponse.addGameId(respBuilder, respGameId); + OpenEnrollmentResponse.addStatus(respBuilder, status); + respBuilder.finish(OpenEnrollmentResponse.endOpenEnrollmentResponse(respBuilder)); + const respDecoded = OpenEnrollmentResponse.getRootAsOpenEnrollmentResponse( + new ByteBuffer(respBuilder.asUint8Array()), + ); + expect(respDecoded.gameId()).toBe("game-private-7c8f"); + expect(respDecoded.status()).toBe("enrollment_open"); + }); + + test("GameCreateRequest and Response round-trip", () => { + const builder = new Builder(256); + const name = builder.createString("First Contact"); + const description = builder.createString(""); + const turnSchedule = builder.createString("0 0 * * *"); + const targetVersion = builder.createString("v1"); + GameCreateRequest.startGameCreateRequest(builder); + GameCreateRequest.addGameName(builder, name); + GameCreateRequest.addDescription(builder, description); + GameCreateRequest.addMinPlayers(builder, 2); + GameCreateRequest.addMaxPlayers(builder, 8); + GameCreateRequest.addStartGapHours(builder, 24); + GameCreateRequest.addStartGapPlayers(builder, 2); + GameCreateRequest.addEnrollmentEndsAtMs(builder, 1_780_000_000_000n); + GameCreateRequest.addTurnSchedule(builder, turnSchedule); + GameCreateRequest.addTargetEngineVersion(builder, targetVersion); + builder.finish(GameCreateRequest.endGameCreateRequest(builder)); + const reqDecoded = GameCreateRequest.getRootAsGameCreateRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(reqDecoded.gameName()).toBe("First Contact"); + expect(reqDecoded.minPlayers()).toBe(2); + expect(reqDecoded.maxPlayers()).toBe(8); + expect(reqDecoded.turnSchedule()).toBe("0 0 * * *"); + expect(reqDecoded.targetEngineVersion()).toBe("v1"); + expect(reqDecoded.enrollmentEndsAtMs()).toBe(1_780_000_000_000n); + + const respBuilder = new Builder(256); + const game = encodeGameSummary(respBuilder, PRIVATE_GAME); + GameCreateResponse.startGameCreateResponse(respBuilder); + GameCreateResponse.addGame(respBuilder, game); + respBuilder.finish(GameCreateResponse.endGameCreateResponse(respBuilder)); + const respDecoded = GameCreateResponse.getRootAsGameCreateResponse( + new ByteBuffer(respBuilder.asUint8Array()), + ); + expectGameSummary(respDecoded.game(), PRIVATE_GAME); + }); + + test("ApplicationSubmitRequest and Response round-trip", () => { + const builder = new Builder(128); + const gameId = builder.createString("public-1"); + const raceName = builder.createString("Vegan Federation"); + ApplicationSubmitRequest.startApplicationSubmitRequest(builder); + ApplicationSubmitRequest.addGameId(builder, gameId); + ApplicationSubmitRequest.addRaceName(builder, raceName); + builder.finish(ApplicationSubmitRequest.endApplicationSubmitRequest(builder)); + const reqDecoded = ApplicationSubmitRequest.getRootAsApplicationSubmitRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(reqDecoded.gameId()).toBe("public-1"); + expect(reqDecoded.raceName()).toBe("Vegan Federation"); + + const respBuilder = new Builder(128); + const appId = respBuilder.createString("app-3"); + const appGameId = respBuilder.createString("public-1"); + const applicant = respBuilder.createString("user-1"); + const race = respBuilder.createString("Vegan Federation"); + const status = respBuilder.createString("pending"); + ApplicationSummary.startApplicationSummary(respBuilder); + ApplicationSummary.addApplicationId(respBuilder, appId); + ApplicationSummary.addGameId(respBuilder, appGameId); + ApplicationSummary.addApplicantUserId(respBuilder, applicant); + ApplicationSummary.addRaceName(respBuilder, race); + ApplicationSummary.addStatus(respBuilder, status); + ApplicationSummary.addCreatedAtMs(respBuilder, 1_770_000_000_000n); + ApplicationSummary.addDecidedAtMs(respBuilder, 0n); + const app = ApplicationSummary.endApplicationSummary(respBuilder); + ApplicationSubmitResponse.startApplicationSubmitResponse(respBuilder); + ApplicationSubmitResponse.addApplication(respBuilder, app); + respBuilder.finish(ApplicationSubmitResponse.endApplicationSubmitResponse(respBuilder)); + const respDecoded = ApplicationSubmitResponse.getRootAsApplicationSubmitResponse( + new ByteBuffer(respBuilder.asUint8Array()), + ); + const application = respDecoded.application(); + expect(application).not.toBeNull(); + expect(application!.applicationId()).toBe("app-3"); + expect(application!.status()).toBe("pending"); + }); + + test("InviteRedeem and InviteDecline requests round-trip", () => { + for (const ctor of [InviteRedeemRequest, InviteDeclineRequest] as const) { + const builder = new Builder(128); + const gameId = builder.createString("private-1"); + const inviteId = builder.createString("invite-1"); + if (ctor === InviteRedeemRequest) { + InviteRedeemRequest.startInviteRedeemRequest(builder); + InviteRedeemRequest.addGameId(builder, gameId); + InviteRedeemRequest.addInviteId(builder, inviteId); + builder.finish(InviteRedeemRequest.endInviteRedeemRequest(builder)); + const decoded = InviteRedeemRequest.getRootAsInviteRedeemRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.gameId()).toBe("private-1"); + expect(decoded.inviteId()).toBe("invite-1"); + } else { + InviteDeclineRequest.startInviteDeclineRequest(builder); + InviteDeclineRequest.addGameId(builder, gameId); + InviteDeclineRequest.addInviteId(builder, inviteId); + builder.finish(InviteDeclineRequest.endInviteDeclineRequest(builder)); + const decoded = InviteDeclineRequest.getRootAsInviteDeclineRequest( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.gameId()).toBe("private-1"); + expect(decoded.inviteId()).toBe("invite-1"); + } + } + }); + + test("InviteRedeemResponse and InviteDeclineResponse carry an InviteSummary", () => { + for (const status of ["accepted", "declined"]) { + const builder = new Builder(128); + const inviteId = builder.createString("invite-1"); + const gameId = builder.createString("private-1"); + const inviter = builder.createString("user-host"); + const invited = builder.createString("user-1"); + const code = builder.createString(""); + const race = builder.createString("Vegan Federation"); + const statusStr = builder.createString(status); + InviteSummary.startInviteSummary(builder); + InviteSummary.addInviteId(builder, inviteId); + InviteSummary.addGameId(builder, gameId); + InviteSummary.addInviterUserId(builder, inviter); + InviteSummary.addInvitedUserId(builder, invited); + InviteSummary.addCode(builder, code); + InviteSummary.addRaceName(builder, race); + InviteSummary.addStatus(builder, statusStr); + InviteSummary.addCreatedAtMs(builder, 1_770_000_000_000n); + InviteSummary.addExpiresAtMs(builder, 1_780_000_000_000n); + InviteSummary.addDecidedAtMs(builder, 1_770_010_000_000n); + const summary = InviteSummary.endInviteSummary(builder); + + if (status === "accepted") { + InviteRedeemResponse.startInviteRedeemResponse(builder); + InviteRedeemResponse.addInvite(builder, summary); + builder.finish(InviteRedeemResponse.endInviteRedeemResponse(builder)); + const decoded = InviteRedeemResponse.getRootAsInviteRedeemResponse( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.invite()?.status()).toBe("accepted"); + } else { + InviteDeclineResponse.startInviteDeclineResponse(builder); + InviteDeclineResponse.addInvite(builder, summary); + builder.finish(InviteDeclineResponse.endInviteDeclineResponse(builder)); + const decoded = InviteDeclineResponse.getRootAsInviteDeclineResponse( + new ByteBuffer(builder.asUint8Array()), + ); + expect(decoded.invite()?.status()).toBe("declined"); + } + } + }); + + test("ErrorResponse round-trips a code/message pair", () => { + const builder = new Builder(128); + const code = builder.createString("conflict"); + const message = builder.createString("request conflicts with current state"); + ErrorBody.startErrorBody(builder); + ErrorBody.addCode(builder, code); + ErrorBody.addMessage(builder, message); + const errorOff = ErrorBody.endErrorBody(builder); + ErrorResponse.startErrorResponse(builder); + ErrorResponse.addError(builder, errorOff); + builder.finish(ErrorResponse.endErrorResponse(builder)); + const decoded = ErrorResponse.getRootAsErrorResponse( + new ByteBuffer(builder.asUint8Array()), + ); + const error = decoded.error(); + expect(error).not.toBeNull(); + expect(error!.code()).toBe("conflict"); + expect(error!.message()).toBe("request conflicts with current state"); + }); +}); diff --git a/ui/frontend/tests/lobby-page.test.ts b/ui/frontend/tests/lobby-page.test.ts new file mode 100644 index 0000000..0394008 --- /dev/null +++ b/ui/frontend/tests/lobby-page.test.ts @@ -0,0 +1,361 @@ +// Component tests for the Phase 8 lobby page. The lobby API and the +// gateway client are mocked at module level; the session singleton is +// wired to a per-test `SessionStore`-backing IndexedDB so the page's +// boot path settles on `authenticated` and constructs a real +// GalaxyClient (which is then never called because the lobby API +// wrappers are stubs). The tests assert the section rendering, the +// inline race-name form for public games, and the invitation Accept +// flow. + +import "fake-indexeddb/auto"; +import { fireEvent, render, waitFor } from "@testing-library/svelte"; +import { + afterEach, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; +import type { IDBPDatabase } from "idb"; + +import { i18n } from "../src/lib/i18n/index.svelte"; +import { session } from "../src/lib/session-store.svelte"; +import { type GalaxyDB, openGalaxyDB } from "../src/platform/store/idb"; +import { IDBCache } from "../src/platform/store/idb-cache"; +import { WebCryptoKeyStore } from "../src/platform/store/webcrypto-keystore"; + +vi.mock("$app/navigation", () => ({ + goto: vi.fn(async () => {}), +})); + +const listMyGamesSpy = vi.fn(); +const listPublicGamesSpy = vi.fn(); +const listMyInvitesSpy = vi.fn(); +const listMyApplicationsSpy = vi.fn(); +const submitApplicationSpy = vi.fn(); +const redeemInviteSpy = vi.fn(); +const declineInviteSpy = vi.fn(); + +vi.mock("../src/api/lobby", async () => { + const actual = await vi.importActual( + "../src/api/lobby", + ); + return { + ...actual, + listMyGames: (...args: unknown[]) => listMyGamesSpy(...args), + listPublicGames: (...args: unknown[]) => listPublicGamesSpy(...args), + listMyInvites: (...args: unknown[]) => listMyInvitesSpy(...args), + listMyApplications: (...args: unknown[]) => listMyApplicationsSpy(...args), + submitApplication: (...args: unknown[]) => submitApplicationSpy(...args), + redeemInvite: (...args: unknown[]) => redeemInviteSpy(...args), + declineInvite: (...args: unknown[]) => declineInviteSpy(...args), + }; +}); + +vi.mock("../src/lib/env", () => ({ + GATEWAY_BASE_URL: "http://gateway.test", + GATEWAY_RESPONSE_PUBLIC_KEY: new Uint8Array(32).fill(0x55), +})); + +vi.mock("../src/api/connect", () => ({ + createEdgeGatewayClient: vi.fn(() => ({})), +})); + +vi.mock("../src/api/galaxy-client", () => { + class FakeGalaxyClient { + executeCommand = vi.fn(async () => ({ + resultCode: "ok", + payloadBytes: new Uint8Array(), + })); + } + return { GalaxyClient: FakeGalaxyClient }; +}); + +vi.mock("../src/platform/core/index", () => ({ + loadCore: async () => ({ + signRequest: () => new Uint8Array(), + verifyResponse: () => true, + verifyEvent: () => true, + verifyPayloadHash: () => true, + }), +})); + +let db: IDBPDatabase; +let dbName: string; + +beforeEach(async () => { + dbName = `galaxy-ui-test-${crypto.randomUUID()}`; + db = await openGalaxyDB(dbName); + const store = { + keyStore: new WebCryptoKeyStore(db), + cache: new IDBCache(db), + }; + session.resetForTests(); + session.setStoreLoaderForTests(async () => store); + await session.init(); + await session.signIn("device-1"); + i18n.resetForTests("en"); + + listMyGamesSpy.mockReset(); + listPublicGamesSpy.mockReset(); + listMyInvitesSpy.mockReset(); + listMyApplicationsSpy.mockReset(); + submitApplicationSpy.mockReset(); + redeemInviteSpy.mockReset(); + declineInviteSpy.mockReset(); +}); + +afterEach(async () => { + session.resetForTests(); + i18n.resetForTests("en"); + db.close(); + await new Promise((resolve) => { + const req = indexedDB.deleteDatabase(dbName); + req.onsuccess = () => resolve(); + req.onerror = () => resolve(); + req.onblocked = () => resolve(); + }); +}); + +async function importLobbyPage(): Promise { + return import("../src/routes/lobby/+page.svelte"); +} + +const baseDate = new Date("2026-05-07T10:00:00Z"); + +function makeGame(id: string, name: string, status = "draft") { + return { + gameId: id, + gameName: name, + gameType: "private", + status, + ownerUserId: "user-1", + minPlayers: 2, + maxPlayers: 8, + enrollmentEndsAt: baseDate, + createdAt: baseDate, + updatedAt: baseDate, + }; +} + +function makePublicGame(id: string, name: string) { + return { + gameId: id, + gameName: name, + gameType: "public", + status: "enrollment_open", + ownerUserId: "", + minPlayers: 4, + maxPlayers: 12, + enrollmentEndsAt: baseDate, + createdAt: baseDate, + updatedAt: baseDate, + }; +} + +function makeInvite(id: string) { + return { + inviteId: id, + gameId: "private-1", + inviterUserId: "host", + invitedUserId: "user-1", + code: "", + raceName: "Vegan Federation", + status: "pending", + createdAt: baseDate, + expiresAt: baseDate, + decidedAt: null, + }; +} + +function makeApplication(id: string, status: string) { + return { + applicationId: id, + gameId: "public-1", + applicantUserId: "user-1", + raceName: "Vegan Federation", + status, + createdAt: baseDate, + decidedAt: status === "pending" ? null : baseDate, + }; +} + +describe("lobby page", () => { + test("renders empty states for every section when API returns no items", async () => { + listMyGamesSpy.mockResolvedValue([]); + listPublicGamesSpy.mockResolvedValue({ items: [], page: 1, pageSize: 50, total: 0 }); + listMyInvitesSpy.mockResolvedValue([]); + listMyApplicationsSpy.mockResolvedValue([]); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => { + expect(ui.getByTestId("lobby-my-games-empty")).toBeInTheDocument(); + expect(ui.getByTestId("lobby-invitations-empty")).toBeInTheDocument(); + expect(ui.getByTestId("lobby-applications-empty")).toBeInTheDocument(); + expect(ui.getByTestId("lobby-public-games-empty")).toBeInTheDocument(); + }); + }); + + test("renders my-game cards and public-game cards when items are present", async () => { + listMyGamesSpy.mockResolvedValue([makeGame("private-1", "First Contact")]); + listPublicGamesSpy.mockResolvedValue({ + items: [makePublicGame("public-1", "Open Lobby")], + page: 1, + pageSize: 50, + total: 1, + }); + listMyInvitesSpy.mockResolvedValue([]); + listMyApplicationsSpy.mockResolvedValue([]); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => { + expect(ui.getAllByTestId("lobby-my-game-card").length).toBe(1); + expect(ui.getByText("First Contact")).toBeInTheDocument(); + expect(ui.getByText("Open Lobby")).toBeInTheDocument(); + }); + }); + + test("submitting an application opens the inline form and posts race_name", async () => { + listMyGamesSpy.mockResolvedValue([]); + listPublicGamesSpy.mockResolvedValue({ + items: [makePublicGame("public-1", "Open Lobby")], + page: 1, + pageSize: 50, + total: 1, + }); + listMyInvitesSpy.mockResolvedValue([]); + listMyApplicationsSpy.mockResolvedValue([]); + submitApplicationSpy.mockResolvedValue(makeApplication("app-1", "pending")); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => { + expect(ui.getByTestId("lobby-public-game-apply")).toBeInTheDocument(); + }); + + await fireEvent.click(ui.getByTestId("lobby-public-game-apply")); + + await waitFor(() => { + expect(ui.getByTestId("lobby-application-form")).toBeInTheDocument(); + }); + + await fireEvent.input(ui.getByTestId("lobby-application-race-name"), { + target: { value: "Vegan Federation" }, + }); + await fireEvent.click(ui.getByTestId("lobby-application-submit")); + + await waitFor(() => { + expect(submitApplicationSpy).toHaveBeenCalledWith( + expect.anything(), + "public-1", + "Vegan Federation", + ); + expect(ui.getByTestId("lobby-application-card")).toBeInTheDocument(); + }); + }); + + test("submitting an empty race name surfaces a validation error and does not call the API", async () => { + listMyGamesSpy.mockResolvedValue([]); + listPublicGamesSpy.mockResolvedValue({ + items: [makePublicGame("public-1", "Open Lobby")], + page: 1, + pageSize: 50, + total: 1, + }); + listMyInvitesSpy.mockResolvedValue([]); + listMyApplicationsSpy.mockResolvedValue([]); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => + expect(ui.getByTestId("lobby-public-game-apply")).toBeInTheDocument(), + ); + await fireEvent.click(ui.getByTestId("lobby-public-game-apply")); + await fireEvent.click(ui.getByTestId("lobby-application-submit")); + + await waitFor(() => { + expect(ui.getByTestId("lobby-application-error")).toBeInTheDocument(); + expect(submitApplicationSpy).not.toHaveBeenCalled(); + }); + }); + + test("accepting an invitation calls redeemInvite and removes the card", async () => { + listMyGamesSpy.mockResolvedValue([]); + listPublicGamesSpy.mockResolvedValue({ items: [], page: 1, pageSize: 50, total: 0 }); + listMyInvitesSpy.mockResolvedValue([makeInvite("invite-1")]); + listMyApplicationsSpy.mockResolvedValue([]); + redeemInviteSpy.mockResolvedValue(makeInvite("invite-1")); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => + expect(ui.getByTestId("lobby-invite-accept")).toBeInTheDocument(), + ); + + await fireEvent.click(ui.getByTestId("lobby-invite-accept")); + + await waitFor(() => { + expect(redeemInviteSpy).toHaveBeenCalledWith( + expect.anything(), + "private-1", + "invite-1", + ); + expect(ui.queryByTestId("lobby-invite-accept")).not.toBeInTheDocument(); + expect(ui.getByTestId("lobby-invitations-empty")).toBeInTheDocument(); + }); + }); + + test("declining an invitation calls declineInvite and removes the card", async () => { + listMyGamesSpy.mockResolvedValue([]); + listPublicGamesSpy.mockResolvedValue({ items: [], page: 1, pageSize: 50, total: 0 }); + listMyInvitesSpy.mockResolvedValue([makeInvite("invite-2")]); + listMyApplicationsSpy.mockResolvedValue([]); + declineInviteSpy.mockResolvedValue({ ...makeInvite("invite-2"), status: "declined" }); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => + expect(ui.getByTestId("lobby-invite-decline")).toBeInTheDocument(), + ); + + await fireEvent.click(ui.getByTestId("lobby-invite-decline")); + + await waitFor(() => { + expect(declineInviteSpy).toHaveBeenCalledWith( + expect.anything(), + "private-1", + "invite-2", + ); + expect(ui.queryByTestId("lobby-invite-decline")).not.toBeInTheDocument(); + }); + }); + + test("application status badges localise pending and approved states", async () => { + listMyGamesSpy.mockResolvedValue([]); + listPublicGamesSpy.mockResolvedValue({ items: [], page: 1, pageSize: 50, total: 0 }); + listMyInvitesSpy.mockResolvedValue([]); + listMyApplicationsSpy.mockResolvedValue([ + makeApplication("app-1", "pending"), + makeApplication("app-2", "approved"), + ]); + + const Page = (await importLobbyPage()).default; + const ui = render(Page); + + await waitFor(() => { + const cards = ui.getAllByTestId("lobby-application-card"); + expect(cards.length).toBe(2); + expect(cards[0]!.querySelector(".status")?.textContent?.trim()).toBe("pending"); + expect(cards[1]!.querySelector(".status")?.textContent?.trim()).toBe("approved"); + }); + }); +}); diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index fe2781b..8b1e235 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: frontend: dependencies: + flatbuffers: + specifier: ^25.9.23 + version: 25.9.23 idb: specifier: ^8.0.3 version: 8.0.3 @@ -577,6 +580,9 @@ packages: picomatch: optional: true + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -1515,6 +1521,8 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + flatbuffers@25.9.23: {} + form-data@4.0.5: dependencies: asynckit: 0.4.0