Phase 28: diplomatic mail UI
Merges feat/ui-stage-28 into development.
This commit was merged in pull request #11.
This commit is contained in:
@@ -7,6 +7,12 @@ name: Deploy · Dev
|
||||
# `integration` as part of the PR that produced this push, so this
|
||||
# workflow does not re-run those tests — it focuses on packaging and
|
||||
# rollout.
|
||||
#
|
||||
# `workflow_dispatch` is also accepted so a developer can deploy any
|
||||
# branch (typically a feature branch under active review) into the
|
||||
# shared dev environment from the Gitea Actions UI without waiting for
|
||||
# the PR to merge first. The deploy job picks up whatever the chosen
|
||||
# ref is — same packaging + healthcheck steps as the merge path.
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -23,6 +29,7 @@ on:
|
||||
- 'tools/dev-deploy/**'
|
||||
- '.gitea/workflows/dev-deploy.yaml'
|
||||
- '!**/*.md'
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
@@ -62,6 +69,11 @@ jobs:
|
||||
working-directory: ui/frontend
|
||||
env:
|
||||
VITE_GATEWAY_BASE_URL: https://api.galaxy.lan
|
||||
# Surface the synthetic-report loader and similar dev-only
|
||||
# affordances in the long-lived dev bundle. The prod build
|
||||
# path (`prod-build.yaml`) leaves this flag unset so the
|
||||
# production bundle keeps the same affordances stripped.
|
||||
VITE_GALAXY_DEV_AFFORDANCES: "true"
|
||||
run: |
|
||||
# The response-signing public key is committed in
|
||||
# `.env.development` alongside its private counterpart in
|
||||
|
||||
@@ -513,6 +513,52 @@ func TestConfirmEmailCodeWrongCode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfirmEmailCodeDevFixedCodeBypassesAttemptsCeiling proves the
|
||||
// dev-mode override is a true escape hatch: a developer who already
|
||||
// burned past ChallengeMaxAttempts on a long-lived dev challenge
|
||||
// (typically because the throttle merged repeated send-email-code
|
||||
// calls onto one challenge_id) can still recover by submitting the
|
||||
// fixed code without first waiting out the challenge TTL.
|
||||
func TestConfirmEmailCodeDevFixedCodeBypassesAttemptsCeiling(t *testing.T) {
|
||||
db := startPostgres(t)
|
||||
cfg := authConfig()
|
||||
cfg.DevFixedCode = "999999"
|
||||
svc := buildServiceWithConfig(t, db, cfg)
|
||||
ctx := context.Background()
|
||||
|
||||
id, err := svc.SendEmailCode(ctx, "dev-bypass-ceiling@example.test", "en", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("send: %v", err)
|
||||
}
|
||||
|
||||
// Burn through the attempts ceiling with deliberately wrong codes.
|
||||
for i := range cfg.ChallengeMaxAttempts + 1 {
|
||||
_, err := svc.ConfirmEmailCode(ctx, auth.ConfirmInputs{
|
||||
ChallengeID: id,
|
||||
Code: "111111",
|
||||
ClientPublicKey: randomKey(t),
|
||||
TimeZone: "UTC",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("attempt %d unexpectedly succeeded", i)
|
||||
}
|
||||
}
|
||||
|
||||
// The dev-fixed code still goes through.
|
||||
session, err := svc.ConfirmEmailCode(ctx, auth.ConfirmInputs{
|
||||
ChallengeID: id,
|
||||
Code: "999999",
|
||||
ClientPublicKey: randomKey(t),
|
||||
TimeZone: "UTC",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("dev-fixed-code after attempts exhausted: %v", err)
|
||||
}
|
||||
if session.DeviceSessionID == uuid.Nil {
|
||||
t.Fatalf("dev-fixed-code did not produce a session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmEmailCodeAttemptsCeiling(t *testing.T) {
|
||||
db := startPostgres(t)
|
||||
svc, mailer, _, _ := buildService(t, db)
|
||||
|
||||
@@ -163,15 +163,28 @@ func (s *Service) ConfirmEmailCode(ctx context.Context, in ConfirmInputs) (Sessi
|
||||
return Session{}, err
|
||||
}
|
||||
|
||||
if int(loaded.Attempts) > s.deps.Config.ChallengeMaxAttempts {
|
||||
s.deps.Logger.Info("auth challenge attempts exhausted",
|
||||
// The dev-mode fixed-code override is checked first so it bypasses
|
||||
// both the bcrypt verify and the per-challenge attempts ceiling.
|
||||
// Without this, a developer who already burned through
|
||||
// `ChallengeMaxAttempts` on an existing un-consumed challenge —
|
||||
// for example after the throttle merged repeated send-email-code
|
||||
// calls onto one challenge_id — could not recover with the fixed
|
||||
// code either, defeating the purpose of the override. Production
|
||||
// deployments leave `DevFixedCode` empty, so this branch is
|
||||
// inert and the regular attempts gate still applies.
|
||||
if s.devFixedCodeMatches(in.Code) {
|
||||
s.deps.Logger.Warn("auth challenge accepted via dev-mode fixed code override",
|
||||
zap.String("challenge_id", in.ChallengeID.String()),
|
||||
zap.Int32("attempts", loaded.Attempts),
|
||||
)
|
||||
return Session{}, ErrTooManyAttempts
|
||||
}
|
||||
|
||||
if !s.devFixedCodeMatches(in.Code) {
|
||||
} else {
|
||||
if int(loaded.Attempts) > s.deps.Config.ChallengeMaxAttempts {
|
||||
s.deps.Logger.Info("auth challenge attempts exhausted",
|
||||
zap.String("challenge_id", in.ChallengeID.String()),
|
||||
zap.Int32("attempts", loaded.Attempts),
|
||||
)
|
||||
return Session{}, ErrTooManyAttempts
|
||||
}
|
||||
if err := verifyCode(loaded.CodeHash, in.Code); err != nil {
|
||||
if errors.Is(err, ErrCodeMismatch) {
|
||||
s.deps.Logger.Info("auth challenge code mismatch",
|
||||
@@ -182,10 +195,6 @@ func (s *Service) ConfirmEmailCode(ctx context.Context, in ConfirmInputs) (Sessi
|
||||
}
|
||||
return Session{}, err
|
||||
}
|
||||
} else {
|
||||
s.deps.Logger.Warn("auth challenge accepted via dev-mode fixed code override",
|
||||
zap.String("challenge_id", in.ChallengeID.String()),
|
||||
)
|
||||
}
|
||||
|
||||
// Re-check permanent_block after verifying the code. SendEmailCode
|
||||
|
||||
@@ -26,7 +26,10 @@ Three Postgres tables in the `backend` schema:
|
||||
|
||||
- `diplomail_messages` — one row per send (personal, admin, or
|
||||
system). Captures `game_name` and IP at insert time so audit
|
||||
rendering survives renames and purges.
|
||||
rendering survives renames and purges. The `sender_race_name`
|
||||
column snapshots the sender's race in the game at send time when
|
||||
the sender is a player with an active membership; the in-game UI
|
||||
keys per-race thread grouping on this column.
|
||||
- `diplomail_recipients` — one row per (message, recipient). Holds
|
||||
per-user `read_at`, `deleted_at`, `delivered_at`, `notified_at`
|
||||
state. Snapshot fields (`recipient_user_name`,
|
||||
@@ -72,6 +75,24 @@ mail to every active member; `Service.changeMembershipStatus` /
|
||||
`detector.LanguageDetector` (default: `whatlanggo`, body-only,
|
||||
≥ 25 runes; shorter bodies stay `und`).
|
||||
|
||||
## Recipient selection
|
||||
|
||||
`POST /messages` and `POST /admin` (when `target="user"`) accept the
|
||||
recipient identifier in one of two shapes:
|
||||
|
||||
- `recipient_user_id` (uuid) — explicit user lookup; the recipient
|
||||
may be any active member of the game.
|
||||
- `recipient_race_name` (string) — resolves to the active member
|
||||
with this race name in the game. Race names are unique by lobby
|
||||
invariant; lobby-removed and blocked members cannot be reached
|
||||
through the race-name shortcut (they no longer appear in the
|
||||
active scope). Exactly one of the two fields must be supplied;
|
||||
supplying both, or neither, returns `invalid_request`.
|
||||
|
||||
The race-name path lets the in-game UI compose mail directly off
|
||||
the engine's `report.races[]` view without an extra membership
|
||||
round-trip.
|
||||
|
||||
## Translation
|
||||
|
||||
Stage D adds a lazy translation cache. When a recipient reads a
|
||||
|
||||
@@ -29,7 +29,11 @@ func (s *Service) SendAdminPersonal(ctx context.Context, in SendAdminPersonalInp
|
||||
return Message{}, Recipient{}, err
|
||||
}
|
||||
|
||||
recipient, err := s.deps.Memberships.GetMembershipAnyStatus(ctx, in.GameID, in.RecipientUserID)
|
||||
recipientID, err := s.resolveActiveRecipient(ctx, in.GameID, in.RecipientUserID, in.RecipientRaceName)
|
||||
if err != nil {
|
||||
return Message{}, Recipient{}, err
|
||||
}
|
||||
recipient, err := s.deps.Memberships.GetMembershipAnyStatus(ctx, in.GameID, recipientID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return Message{}, Recipient{}, fmt.Errorf("%w: recipient is not a member of the game", ErrForbidden)
|
||||
@@ -37,7 +41,7 @@ func (s *Service) SendAdminPersonal(ctx context.Context, in SendAdminPersonalInp
|
||||
return Message{}, Recipient{}, fmt.Errorf("diplomail: load admin recipient: %w", err)
|
||||
}
|
||||
|
||||
msgInsert, err := s.buildAdminMessageInsert(in.CallerKind, in.CallerUserID, in.CallerUsername,
|
||||
msgInsert, err := s.buildAdminMessageInsert(ctx, in.CallerKind, in.CallerUserID, in.CallerUsername,
|
||||
recipient.GameID, recipient.GameName, subject, body, in.SenderIP, BroadcastScopeSingle)
|
||||
if err != nil {
|
||||
return Message{}, Recipient{}, err
|
||||
@@ -84,7 +88,7 @@ func (s *Service) SendAdminBroadcast(ctx context.Context, in SendAdminBroadcastI
|
||||
}
|
||||
|
||||
gameName := members[0].GameName
|
||||
msgInsert, err := s.buildAdminMessageInsert(in.CallerKind, in.CallerUserID, in.CallerUsername,
|
||||
msgInsert, err := s.buildAdminMessageInsert(ctx, in.CallerKind, in.CallerUserID, in.CallerUsername,
|
||||
in.GameID, gameName, subject, body, in.SenderIP, BroadcastScopeGameBroadcast)
|
||||
if err != nil {
|
||||
return Message{}, nil, err
|
||||
@@ -147,6 +151,7 @@ func (s *Service) SendPlayerBroadcast(ctx context.Context, in SendPlayerBroadcas
|
||||
}
|
||||
|
||||
username := sender.UserName
|
||||
senderRace := sender.RaceName
|
||||
msgInsert := MessageInsert{
|
||||
MessageID: uuid.New(),
|
||||
GameID: in.GameID,
|
||||
@@ -155,6 +160,7 @@ func (s *Service) SendPlayerBroadcast(ctx context.Context, in SendPlayerBroadcas
|
||||
SenderKind: SenderKindPlayer,
|
||||
SenderUserID: &callerID,
|
||||
SenderUsername: &username,
|
||||
SenderRaceName: &senderRace,
|
||||
SenderIP: in.SenderIP,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
@@ -217,7 +223,7 @@ func (s *Service) SendAdminMultiGameBroadcast(ctx context.Context, in SendMultiG
|
||||
zap.String("scope", scope))
|
||||
continue
|
||||
}
|
||||
msgInsert, err := s.buildAdminMessageInsert(CallerKindAdmin, nil, in.CallerUsername,
|
||||
msgInsert, err := s.buildAdminMessageInsert(ctx, CallerKindAdmin, nil, in.CallerUsername,
|
||||
game.GameID, game.GameName, subject, body, in.SenderIP, BroadcastScopeMultiGameBroadcast)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -356,7 +362,7 @@ func (s *Service) publishGameLifecycle(ctx context.Context, ev LifecycleEvent) e
|
||||
gameName := members[0].GameName
|
||||
subject, body := renderGameLifecycle(ev.Kind, gameName, ev.Actor, ev.Reason)
|
||||
|
||||
msgInsert, err := s.buildAdminMessageInsert(CallerKindSystem, nil, "",
|
||||
msgInsert, err := s.buildAdminMessageInsert(ctx, CallerKindSystem, nil, "",
|
||||
ev.GameID, gameName, subject, body, "", BroadcastScopeGameBroadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -385,7 +391,7 @@ func (s *Service) publishMembershipLifecycle(ctx context.Context, ev LifecycleEv
|
||||
}
|
||||
subject, body := renderMembershipLifecycle(ev.Kind, target.GameName, ev.Actor, ev.Reason)
|
||||
|
||||
msgInsert, err := s.buildAdminMessageInsert(CallerKindSystem, nil, "",
|
||||
msgInsert, err := s.buildAdminMessageInsert(ctx, CallerKindSystem, nil, "",
|
||||
ev.GameID, target.GameName, subject, body, "", BroadcastScopeSingle)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -417,10 +423,12 @@ func (s *Service) prepareContent(subject, body string) (string, string, error) {
|
||||
// for every admin-kind send. The CHECK constraint maps sender
|
||||
// shapes:
|
||||
//
|
||||
// sender_kind='player' → CallerKind owner; sender_user_id set
|
||||
// sender_kind='player' → CallerKind owner; sender_user_id set,
|
||||
// sender_race_name resolved from
|
||||
// Memberships.GetActiveMembership
|
||||
// sender_kind='admin' → CallerKind admin; sender_user_id nil
|
||||
// sender_kind='system' → CallerKind system; sender_username nil
|
||||
func (s *Service) buildAdminMessageInsert(callerKind string, callerUserID *uuid.UUID, callerUsername string,
|
||||
func (s *Service) buildAdminMessageInsert(ctx context.Context, callerKind string, callerUserID *uuid.UUID, callerUsername string,
|
||||
gameID uuid.UUID, gameName, subject, body, senderIP, scope string) (MessageInsert, error) {
|
||||
out := MessageInsert{
|
||||
MessageID: uuid.New(),
|
||||
@@ -443,6 +451,17 @@ func (s *Service) buildAdminMessageInsert(callerKind string, callerUserID *uuid.
|
||||
out.SenderKind = SenderKindPlayer
|
||||
out.SenderUserID = &uid
|
||||
out.SenderUsername = &uname
|
||||
// Owner race snapshot is best-effort: a private-game owner who
|
||||
// has an active membership in their own game contributes a
|
||||
// race name; an owner who is not a current member (or whose
|
||||
// membership is removed/blocked) leaves the field nil. The
|
||||
// CHECK constraint accepts both shapes for sender_kind='player'.
|
||||
if ownerMember, err := s.deps.Memberships.GetActiveMembership(ctx, gameID, uid); err == nil {
|
||||
race := ownerMember.RaceName
|
||||
out.SenderRaceName = &race
|
||||
} else if !errors.Is(err, ErrNotFound) {
|
||||
return MessageInsert{}, fmt.Errorf("diplomail: load owner membership: %w", err)
|
||||
}
|
||||
case CallerKindAdmin:
|
||||
uname := callerUsername
|
||||
out.SenderKind = SenderKindAdmin
|
||||
|
||||
@@ -369,6 +369,114 @@ func TestDiplomailPersonalFlow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiplomailPersonalByRaceName exercises the Phase 28 contract: the
|
||||
// UI passes a recipient race name (read out of the game report); the
|
||||
// service resolves it to the active member with that race name and
|
||||
// snapshots the sender's race onto the message row. Error cases cover
|
||||
// the validation rules baked into the wire schema.
|
||||
func TestDiplomailPersonalByRaceName(t *testing.T) {
|
||||
db := startPostgres(t)
|
||||
ctx := context.Background()
|
||||
|
||||
gameID := uuid.New()
|
||||
sender := uuid.New()
|
||||
recipient := uuid.New()
|
||||
kicked := uuid.New()
|
||||
seedAccount(t, db, sender)
|
||||
seedAccount(t, db, recipient)
|
||||
seedAccount(t, db, kicked)
|
||||
seedGame(t, db, gameID, "Race-Name Resolution Game")
|
||||
|
||||
lookup := &staticMembershipLookup{
|
||||
rows: map[[2]uuid.UUID]diplomail.ActiveMembership{
|
||||
{gameID, sender}: {
|
||||
UserID: sender, GameID: gameID, GameName: "Race-Name Resolution Game",
|
||||
UserName: "sender", RaceName: "Senders",
|
||||
},
|
||||
{gameID, recipient}: {
|
||||
UserID: recipient, GameID: gameID, GameName: "Race-Name Resolution Game",
|
||||
UserName: "recipient", RaceName: "Receivers",
|
||||
},
|
||||
},
|
||||
inactive: map[[2]uuid.UUID]diplomail.MemberSnapshot{
|
||||
{gameID, kicked}: {
|
||||
UserID: kicked, GameID: gameID, GameName: "Race-Name Resolution Game",
|
||||
UserName: "kicked", RaceName: "Departed", Status: "removed",
|
||||
},
|
||||
},
|
||||
}
|
||||
svc := diplomail.NewService(diplomail.Deps{
|
||||
Store: diplomail.NewStore(db),
|
||||
Memberships: lookup,
|
||||
Notification: &recordingPublisher{},
|
||||
Config: config.DiplomailConfig{
|
||||
MaxBodyBytes: 4096,
|
||||
MaxSubjectBytes: 256,
|
||||
},
|
||||
})
|
||||
|
||||
// Happy path: race name resolves and sender_race_name is snapshotted.
|
||||
msg, rcpt, err := svc.SendPersonal(ctx, diplomail.SendPersonalInput{
|
||||
GameID: gameID,
|
||||
SenderUserID: sender,
|
||||
RecipientRaceName: "Receivers",
|
||||
Subject: "Trade",
|
||||
Body: "Care to talk?",
|
||||
SenderIP: "203.0.113.4",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send by race name: %v", err)
|
||||
}
|
||||
if rcpt.UserID != recipient {
|
||||
t.Fatalf("recipient = %s, want %s", rcpt.UserID, recipient)
|
||||
}
|
||||
if msg.SenderRaceName == nil || *msg.SenderRaceName != "Senders" {
|
||||
t.Fatalf("sender_race_name = %v, want \"Senders\"", msg.SenderRaceName)
|
||||
}
|
||||
|
||||
// Both identifiers supplied → invalid_request.
|
||||
if _, _, err := svc.SendPersonal(ctx, diplomail.SendPersonalInput{
|
||||
GameID: gameID,
|
||||
SenderUserID: sender,
|
||||
RecipientUserID: recipient,
|
||||
RecipientRaceName: "Receivers",
|
||||
Body: "x",
|
||||
}); !errors.Is(err, diplomail.ErrInvalidInput) {
|
||||
t.Fatalf("dual identifier: %v, want ErrInvalidInput", err)
|
||||
}
|
||||
|
||||
// Neither identifier supplied → invalid_request.
|
||||
if _, _, err := svc.SendPersonal(ctx, diplomail.SendPersonalInput{
|
||||
GameID: gameID,
|
||||
SenderUserID: sender,
|
||||
Body: "x",
|
||||
}); !errors.Is(err, diplomail.ErrInvalidInput) {
|
||||
t.Fatalf("no identifier: %v, want ErrInvalidInput", err)
|
||||
}
|
||||
|
||||
// Race name with no matching active member → invalid_request.
|
||||
if _, _, err := svc.SendPersonal(ctx, diplomail.SendPersonalInput{
|
||||
GameID: gameID,
|
||||
SenderUserID: sender,
|
||||
RecipientRaceName: "Strangers",
|
||||
Body: "x",
|
||||
}); !errors.Is(err, diplomail.ErrInvalidInput) {
|
||||
t.Fatalf("unknown race: %v, want ErrInvalidInput", err)
|
||||
}
|
||||
|
||||
// Race name of a lobby-removed member → invalid_request (the
|
||||
// active-only scope filters them out; the lookup never returns
|
||||
// them).
|
||||
if _, _, err := svc.SendPersonal(ctx, diplomail.SendPersonalInput{
|
||||
GameID: gameID,
|
||||
SenderUserID: sender,
|
||||
RecipientRaceName: "Departed",
|
||||
Body: "x",
|
||||
}); !errors.Is(err, diplomail.ErrInvalidInput) {
|
||||
t.Fatalf("kicked race: %v, want ErrInvalidInput", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiplomailRejectsNonActiveSender(t *testing.T) {
|
||||
db := startPostgres(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -32,15 +32,20 @@ const previewMaxRunes = 120
|
||||
// ErrForbidden; the inserted Message is never persisted in those
|
||||
// cases.
|
||||
func (s *Service) SendPersonal(ctx context.Context, in SendPersonalInput) (Message, Recipient, error) {
|
||||
if in.SenderUserID == in.RecipientUserID {
|
||||
return Message{}, Recipient{}, fmt.Errorf("%w: cannot send mail to yourself", ErrInvalidInput)
|
||||
}
|
||||
subject := strings.TrimRight(in.Subject, " \t")
|
||||
body := strings.TrimRight(in.Body, " \t\n")
|
||||
if err := s.validateContent(subject, body); err != nil {
|
||||
return Message{}, Recipient{}, err
|
||||
}
|
||||
|
||||
recipientID, err := s.resolveActiveRecipient(ctx, in.GameID, in.RecipientUserID, in.RecipientRaceName)
|
||||
if err != nil {
|
||||
return Message{}, Recipient{}, err
|
||||
}
|
||||
if in.SenderUserID == recipientID {
|
||||
return Message{}, Recipient{}, fmt.Errorf("%w: cannot send mail to yourself", ErrInvalidInput)
|
||||
}
|
||||
|
||||
sender, err := s.deps.Memberships.GetActiveMembership(ctx, in.GameID, in.SenderUserID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
@@ -48,7 +53,7 @@ func (s *Service) SendPersonal(ctx context.Context, in SendPersonalInput) (Messa
|
||||
}
|
||||
return Message{}, Recipient{}, fmt.Errorf("diplomail: load sender membership: %w", err)
|
||||
}
|
||||
recipient, err := s.deps.Memberships.GetActiveMembership(ctx, in.GameID, in.RecipientUserID)
|
||||
recipient, err := s.deps.Memberships.GetActiveMembership(ctx, in.GameID, recipientID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return Message{}, Recipient{}, fmt.Errorf("%w: recipient is not an active member of the game", ErrForbidden)
|
||||
@@ -57,14 +62,17 @@ func (s *Service) SendPersonal(ctx context.Context, in SendPersonalInput) (Messa
|
||||
}
|
||||
|
||||
username := sender.UserName
|
||||
senderRace := sender.RaceName
|
||||
senderUserID := in.SenderUserID
|
||||
msgInsert := MessageInsert{
|
||||
MessageID: uuid.New(),
|
||||
GameID: in.GameID,
|
||||
GameName: sender.GameName,
|
||||
Kind: KindPersonal,
|
||||
SenderKind: SenderKindPlayer,
|
||||
SenderUserID: &in.SenderUserID,
|
||||
SenderUserID: &senderUserID,
|
||||
SenderUsername: &username,
|
||||
SenderRaceName: &senderRace,
|
||||
SenderIP: in.SenderIP,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
@@ -75,7 +83,7 @@ func (s *Service) SendPersonal(ctx context.Context, in SendPersonalInput) (Messa
|
||||
rcptInsert := buildRecipientInsert(
|
||||
msgInsert.MessageID,
|
||||
MemberSnapshot{
|
||||
UserID: in.RecipientUserID,
|
||||
UserID: recipientID,
|
||||
GameID: in.GameID,
|
||||
GameName: recipient.GameName,
|
||||
UserName: recipient.UserName,
|
||||
@@ -101,6 +109,47 @@ func (s *Service) SendPersonal(ctx context.Context, in SendPersonalInput) (Messa
|
||||
return msg, recipients[0], nil
|
||||
}
|
||||
|
||||
// resolveActiveRecipient turns a (user_id, race_name) pair into the
|
||||
// canonical user id of an active member of gameID. Exactly one of the
|
||||
// two inputs must be set; both-set or both-empty returns
|
||||
// ErrInvalidInput. Race-name resolution is restricted to the active
|
||||
// scope so lobby-removed and blocked members cannot be reached
|
||||
// through the race-name shortcut. ErrInvalidInput is also returned
|
||||
// when the race name matches zero members; ErrForbidden when the
|
||||
// race name matches more than one active row (defence in depth — race
|
||||
// names are unique within a game by lobby invariant).
|
||||
func (s *Service) resolveActiveRecipient(ctx context.Context, gameID uuid.UUID, byUserID uuid.UUID, byRaceName string) (uuid.UUID, error) {
|
||||
byRaceName = strings.TrimSpace(byRaceName)
|
||||
hasUser := byUserID != uuid.Nil
|
||||
hasRace := byRaceName != ""
|
||||
switch {
|
||||
case hasUser && hasRace:
|
||||
return uuid.Nil, fmt.Errorf("%w: only one of recipient_user_id, recipient_race_name may be supplied", ErrInvalidInput)
|
||||
case !hasUser && !hasRace:
|
||||
return uuid.Nil, fmt.Errorf("%w: recipient_user_id or recipient_race_name must be supplied", ErrInvalidInput)
|
||||
case hasUser:
|
||||
return byUserID, nil
|
||||
}
|
||||
members, err := s.deps.Memberships.ListMembers(ctx, gameID, RecipientScopeActive)
|
||||
if err != nil {
|
||||
return uuid.Nil, fmt.Errorf("diplomail: list active members for race lookup: %w", err)
|
||||
}
|
||||
var found []MemberSnapshot
|
||||
for _, m := range members {
|
||||
if m.RaceName == byRaceName {
|
||||
found = append(found, m)
|
||||
}
|
||||
}
|
||||
switch len(found) {
|
||||
case 0:
|
||||
return uuid.Nil, fmt.Errorf("%w: no active member with race %q in this game", ErrInvalidInput, byRaceName)
|
||||
case 1:
|
||||
return found[0].UserID, nil
|
||||
default:
|
||||
return uuid.Nil, fmt.Errorf("%w: race %q matches multiple active members", ErrForbidden, byRaceName)
|
||||
}
|
||||
}
|
||||
|
||||
// GetMessage returns the InboxEntry for messageID addressed to
|
||||
// userID. ErrNotFound is returned when the caller is not a recipient
|
||||
// of the message — handlers translate that to 404 so the existence
|
||||
@@ -267,10 +316,13 @@ func (s *Service) allowedKinds(ctx context.Context, gameID, userID uuid.UUID) (m
|
||||
return map[string]bool{KindAdmin: true}, nil
|
||||
}
|
||||
|
||||
// ListSent returns personal messages authored by senderUserID in
|
||||
// gameID, newest first. Admin/system rows have no `sender_user_id`
|
||||
// and are therefore excluded; the user surface does not need them.
|
||||
func (s *Service) ListSent(ctx context.Context, gameID, senderUserID uuid.UUID) ([]Message, error) {
|
||||
// ListSent returns the sender-side view of personal messages
|
||||
// authored by senderUserID in gameID, newest first. Each entry pairs
|
||||
// the message with one of its recipient rows; single sends contribute
|
||||
// one entry per message, broadcasts contribute one entry per
|
||||
// addressee. Admin and system rows have no `sender_user_id` and are
|
||||
// therefore excluded; the user surface does not need them.
|
||||
func (s *Service) ListSent(ctx context.Context, gameID, senderUserID uuid.UUID) ([]InboxEntry, error) {
|
||||
return s.deps.Store.ListSent(ctx, gameID, senderUserID)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func messageColumns() postgres.ColumnList {
|
||||
m := table.DiplomailMessages
|
||||
return postgres.ColumnList{
|
||||
m.MessageID, m.GameID, m.GameName, m.Kind, m.SenderKind,
|
||||
m.SenderUserID, m.SenderUsername, m.SenderIP,
|
||||
m.SenderUserID, m.SenderUsername, m.SenderRaceName, m.SenderIP,
|
||||
m.Subject, m.Body, m.BodyLang, m.BroadcastScope, m.CreatedAt,
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ type MessageInsert struct {
|
||||
SenderKind string
|
||||
SenderUserID *uuid.UUID
|
||||
SenderUsername *string
|
||||
SenderRaceName *string
|
||||
SenderIP string
|
||||
Subject string
|
||||
Body string
|
||||
@@ -101,7 +102,7 @@ func (s *Store) InsertMessageWithRecipients(ctx context.Context, msg MessageInse
|
||||
m := table.DiplomailMessages
|
||||
msgStmt := m.INSERT(
|
||||
m.MessageID, m.GameID, m.GameName, m.Kind, m.SenderKind,
|
||||
m.SenderUserID, m.SenderUsername, m.SenderIP,
|
||||
m.SenderUserID, m.SenderUsername, m.SenderRaceName, m.SenderIP,
|
||||
m.Subject, m.Body, m.BodyLang, m.BroadcastScope,
|
||||
).VALUES(
|
||||
msg.MessageID,
|
||||
@@ -111,6 +112,7 @@ func (s *Store) InsertMessageWithRecipients(ctx context.Context, msg MessageInse
|
||||
msg.SenderKind,
|
||||
uuidPtrArg(msg.SenderUserID),
|
||||
stringPtrArg(msg.SenderUsername),
|
||||
stringPtrArg(msg.SenderRaceName),
|
||||
msg.SenderIP,
|
||||
msg.Subject,
|
||||
msg.Body,
|
||||
@@ -241,25 +243,38 @@ func (s *Store) ListInbox(ctx context.Context, gameID, userID uuid.UUID) ([]Inbo
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListSent returns messages authored by senderUserID in gameID,
|
||||
// newest first. Personal messages only — admin/system rows have
|
||||
// `sender_user_id IS NULL` and are filtered out by the WHERE clause.
|
||||
func (s *Store) ListSent(ctx context.Context, gameID, senderUserID uuid.UUID) ([]Message, error) {
|
||||
// ListSent returns the sender-side view of personal messages
|
||||
// authored by senderUserID in gameID, newest first. Each
|
||||
// `InboxEntry` carries the message together with one of its
|
||||
// recipient rows — single sends produce one entry per message;
|
||||
// game broadcasts produce one entry per addressee (the in-game
|
||||
// mail UI collapses broadcast entries into a single stand-alone
|
||||
// item by `message_id`). Admin / system rows have
|
||||
// `sender_user_id IS NULL` and are excluded by the WHERE clause.
|
||||
func (s *Store) ListSent(ctx context.Context, gameID, senderUserID uuid.UUID) ([]InboxEntry, error) {
|
||||
m := table.DiplomailMessages
|
||||
stmt := postgres.SELECT(messageColumns()).
|
||||
FROM(m).
|
||||
r := table.DiplomailRecipients
|
||||
cols := append(messageColumns(), recipientColumns()...)
|
||||
stmt := postgres.SELECT(cols).
|
||||
FROM(m.INNER_JOIN(r, r.MessageID.EQ(m.MessageID))).
|
||||
WHERE(
|
||||
m.GameID.EQ(postgres.UUID(gameID)).
|
||||
AND(m.SenderUserID.EQ(postgres.UUID(senderUserID))),
|
||||
).
|
||||
ORDER_BY(m.CreatedAt.DESC(), m.MessageID.DESC())
|
||||
var rows []model.DiplomailMessages
|
||||
if err := stmt.QueryContext(ctx, s.db, &rows); err != nil {
|
||||
ORDER_BY(m.CreatedAt.DESC(), m.MessageID.DESC(), r.RecipientID.ASC())
|
||||
var dest []struct {
|
||||
model.DiplomailMessages
|
||||
Recipient model.DiplomailRecipients `alias:"diplomail_recipients"`
|
||||
}
|
||||
if err := stmt.QueryContext(ctx, s.db, &dest); err != nil {
|
||||
return nil, fmt.Errorf("diplomail store: list sent %s/%s: %w", gameID, senderUserID, err)
|
||||
}
|
||||
out := make([]Message, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, messageFromModel(row))
|
||||
out := make([]InboxEntry, 0, len(dest))
|
||||
for _, row := range dest {
|
||||
out = append(out, InboxEntry{
|
||||
Message: messageFromModel(row.DiplomailMessages),
|
||||
Recipient: recipientFromModel(row.Recipient),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -737,6 +752,10 @@ func messageFromModel(row model.DiplomailMessages) Message {
|
||||
name := *row.SenderUsername
|
||||
out.SenderUsername = &name
|
||||
}
|
||||
if row.SenderRaceName != nil {
|
||||
name := *row.SenderRaceName
|
||||
out.SenderRaceName = &name
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ type Message struct {
|
||||
SenderKind string
|
||||
SenderUserID *uuid.UUID
|
||||
SenderUsername *string
|
||||
// SenderRaceName carries the snapshot of the sender's race in the
|
||||
// game at send time. Non-nil for sender_kind='player' rows, nil
|
||||
// for admin and system. The in-game mail UI groups personal
|
||||
// threads by this name (Phase 28).
|
||||
SenderRaceName *string
|
||||
SenderIP string
|
||||
Subject string
|
||||
Body string
|
||||
@@ -92,16 +97,19 @@ type Translation struct {
|
||||
}
|
||||
|
||||
// SendPersonalInput is the request payload for SendPersonal: the
|
||||
// caller sending a single-recipient personal message. Validation
|
||||
// (active membership, body length, etc.) is performed inside the
|
||||
// service.
|
||||
// caller sending a single-recipient personal message. Exactly one of
|
||||
// RecipientUserID and RecipientRaceName must be non-zero; the
|
||||
// service resolves a non-empty RecipientRaceName to the active
|
||||
// member with that race in the game. Other validation (active
|
||||
// membership, body length, etc.) is performed inside the service.
|
||||
type SendPersonalInput struct {
|
||||
GameID uuid.UUID
|
||||
SenderUserID uuid.UUID
|
||||
RecipientUserID uuid.UUID
|
||||
Subject string
|
||||
Body string
|
||||
SenderIP string
|
||||
GameID uuid.UUID
|
||||
SenderUserID uuid.UUID
|
||||
RecipientUserID uuid.UUID
|
||||
RecipientRaceName string
|
||||
Subject string
|
||||
Body string
|
||||
SenderIP string
|
||||
}
|
||||
|
||||
// CallerKind enumerates the privileged sender roles for admin-kind
|
||||
@@ -116,17 +124,21 @@ const (
|
||||
|
||||
// SendAdminPersonalInput is the request payload for an owner /
|
||||
// admin / system sending an admin-kind message to a single
|
||||
// recipient. Authorization (owner-vs-admin distinction) is enforced
|
||||
// by the HTTP layer; the service trusts the caller designation.
|
||||
// recipient. Exactly one of RecipientUserID and RecipientRaceName
|
||||
// must be non-zero; the service resolves a non-empty
|
||||
// RecipientRaceName to the active member with that race in the
|
||||
// game. Authorization (owner-vs-admin distinction) is enforced by
|
||||
// the HTTP layer; the service trusts the caller designation.
|
||||
type SendAdminPersonalInput struct {
|
||||
GameID uuid.UUID
|
||||
CallerKind string
|
||||
CallerUserID *uuid.UUID
|
||||
CallerUsername string
|
||||
RecipientUserID uuid.UUID
|
||||
Subject string
|
||||
Body string
|
||||
SenderIP string
|
||||
GameID uuid.UUID
|
||||
CallerKind string
|
||||
CallerUserID *uuid.UUID
|
||||
CallerUsername string
|
||||
RecipientUserID uuid.UUID
|
||||
RecipientRaceName string
|
||||
Subject string
|
||||
Body string
|
||||
SenderIP string
|
||||
}
|
||||
|
||||
// SendAdminBroadcastInput is the request payload for an owner /
|
||||
|
||||
@@ -20,6 +20,7 @@ type DiplomailMessages struct {
|
||||
SenderKind string
|
||||
SenderUserID *uuid.UUID
|
||||
SenderUsername *string
|
||||
SenderRaceName *string
|
||||
SenderIP string
|
||||
Subject string
|
||||
Body string
|
||||
|
||||
@@ -24,6 +24,7 @@ type diplomailMessagesTable struct {
|
||||
SenderKind postgres.ColumnString
|
||||
SenderUserID postgres.ColumnString
|
||||
SenderUsername postgres.ColumnString
|
||||
SenderRaceName postgres.ColumnString
|
||||
SenderIP postgres.ColumnString
|
||||
Subject postgres.ColumnString
|
||||
Body postgres.ColumnString
|
||||
@@ -78,14 +79,15 @@ func newDiplomailMessagesTableImpl(schemaName, tableName, alias string) diplomai
|
||||
SenderKindColumn = postgres.StringColumn("sender_kind")
|
||||
SenderUserIDColumn = postgres.StringColumn("sender_user_id")
|
||||
SenderUsernameColumn = postgres.StringColumn("sender_username")
|
||||
SenderRaceNameColumn = postgres.StringColumn("sender_race_name")
|
||||
SenderIPColumn = postgres.StringColumn("sender_ip")
|
||||
SubjectColumn = postgres.StringColumn("subject")
|
||||
BodyColumn = postgres.StringColumn("body")
|
||||
BodyLangColumn = postgres.StringColumn("body_lang")
|
||||
BroadcastScopeColumn = postgres.StringColumn("broadcast_scope")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{MessageIDColumn, GameIDColumn, GameNameColumn, KindColumn, SenderKindColumn, SenderUserIDColumn, SenderUsernameColumn, SenderIPColumn, SubjectColumn, BodyColumn, BodyLangColumn, BroadcastScopeColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{GameIDColumn, GameNameColumn, KindColumn, SenderKindColumn, SenderUserIDColumn, SenderUsernameColumn, SenderIPColumn, SubjectColumn, BodyColumn, BodyLangColumn, BroadcastScopeColumn, CreatedAtColumn}
|
||||
allColumns = postgres.ColumnList{MessageIDColumn, GameIDColumn, GameNameColumn, KindColumn, SenderKindColumn, SenderUserIDColumn, SenderUsernameColumn, SenderRaceNameColumn, SenderIPColumn, SubjectColumn, BodyColumn, BodyLangColumn, BroadcastScopeColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{GameIDColumn, GameNameColumn, KindColumn, SenderKindColumn, SenderUserIDColumn, SenderUsernameColumn, SenderRaceNameColumn, SenderIPColumn, SubjectColumn, BodyColumn, BodyLangColumn, BroadcastScopeColumn, CreatedAtColumn}
|
||||
defaultColumns = postgres.ColumnList{SenderIPColumn, SubjectColumn, BodyLangColumn, BroadcastScopeColumn, CreatedAtColumn}
|
||||
)
|
||||
|
||||
@@ -100,6 +102,7 @@ func newDiplomailMessagesTableImpl(schemaName, tableName, alias string) diplomai
|
||||
SenderKind: SenderKindColumn,
|
||||
SenderUserID: SenderUserIDColumn,
|
||||
SenderUsername: SenderUsernameColumn,
|
||||
SenderRaceName: SenderRaceNameColumn,
|
||||
SenderIP: SenderIPColumn,
|
||||
Subject: SubjectColumn,
|
||||
Body: BodyColumn,
|
||||
|
||||
@@ -683,6 +683,11 @@ CREATE TABLE diplomail_messages (
|
||||
sender_kind text NOT NULL,
|
||||
sender_user_id uuid,
|
||||
sender_username text,
|
||||
-- sender_race_name is the immutable snapshot of the sender's race
|
||||
-- in this game, captured at insert time when sender_kind='player'.
|
||||
-- Admin and system messages carry NULL. The Phase 28 mail UI keys
|
||||
-- per-race threading on this column.
|
||||
sender_race_name text,
|
||||
sender_ip text NOT NULL DEFAULT '',
|
||||
subject text NOT NULL DEFAULT '',
|
||||
body text NOT NULL,
|
||||
@@ -698,6 +703,13 @@ CREATE TABLE diplomail_messages (
|
||||
(sender_kind = 'admin' AND sender_user_id IS NULL AND sender_username IS NOT NULL) OR
|
||||
(sender_kind = 'system' AND sender_user_id IS NULL AND sender_username IS NULL)
|
||||
),
|
||||
-- sender_race_name is only meaningful for player senders. Admin
|
||||
-- and system rows never carry a race; player rows carry one when
|
||||
-- the sender has an active membership at send time (a non-playing
|
||||
-- private-game owner may legitimately have none).
|
||||
CONSTRAINT diplomail_messages_sender_race_chk CHECK (
|
||||
sender_kind = 'player' OR sender_race_name IS NULL
|
||||
),
|
||||
CONSTRAINT diplomail_messages_kind_sender_chk CHECK (
|
||||
(kind = 'personal' AND sender_kind = 'player') OR
|
||||
(kind = 'admin' AND sender_kind IN ('player', 'admin', 'system'))
|
||||
|
||||
@@ -60,19 +60,24 @@ func (h *AdminDiplomailHandlers) Send() gin.HandlerFunc {
|
||||
ctx := c.Request.Context()
|
||||
switch req.Target {
|
||||
case "", "user":
|
||||
recipientID, parseErr := uuid.Parse(req.RecipientUserID)
|
||||
if parseErr != nil {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
|
||||
return
|
||||
var recipientID uuid.UUID
|
||||
if req.RecipientUserID != "" {
|
||||
parsed, parseErr := uuid.Parse(req.RecipientUserID)
|
||||
if parseErr != nil {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
|
||||
return
|
||||
}
|
||||
recipientID = parsed
|
||||
}
|
||||
msg, rcpt, sendErr := h.svc.SendAdminPersonal(ctx, diplomail.SendAdminPersonalInput{
|
||||
GameID: gameID,
|
||||
CallerKind: diplomail.CallerKindAdmin,
|
||||
CallerUsername: username,
|
||||
RecipientUserID: recipientID,
|
||||
Subject: req.Subject,
|
||||
Body: req.Body,
|
||||
SenderIP: clientip.ExtractSourceIP(c),
|
||||
GameID: gameID,
|
||||
CallerKind: diplomail.CallerKindAdmin,
|
||||
CallerUsername: username,
|
||||
RecipientUserID: recipientID,
|
||||
RecipientRaceName: req.RecipientRaceName,
|
||||
Subject: req.Subject,
|
||||
Body: req.Body,
|
||||
SenderIP: clientip.ExtractSourceIP(c),
|
||||
})
|
||||
if sendErr != nil {
|
||||
respondDiplomailError(c, h.logger, "admin mail send personal", ctx, sendErr)
|
||||
|
||||
@@ -87,19 +87,24 @@ func (h *UserMailHandlers) SendPersonal() gin.HandlerFunc {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be valid JSON")
|
||||
return
|
||||
}
|
||||
recipientID, err := uuid.Parse(req.RecipientUserID)
|
||||
if err != nil {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
|
||||
return
|
||||
var recipientID uuid.UUID
|
||||
if req.RecipientUserID != "" {
|
||||
parsed, perr := uuid.Parse(req.RecipientUserID)
|
||||
if perr != nil {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
|
||||
return
|
||||
}
|
||||
recipientID = parsed
|
||||
}
|
||||
ctx := c.Request.Context()
|
||||
msg, rcpt, err := h.svc.SendPersonal(ctx, diplomail.SendPersonalInput{
|
||||
GameID: gameID,
|
||||
SenderUserID: userID,
|
||||
RecipientUserID: recipientID,
|
||||
Subject: req.Subject,
|
||||
Body: req.Body,
|
||||
SenderIP: clientip.ExtractSourceIP(c),
|
||||
GameID: gameID,
|
||||
SenderUserID: userID,
|
||||
RecipientUserID: recipientID,
|
||||
RecipientRaceName: req.RecipientRaceName,
|
||||
Subject: req.Subject,
|
||||
Body: req.Body,
|
||||
SenderIP: clientip.ExtractSourceIP(c),
|
||||
})
|
||||
if err != nil {
|
||||
respondDiplomailError(c, h.logger, "user mail send personal", ctx, err)
|
||||
@@ -189,9 +194,9 @@ func (h *UserMailHandlers) Sent() gin.HandlerFunc {
|
||||
respondDiplomailError(c, h.logger, "user mail sent", ctx, err)
|
||||
return
|
||||
}
|
||||
out := userMailSentListWire{Items: make([]userMailSentSummaryWire, 0, len(items))}
|
||||
for _, m := range items {
|
||||
out.Items = append(out.Items, mailMessageSummaryToWire(m))
|
||||
out := userMailSentListWire{Items: make([]userMailMessageDetailWire, 0, len(items))}
|
||||
for _, entry := range items {
|
||||
out.Items = append(out.Items, mailMessageDetailToWire(entry, false))
|
||||
}
|
||||
c.JSON(http.StatusOK, out)
|
||||
}
|
||||
@@ -341,21 +346,26 @@ func (h *UserMailHandlers) SendAdmin() gin.HandlerFunc {
|
||||
|
||||
switch req.Target {
|
||||
case "", "user":
|
||||
recipientID, parseErr := uuid.Parse(req.RecipientUserID)
|
||||
if parseErr != nil {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
|
||||
return
|
||||
var recipientID uuid.UUID
|
||||
if req.RecipientUserID != "" {
|
||||
parsed, parseErr := uuid.Parse(req.RecipientUserID)
|
||||
if parseErr != nil {
|
||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "recipient_user_id must be a valid UUID")
|
||||
return
|
||||
}
|
||||
recipientID = parsed
|
||||
}
|
||||
callerUserID := userID
|
||||
msg, rcpt, sendErr := h.svc.SendAdminPersonal(ctx, diplomail.SendAdminPersonalInput{
|
||||
GameID: gameID,
|
||||
CallerKind: diplomail.CallerKindOwner,
|
||||
CallerUserID: &callerUserID,
|
||||
CallerUsername: account.UserName,
|
||||
RecipientUserID: recipientID,
|
||||
Subject: req.Subject,
|
||||
Body: req.Body,
|
||||
SenderIP: clientip.ExtractSourceIP(c),
|
||||
GameID: gameID,
|
||||
CallerKind: diplomail.CallerKindOwner,
|
||||
CallerUserID: &callerUserID,
|
||||
CallerUsername: account.UserName,
|
||||
RecipientUserID: recipientID,
|
||||
RecipientRaceName: req.RecipientRaceName,
|
||||
Subject: req.Subject,
|
||||
Body: req.Body,
|
||||
SenderIP: clientip.ExtractSourceIP(c),
|
||||
})
|
||||
if sendErr != nil {
|
||||
respondDiplomailError(c, h.logger, "user mail send admin personal", ctx, sendErr)
|
||||
@@ -449,10 +459,13 @@ func parseMessageIDParam(c *gin.Context) (uuid.UUID, bool) {
|
||||
}
|
||||
|
||||
// userMailSendRequestWire mirrors the request body for SendPersonal.
|
||||
// Exactly one of `recipient_user_id` and `recipient_race_name` must
|
||||
// be supplied; the service rejects ambiguous and empty inputs.
|
||||
type userMailSendRequestWire struct {
|
||||
RecipientUserID string `json:"recipient_user_id"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
RecipientUserID string `json:"recipient_user_id,omitempty"`
|
||||
RecipientRaceName string `json:"recipient_race_name,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// userMailSendBroadcastRequestWire mirrors the request body for the
|
||||
@@ -464,15 +477,16 @@ type userMailSendBroadcastRequestWire struct {
|
||||
}
|
||||
|
||||
// userMailSendAdminRequestWire mirrors the request body for the
|
||||
// owner-only admin send. `target="user"` requires
|
||||
// `recipient_user_id`; `target="all"` accepts the optional
|
||||
// `recipients` scope (default `active`).
|
||||
// owner-only admin send. `target="user"` requires exactly one of
|
||||
// `recipient_user_id` and `recipient_race_name`; `target="all"`
|
||||
// accepts the optional `recipients` scope (default `active`).
|
||||
type userMailSendAdminRequestWire struct {
|
||||
Target string `json:"target"`
|
||||
RecipientUserID string `json:"recipient_user_id,omitempty"`
|
||||
Recipients string `json:"recipients,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
Target string `json:"target"`
|
||||
RecipientUserID string `json:"recipient_user_id,omitempty"`
|
||||
RecipientRaceName string `json:"recipient_race_name,omitempty"`
|
||||
Recipients string `json:"recipients,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// userMailBroadcastReceiptWire is the response shape returned after a
|
||||
@@ -524,6 +538,7 @@ type userMailMessageDetailWire struct {
|
||||
SenderKind string `json:"sender_kind"`
|
||||
SenderUserID *string `json:"sender_user_id,omitempty"`
|
||||
SenderUsername *string `json:"sender_username,omitempty"`
|
||||
SenderRaceName *string `json:"sender_race_name,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
BodyLang string `json:"body_lang"`
|
||||
@@ -540,27 +555,18 @@ type userMailMessageDetailWire struct {
|
||||
Translator *string `json:"translator,omitempty"`
|
||||
}
|
||||
|
||||
// userMailSentSummaryWire mirrors the response shape for the
|
||||
// sender-side listing. Recipient state is intentionally omitted (one
|
||||
// author may have N recipients per broadcast in later stages).
|
||||
type userMailSentSummaryWire struct {
|
||||
MessageID string `json:"message_id"`
|
||||
GameID string `json:"game_id"`
|
||||
GameName string `json:"game_name,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
BodyLang string `json:"body_lang"`
|
||||
BroadcastScope string `json:"broadcast_scope"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type userMailInboxListWire struct {
|
||||
Items []userMailMessageDetailWire `json:"items"`
|
||||
}
|
||||
|
||||
// userMailSentListWire mirrors the response shape for the
|
||||
// sender-side listing. Phase 28's in-game UI threads sent messages
|
||||
// by the recipient's race name, so the wire carries the full
|
||||
// message detail (including the recipient snapshot) — single sends
|
||||
// contribute one row per message, broadcasts contribute one row per
|
||||
// addressee and the UI collapses them by `message_id`.
|
||||
type userMailSentListWire struct {
|
||||
Items []userMailSentSummaryWire `json:"items"`
|
||||
Items []userMailMessageDetailWire `json:"items"`
|
||||
}
|
||||
|
||||
type userMailUnreadCountWire struct {
|
||||
@@ -597,6 +603,10 @@ func mailMessageDetailToWire(entry diplomail.InboxEntry, justCreated bool) userM
|
||||
s := *entry.SenderUsername
|
||||
out.SenderUsername = &s
|
||||
}
|
||||
if entry.SenderRaceName != nil {
|
||||
s := *entry.SenderRaceName
|
||||
out.SenderRaceName = &s
|
||||
}
|
||||
if entry.Recipient.RecipientRaceName != nil {
|
||||
s := *entry.Recipient.RecipientRaceName
|
||||
out.RecipientRaceName = &s
|
||||
@@ -624,20 +634,6 @@ func mailMessageDetailToWire(entry diplomail.InboxEntry, justCreated bool) userM
|
||||
return out
|
||||
}
|
||||
|
||||
func mailMessageSummaryToWire(m diplomail.Message) userMailSentSummaryWire {
|
||||
return userMailSentSummaryWire{
|
||||
MessageID: m.MessageID.String(),
|
||||
GameID: m.GameID.String(),
|
||||
GameName: m.GameName,
|
||||
Kind: m.Kind,
|
||||
Subject: m.Subject,
|
||||
Body: m.Body,
|
||||
BodyLang: m.BodyLang,
|
||||
BroadcastScope: m.BroadcastScope,
|
||||
CreatedAt: m.CreatedAt.UTC().Format(timestampLayout),
|
||||
}
|
||||
}
|
||||
|
||||
// mailRecipientStateToWire renders the recipient row after a
|
||||
// mark-read or soft-delete call. The caller only needs the per-user
|
||||
// state, not the full message body again.
|
||||
|
||||
+43
-41
@@ -4068,11 +4068,22 @@ components:
|
||||
UserMailSendRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [recipient_user_id, body]
|
||||
required: [body]
|
||||
properties:
|
||||
recipient_user_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: |
|
||||
Either `recipient_user_id` or `recipient_race_name` must
|
||||
be supplied; supplying both is rejected as
|
||||
`invalid_request`.
|
||||
recipient_race_name:
|
||||
type: string
|
||||
description: |
|
||||
Resolves to the active member with this race name in the
|
||||
game. Mutually exclusive with `recipient_user_id`. The
|
||||
server returns `forbidden` when the matching member is no
|
||||
longer active (lobby-removed / blocked).
|
||||
subject:
|
||||
type: string
|
||||
description: |
|
||||
@@ -4093,10 +4104,18 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
description: |
|
||||
Required when `target="user"`. Identifies the recipient
|
||||
of the personal admin message; the recipient may be in
|
||||
any membership status (admin notifications can reach
|
||||
kicked players).
|
||||
One of `recipient_user_id` and `recipient_race_name` is
|
||||
required when `target="user"`. Identifies the recipient
|
||||
of the personal admin message by uuid; the recipient may
|
||||
be in any membership status (admin notifications can
|
||||
reach kicked players when addressed by user_id).
|
||||
recipient_race_name:
|
||||
type: string
|
||||
description: |
|
||||
Optional alternative to `recipient_user_id` when
|
||||
`target="user"`. Resolves to the active member with this
|
||||
race name in the game; lobby-removed and blocked members
|
||||
cannot be reached through the race-name shortcut.
|
||||
recipients:
|
||||
type: string
|
||||
enum: [active, active_and_removed, all_members]
|
||||
@@ -4323,6 +4342,17 @@ components:
|
||||
sender_username:
|
||||
type: string
|
||||
nullable: true
|
||||
sender_race_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: |
|
||||
Snapshot of the sender's race name in this game at send
|
||||
time. Populated when `sender_kind="player"` and the
|
||||
sender had an active membership at send time; nil for
|
||||
admin and system messages, and for player messages sent
|
||||
by a private-game owner who was not an active member at
|
||||
send time. The in-game UI keys per-race threading on this
|
||||
field.
|
||||
subject:
|
||||
type: string
|
||||
body:
|
||||
@@ -4370,41 +4400,6 @@ components:
|
||||
translator:
|
||||
type: string
|
||||
description: Identifier of the translation engine that produced the cached row.
|
||||
UserMailSentSummary:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- message_id
|
||||
- game_id
|
||||
- kind
|
||||
- body
|
||||
- body_lang
|
||||
- broadcast_scope
|
||||
- created_at
|
||||
properties:
|
||||
message_id:
|
||||
type: string
|
||||
format: uuid
|
||||
game_id:
|
||||
type: string
|
||||
format: uuid
|
||||
game_name:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum: [personal, admin]
|
||||
subject:
|
||||
type: string
|
||||
body:
|
||||
type: string
|
||||
body_lang:
|
||||
type: string
|
||||
broadcast_scope:
|
||||
type: string
|
||||
enum: [single, game_broadcast, multi_game_broadcast]
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
UserMailInboxList:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
@@ -4415,6 +4410,13 @@ components:
|
||||
items:
|
||||
$ref: "#/components/schemas/UserMailMessageDetail"
|
||||
UserMailSentList:
|
||||
description: |
|
||||
Sender-side listing of personal messages authored by the
|
||||
caller. Each item carries the same shape as inbox entries
|
||||
(including the recipient snapshot); single sends contribute
|
||||
one row per message, broadcasts contribute one row per
|
||||
addressee so the in-game UI can collapse them by
|
||||
`message_id` into a single stand-alone item.
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [items]
|
||||
@@ -4422,7 +4424,7 @@ components:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UserMailSentSummary"
|
||||
$ref: "#/components/schemas/UserMailMessageDetail"
|
||||
UserMailUnreadCount:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
||||
@@ -1270,6 +1270,20 @@ The message detail response includes both the original body and,
|
||||
when available, the cached translation; the client UI defaults to
|
||||
the translated text and offers a "show original" toggle.
|
||||
|
||||
The in-game UI groups personal mail into per-race threads — every
|
||||
personal message exchanged between the local player and another
|
||||
race lands in one thread keyed on the other party's race. System
|
||||
mail, admin notifications, and the player's own paid-tier
|
||||
broadcasts render as stand-alone entries in the same list pane and
|
||||
are never threaded. `read_at` and `deleted_at` drive the local
|
||||
unread counter and the soft-delete affordance but are not surfaced
|
||||
to the user — diplomatic mail does not promise read receipts. The
|
||||
compose form picks the recipient by race name (resolved
|
||||
server-side from `Memberships.ListMembers(game_id, "active")`); no
|
||||
client-side memberships listing is fetched. See
|
||||
[`ui/docs/diplomail-ui.md`](../ui/docs/diplomail-ui.md) for the
|
||||
detailed UI breakdown.
|
||||
|
||||
### 11.5 Lifecycle hooks
|
||||
|
||||
Three lobby transitions land as system mail in the affected
|
||||
|
||||
@@ -1309,6 +1309,20 @@ bulk-purge всей почты соответствующей партии.
|
||||
кэш) перевод; UI по умолчанию показывает перевод и предлагает
|
||||
переключение «показать оригинал».
|
||||
|
||||
Внутриигровой UI группирует личную почту по веткам по расам —
|
||||
каждая личная переписка между локальным игроком и другой расой
|
||||
оказывается в одной ветке, ключевая по расе собеседника.
|
||||
Системные сообщения, административные уведомления и собственные
|
||||
рассылки игрока (платный тариф) показываются отдельными
|
||||
автономными записями в том же списке и никогда не группируются.
|
||||
`read_at` и `deleted_at` поддерживают локальный счётчик
|
||||
непрочитанного и кнопку удаления, но не показываются игроку —
|
||||
дипломатическая почта не обещает уведомления о прочтении. Форма
|
||||
compose выбирает получателя по имени расы (сервер резолвит через
|
||||
`Memberships.ListMembers(game_id, "active")`); клиент не тянет
|
||||
отдельный список членов. Подробнее — в
|
||||
[`ui/docs/diplomail-ui.md`](../ui/docs/diplomail-ui.md).
|
||||
|
||||
### 11.5 Хуки жизненного цикла
|
||||
|
||||
Три транзитных перехода в лобби порождают system mail в inbox
|
||||
|
||||
@@ -186,7 +186,8 @@ func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, lo
|
||||
userRoutes := backendclient.UserRoutes(backend.REST())
|
||||
lobbyRoutes := backendclient.LobbyRoutes(backend.REST())
|
||||
gameRoutes := backendclient.GameRoutes(backend.REST())
|
||||
allRoutes := make(map[string]downstream.Client, len(userRoutes)+len(lobbyRoutes)+len(gameRoutes))
|
||||
mailRoutes := backendclient.MailRoutes(backend.REST())
|
||||
allRoutes := make(map[string]downstream.Client, len(userRoutes)+len(lobbyRoutes)+len(gameRoutes)+len(mailRoutes))
|
||||
for k, v := range userRoutes {
|
||||
allRoutes[k] = v
|
||||
}
|
||||
@@ -196,6 +197,9 @@ func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, lo
|
||||
for k, v := range gameRoutes {
|
||||
allRoutes[k] = v
|
||||
}
|
||||
for k, v := range mailRoutes {
|
||||
allRoutes[k] = v
|
||||
}
|
||||
|
||||
cleanup := func() error {
|
||||
return closeRedisClient()
|
||||
|
||||
@@ -63,6 +63,12 @@ func (c *RESTClient) ExecuteGameCommand(ctx context.Context, command downstream.
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute game command %q: %w", command.MessageType, err)
|
||||
}
|
||||
return c.executeUserGamesReport(ctx, command.UserID, req)
|
||||
case reportmodel.MessageTypeUserGamesBattle:
|
||||
req, err := transcoder.PayloadToGameBattleRequest(command.PayloadBytes)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute game command %q: %w", command.MessageType, err)
|
||||
}
|
||||
return c.executeUserGamesBattle(ctx, command.UserID, req)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute game command: unsupported message type %q", command.MessageType)
|
||||
}
|
||||
@@ -127,6 +133,26 @@ func (c *RESTClient) executeUserGamesReport(ctx context.Context, userID string,
|
||||
return projectUserGamesReportResponse(status, respBody)
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeUserGamesBattle(ctx context.Context, userID string, req *reportmodel.GameBattleRequest) (downstream.UnaryResult, error) {
|
||||
if req.GameID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.battle: game_id must not be empty")
|
||||
}
|
||||
if req.BattleID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.battle: battle_id must not be empty")
|
||||
}
|
||||
target := fmt.Sprintf("%s/api/v1/user/games/%s/battles/%d/%s",
|
||||
c.baseURL,
|
||||
url.PathEscape(req.GameID.String()),
|
||||
req.Turn,
|
||||
url.PathEscape(req.BattleID.String()),
|
||||
)
|
||||
respBody, status, err := c.do(ctx, http.MethodGet, target, userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.battle: %w", err)
|
||||
}
|
||||
return projectUserGamesBattleResponse(status, respBody)
|
||||
}
|
||||
|
||||
// buildEngineCommandBody serialises a slice of typed commands into the
|
||||
// JSON shape expected by backend's command/order handlers (a
|
||||
// `gamerest.Command` with the actor field left empty — backend rebinds
|
||||
@@ -262,3 +288,32 @@ func projectUserGamesReportResponse(statusCode int, payload []byte) (downstream.
|
||||
return downstream.UnaryResult{}, fmt.Errorf("unexpected HTTP status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// projectUserGamesBattleResponse decodes the engine's BattleReport JSON
|
||||
// payload (forwarded by backend's user.games.battle proxy) and
|
||||
// re-encodes it as a FlatBuffers BattleReport for the signed-gRPC
|
||||
// client. 404 from backend surfaces as the canonical `not_found`
|
||||
// gateway error so the UI can render its "battle not found" state.
|
||||
func projectUserGamesBattleResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
switch {
|
||||
case statusCode == http.StatusOK:
|
||||
var report reportmodel.BattleReport
|
||||
if err := json.Unmarshal(payload, &report); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode engine battle report: %w", err)
|
||||
}
|
||||
encoded, err := transcoder.BattleReportToPayload(&report)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("encode battle report payload: %w", err)
|
||||
}
|
||||
return downstream.UnaryResult{
|
||||
ResultCode: userCommandResultCodeOK,
|
||||
PayloadBytes: encoded,
|
||||
}, nil
|
||||
case statusCode == http.StatusServiceUnavailable:
|
||||
return downstream.UnaryResult{}, downstream.ErrDownstreamUnavailable
|
||||
case statusCode >= 400 && statusCode <= 599:
|
||||
return projectUserBackendError(statusCode, payload)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("unexpected HTTP status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"galaxy/gateway/internal/backendclient"
|
||||
"galaxy/gateway/internal/downstream"
|
||||
ordermodel "galaxy/model/order"
|
||||
reportmodel "galaxy/model/report"
|
||||
"galaxy/transcoder"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -170,6 +171,78 @@ func TestExecuteUserGamesOrderGetRejectsNegativeTurn(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "user.games.order.get")
|
||||
}
|
||||
|
||||
func TestExecuteUserGamesBattleForwardsAndDecodesResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gameID := uuid.MustParse("66666666-7777-8888-9999-aaaaaaaaaaaa")
|
||||
battleID := uuid.MustParse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")
|
||||
|
||||
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/games/"+gameID.String()+"/battles/7/"+battleID.String(),
|
||||
r.URL.Path,
|
||||
)
|
||||
require.Equal(t, "user-1", r.Header.Get(backendclient.HeaderUserID))
|
||||
raceID := uuid.MustParse("11111111-2222-3333-4444-555555555555")
|
||||
writeJSON(t, w, http.StatusOK, map[string]any{
|
||||
"id": battleID.String(),
|
||||
"planet": uint(42),
|
||||
"planetName": "Tau Ceti II",
|
||||
"races": map[string]string{"1": raceID.String()},
|
||||
"ships": map[string]map[string]any{},
|
||||
"protocol": []any{},
|
||||
})
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := newRESTClient(t, server)
|
||||
payload, err := transcoder.GameBattleRequestToPayload(&reportmodel.GameBattleRequest{
|
||||
GameID: gameID,
|
||||
Turn: 7,
|
||||
BattleID: battleID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result, err := client.ExecuteGameCommand(context.Background(), newAuthCommand(t, reportmodel.MessageTypeUserGamesBattle, payload))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "ok", result.ResultCode)
|
||||
|
||||
decoded, err := transcoder.PayloadToBattleReport(result.PayloadBytes)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decoded)
|
||||
assert.Equal(t, battleID, decoded.ID)
|
||||
assert.Equal(t, uint(42), decoded.Planet)
|
||||
assert.Equal(t, "Tau Ceti II", decoded.PlanetName)
|
||||
}
|
||||
|
||||
func TestExecuteUserGamesBattleMapsNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gameID := uuid.MustParse("77777777-8888-9999-aaaa-bbbbbbbbbbbb")
|
||||
battleID := uuid.MustParse("99999999-aaaa-bbbb-cccc-dddddddddddd")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(t, w, http.StatusNotFound, map[string]any{
|
||||
"error": map[string]any{
|
||||
"code": "not_found",
|
||||
"message": "battle not found",
|
||||
},
|
||||
})
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := newRESTClient(t, server)
|
||||
payload, err := transcoder.GameBattleRequestToPayload(&reportmodel.GameBattleRequest{
|
||||
GameID: gameID,
|
||||
Turn: 2,
|
||||
BattleID: battleID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result, err := client.ExecuteGameCommand(context.Background(), newAuthCommand(t, reportmodel.MessageTypeUserGamesBattle, payload))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "not_found", result.ResultCode)
|
||||
}
|
||||
|
||||
// writeJSON copy below mirrors the helper used by other test files
|
||||
// in this package; keeping it adjacent to its callers avoids
|
||||
// reaching across files in a fresh test.
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
package backendclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/gateway/internal/downstream"
|
||||
diplomailmodel "galaxy/model/diplomail"
|
||||
commonfbs "galaxy/schema/fbs/common"
|
||||
fbs "galaxy/schema/fbs/diplomail"
|
||||
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ExecuteMailCommand routes one authenticated `user.games.mail.*`
|
||||
// command into the matching `/api/v1/user/games/{game_id}/mail/...`
|
||||
// backend REST endpoint. Each command decodes a FlatBuffers request
|
||||
// payload, issues the REST call, decodes the JSON response, and
|
||||
// re-encodes the result as a typed FlatBuffers envelope.
|
||||
func (c *RESTClient) ExecuteMailCommand(ctx context.Context, command downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
|
||||
if c == nil || c.httpClient == nil {
|
||||
return downstream.UnaryResult{}, errors.New("backendclient: execute mail command: nil client")
|
||||
}
|
||||
if ctx == nil {
|
||||
return downstream.UnaryResult{}, errors.New("backendclient: execute mail command: nil context")
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return downstream.UnaryResult{}, err
|
||||
}
|
||||
if strings.TrimSpace(command.UserID) == "" {
|
||||
return downstream.UnaryResult{}, errors.New("backendclient: execute mail command: user_id must not be empty")
|
||||
}
|
||||
|
||||
switch command.MessageType {
|
||||
case diplomailmodel.MessageTypeUserGamesMailInbox:
|
||||
return c.executeMailInbox(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailSent:
|
||||
return c.executeMailSent(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailMessageGet:
|
||||
return c.executeMailMessageGet(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailSend:
|
||||
return c.executeMailSend(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailBroadcast:
|
||||
return c.executeMailBroadcast(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailAdmin:
|
||||
return c.executeMailAdmin(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailRead:
|
||||
return c.executeMailRead(ctx, command.UserID, command.PayloadBytes)
|
||||
case diplomailmodel.MessageTypeUserGamesMailDelete:
|
||||
return c.executeMailDelete(ctx, command.UserID, command.PayloadBytes)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute mail command: unsupported message type %q", command.MessageType)
|
||||
}
|
||||
}
|
||||
|
||||
// mailMessageJSON mirrors the backend's `UserMailMessageDetail` wire
|
||||
// shape from `backend/openapi.yaml`. Pointer fields are nullable in
|
||||
// the OpenAPI spec; the encoder treats empty strings as "absent".
|
||||
type mailMessageJSON struct {
|
||||
MessageID string `json:"message_id"`
|
||||
GameID string `json:"game_id"`
|
||||
GameName string `json:"game_name,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
SenderKind string `json:"sender_kind"`
|
||||
SenderUserID *string `json:"sender_user_id,omitempty"`
|
||||
SenderUsername *string `json:"sender_username,omitempty"`
|
||||
SenderRaceName *string `json:"sender_race_name,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
BodyLang string `json:"body_lang"`
|
||||
BroadcastScope string `json:"broadcast_scope"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
RecipientUserID string `json:"recipient_user_id"`
|
||||
RecipientUserName string `json:"recipient_user_name,omitempty"`
|
||||
RecipientRaceName *string `json:"recipient_race_name,omitempty"`
|
||||
ReadAt *string `json:"read_at,omitempty"`
|
||||
DeletedAt *string `json:"deleted_at,omitempty"`
|
||||
TranslatedSubject *string `json:"translated_subject,omitempty"`
|
||||
TranslatedBody *string `json:"translated_body,omitempty"`
|
||||
TranslationLang *string `json:"translation_lang,omitempty"`
|
||||
Translator *string `json:"translator,omitempty"`
|
||||
}
|
||||
|
||||
// mailRecipientStateJSON mirrors `UserMailRecipientState`.
|
||||
type mailRecipientStateJSON struct {
|
||||
MessageID string `json:"message_id"`
|
||||
ReadAt *string `json:"read_at,omitempty"`
|
||||
DeletedAt *string `json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// mailBroadcastReceiptJSON mirrors `UserMailBroadcastReceipt`.
|
||||
type mailBroadcastReceiptJSON struct {
|
||||
MessageID string `json:"message_id"`
|
||||
GameID string `json:"game_id"`
|
||||
GameName string `json:"game_name,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
SenderKind string `json:"sender_kind"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
BodyLang string `json:"body_lang"`
|
||||
BroadcastScope string `json:"broadcast_scope"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
RecipientCount int `json:"recipient_count"`
|
||||
}
|
||||
|
||||
type mailInboxJSON struct {
|
||||
Items []mailMessageJSON `json:"items"`
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailInbox(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.inbox: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsInboxRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
if gameID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.inbox: game_id is missing")
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/inbox"
|
||||
respBody, status, err := c.do(ctx, http.MethodGet, target, userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.inbox: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
var resp mailInboxJSON
|
||||
if err := json.Unmarshal(respBody, &resp); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail inbox response: %w", err)
|
||||
}
|
||||
out := encodeMailMessageList(resp.Items, fbs.InboxResponseStart, fbs.InboxResponseAddItems, fbs.InboxResponseEnd, fbs.FinishInboxResponseBuffer)
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: out}, nil
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailSent(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.sent: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsSentRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
if gameID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.sent: game_id is missing")
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/sent"
|
||||
respBody, status, err := c.do(ctx, http.MethodGet, target, userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.sent: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
var resp mailInboxJSON
|
||||
if err := json.Unmarshal(respBody, &resp); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail sent response: %w", err)
|
||||
}
|
||||
out := encodeMailMessageList(resp.Items, fbs.SentResponseStart, fbs.SentResponseAddItems, fbs.SentResponseEnd, fbs.FinishSentResponseBuffer)
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: out}, nil
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailMessageGet(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.message.get: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsMessageGetRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
messageID := readUUID(flat.MessageId(nil))
|
||||
if gameID == uuid.Nil || messageID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.message.get: game_id and message_id are required")
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/messages/" + url.PathEscape(messageID.String())
|
||||
respBody, status, err := c.do(ctx, http.MethodGet, target, userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.message.get: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
var msg mailMessageJSON
|
||||
if err := json.Unmarshal(respBody, &msg); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail message response: %w", err)
|
||||
}
|
||||
builder := flatbuffers.NewBuilder(512)
|
||||
msgOff := encodeMailMessage(builder, &msg)
|
||||
fbs.MessageGetResponseStart(builder)
|
||||
fbs.MessageGetResponseAddMessage(builder, msgOff)
|
||||
root := fbs.MessageGetResponseEnd(builder)
|
||||
fbs.FinishMessageGetResponseBuffer(builder, root)
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: builder.FinishedBytes()}, nil
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailSend(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.send: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsSendRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
if gameID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.send: game_id is missing")
|
||||
}
|
||||
body := struct {
|
||||
RecipientUserID string `json:"recipient_user_id,omitempty"`
|
||||
RecipientRaceName string `json:"recipient_race_name,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
}{
|
||||
RecipientUserID: string(flat.RecipientUserId()),
|
||||
RecipientRaceName: string(flat.RecipientRaceName()),
|
||||
Subject: string(flat.Subject()),
|
||||
Body: string(flat.Body()),
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/messages"
|
||||
respBody, status, err := c.do(ctx, http.MethodPost, target, userID, body)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.send: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
var msg mailMessageJSON
|
||||
if err := json.Unmarshal(respBody, &msg); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail send response: %w", err)
|
||||
}
|
||||
builder := flatbuffers.NewBuilder(512)
|
||||
msgOff := encodeMailMessage(builder, &msg)
|
||||
fbs.SendResponseStart(builder)
|
||||
fbs.SendResponseAddMessage(builder, msgOff)
|
||||
root := fbs.SendResponseEnd(builder)
|
||||
fbs.FinishSendResponseBuffer(builder, root)
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: builder.FinishedBytes()}, nil
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailBroadcast(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.broadcast: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsBroadcastRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
if gameID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.broadcast: game_id is missing")
|
||||
}
|
||||
body := struct {
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
}{
|
||||
Subject: string(flat.Subject()),
|
||||
Body: string(flat.Body()),
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/broadcast"
|
||||
respBody, status, err := c.do(ctx, http.MethodPost, target, userID, body)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.broadcast: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
var receipt mailBroadcastReceiptJSON
|
||||
if err := json.Unmarshal(respBody, &receipt); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail broadcast response: %w", err)
|
||||
}
|
||||
builder := flatbuffers.NewBuilder(256)
|
||||
recOff := encodeMailBroadcastReceipt(builder, &receipt)
|
||||
fbs.BroadcastResponseStart(builder)
|
||||
fbs.BroadcastResponseAddReceipt(builder, recOff)
|
||||
root := fbs.BroadcastResponseEnd(builder)
|
||||
fbs.FinishBroadcastResponseBuffer(builder, root)
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: builder.FinishedBytes()}, nil
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailAdmin(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.admin: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsAdminRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
if gameID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.admin: game_id is missing")
|
||||
}
|
||||
target := string(flat.Target())
|
||||
body := struct {
|
||||
Target string `json:"target"`
|
||||
RecipientUserID string `json:"recipient_user_id,omitempty"`
|
||||
RecipientRaceName string `json:"recipient_race_name,omitempty"`
|
||||
Recipients string `json:"recipients,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body"`
|
||||
}{
|
||||
Target: target,
|
||||
RecipientUserID: string(flat.RecipientUserId()),
|
||||
RecipientRaceName: string(flat.RecipientRaceName()),
|
||||
Recipients: string(flat.Recipients()),
|
||||
Subject: string(flat.Subject()),
|
||||
Body: string(flat.Body()),
|
||||
}
|
||||
url := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/admin"
|
||||
respBody, status, err := c.do(ctx, http.MethodPost, url, userID, body)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.admin: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
builder := flatbuffers.NewBuilder(512)
|
||||
if target == "all" {
|
||||
var receipt mailBroadcastReceiptJSON
|
||||
if err := json.Unmarshal(respBody, &receipt); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail admin broadcast response: %w", err)
|
||||
}
|
||||
recOff := encodeMailBroadcastReceipt(builder, &receipt)
|
||||
fbs.AdminResponseStart(builder)
|
||||
fbs.AdminResponseAddReceipt(builder, recOff)
|
||||
root := fbs.AdminResponseEnd(builder)
|
||||
fbs.FinishAdminResponseBuffer(builder, root)
|
||||
} else {
|
||||
var msg mailMessageJSON
|
||||
if err := json.Unmarshal(respBody, &msg); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail admin send response: %w", err)
|
||||
}
|
||||
msgOff := encodeMailMessage(builder, &msg)
|
||||
fbs.AdminResponseStart(builder)
|
||||
fbs.AdminResponseAddMessage(builder, msgOff)
|
||||
root := fbs.AdminResponseEnd(builder)
|
||||
fbs.FinishAdminResponseBuffer(builder, root)
|
||||
}
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: builder.FinishedBytes()}, nil
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailRead(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.read: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsReadRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
messageID := readUUID(flat.MessageId(nil))
|
||||
if gameID == uuid.Nil || messageID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.read: game_id and message_id are required")
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/messages/" + url.PathEscape(messageID.String()) + "/read"
|
||||
respBody, status, err := c.do(ctx, http.MethodPost, target, userID, struct{}{})
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.read: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
return encodeRecipientStateResponse(respBody, fbs.ReadResponseStart, fbs.ReadResponseAddState, fbs.ReadResponseEnd, fbs.FinishReadResponseBuffer)
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeMailDelete(ctx context.Context, userID string, payload []byte) (downstream.UnaryResult, error) {
|
||||
if len(payload) == 0 {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.delete: payload is empty")
|
||||
}
|
||||
flat := fbs.GetRootAsDeleteRequest(payload, 0)
|
||||
gameID := readUUID(flat.GameId(nil))
|
||||
messageID := readUUID(flat.MessageId(nil))
|
||||
if gameID == uuid.Nil || messageID == uuid.Nil {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.games.mail.delete: game_id and message_id are required")
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/games/" + url.PathEscape(gameID.String()) + "/mail/messages/" + url.PathEscape(messageID.String())
|
||||
respBody, status, err := c.do(ctx, http.MethodDelete, target, userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.games.mail.delete: %w", err)
|
||||
}
|
||||
if status < 200 || status >= 300 {
|
||||
return projectMailErrorResponse(status, respBody)
|
||||
}
|
||||
return encodeRecipientStateResponse(respBody, fbs.DeleteResponseStart, fbs.DeleteResponseAddState, fbs.DeleteResponseEnd, fbs.FinishDeleteResponseBuffer)
|
||||
}
|
||||
|
||||
// encodeRecipientStateResponse decodes the JSON recipient-state body
|
||||
// and emits the corresponding FlatBuffers Read/Delete envelope. The
|
||||
// caller supplies the trio of envelope start / add-state / end / finish
|
||||
// functions so this helper covers both endpoints with the same shape.
|
||||
func encodeRecipientStateResponse(respBody []byte,
|
||||
startFn func(*flatbuffers.Builder),
|
||||
addStateFn func(*flatbuffers.Builder, flatbuffers.UOffsetT),
|
||||
endFn func(*flatbuffers.Builder) flatbuffers.UOffsetT,
|
||||
finishFn func(*flatbuffers.Builder, flatbuffers.UOffsetT),
|
||||
) (downstream.UnaryResult, error) {
|
||||
var state mailRecipientStateJSON
|
||||
if err := json.Unmarshal(respBody, &state); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode mail recipient state: %w", err)
|
||||
}
|
||||
builder := flatbuffers.NewBuilder(128)
|
||||
stateOff := encodeMailRecipientState(builder, &state)
|
||||
startFn(builder)
|
||||
addStateFn(builder, stateOff)
|
||||
root := endFn(builder)
|
||||
finishFn(builder, root)
|
||||
return downstream.UnaryResult{ResultCode: userCommandResultCodeOK, PayloadBytes: builder.FinishedBytes()}, nil
|
||||
}
|
||||
|
||||
// encodeMailMessageList is a shared helper that encodes a slice of
|
||||
// mailMessageJSON items into either an InboxResponse or a
|
||||
// SentResponse FlatBuffers envelope. The two envelopes have the same
|
||||
// shape (just a `items` vector of MailMessage) so the trio of
|
||||
// constructor functions parameterises the helper.
|
||||
func encodeMailMessageList(items []mailMessageJSON,
|
||||
startFn func(*flatbuffers.Builder),
|
||||
addItemsFn func(*flatbuffers.Builder, flatbuffers.UOffsetT),
|
||||
endFn func(*flatbuffers.Builder) flatbuffers.UOffsetT,
|
||||
finishFn func(*flatbuffers.Builder, flatbuffers.UOffsetT),
|
||||
) []byte {
|
||||
builder := flatbuffers.NewBuilder(1024)
|
||||
offsets := make([]flatbuffers.UOffsetT, 0, len(items))
|
||||
for i := range items {
|
||||
offsets = append(offsets, encodeMailMessage(builder, &items[i]))
|
||||
}
|
||||
// FlatBuffers vectors are built in reverse: prepend each offset.
|
||||
builder.StartVector(4, len(offsets), 4)
|
||||
for i := len(offsets) - 1; i >= 0; i-- {
|
||||
builder.PrependUOffsetT(offsets[i])
|
||||
}
|
||||
itemsVec := builder.EndVector(len(offsets))
|
||||
startFn(builder)
|
||||
addItemsFn(builder, itemsVec)
|
||||
root := endFn(builder)
|
||||
finishFn(builder, root)
|
||||
return builder.FinishedBytes()
|
||||
}
|
||||
|
||||
// encodeMailMessage builds a MailMessage table inside builder. Returns
|
||||
// the offset of the finished table. Strings are interned through the
|
||||
// builder; missing JSON fields (nil pointers, empty strings) yield
|
||||
// empty FB strings which the readers treat as absent.
|
||||
func encodeMailMessage(builder *flatbuffers.Builder, m *mailMessageJSON) flatbuffers.UOffsetT {
|
||||
messageIDOff := builder.CreateString(m.MessageID)
|
||||
gameIDOff := builder.CreateString(m.GameID)
|
||||
gameNameOff := builder.CreateString(m.GameName)
|
||||
kindOff := builder.CreateString(m.Kind)
|
||||
senderKindOff := builder.CreateString(m.SenderKind)
|
||||
senderUserIDOff := builder.CreateString(stringPtrValue(m.SenderUserID))
|
||||
senderUsernameOff := builder.CreateString(stringPtrValue(m.SenderUsername))
|
||||
senderRaceNameOff := builder.CreateString(stringPtrValue(m.SenderRaceName))
|
||||
subjectOff := builder.CreateString(m.Subject)
|
||||
bodyOff := builder.CreateString(m.Body)
|
||||
bodyLangOff := builder.CreateString(m.BodyLang)
|
||||
broadcastScopeOff := builder.CreateString(m.BroadcastScope)
|
||||
recipientUserIDOff := builder.CreateString(m.RecipientUserID)
|
||||
recipientUserNameOff := builder.CreateString(m.RecipientUserName)
|
||||
recipientRaceNameOff := builder.CreateString(stringPtrValue(m.RecipientRaceName))
|
||||
translatedSubjectOff := builder.CreateString(stringPtrValue(m.TranslatedSubject))
|
||||
translatedBodyOff := builder.CreateString(stringPtrValue(m.TranslatedBody))
|
||||
translationLangOff := builder.CreateString(stringPtrValue(m.TranslationLang))
|
||||
translatorOff := builder.CreateString(stringPtrValue(m.Translator))
|
||||
|
||||
fbs.MailMessageStart(builder)
|
||||
fbs.MailMessageAddMessageId(builder, messageIDOff)
|
||||
fbs.MailMessageAddGameId(builder, gameIDOff)
|
||||
fbs.MailMessageAddGameName(builder, gameNameOff)
|
||||
fbs.MailMessageAddKind(builder, kindOff)
|
||||
fbs.MailMessageAddSenderKind(builder, senderKindOff)
|
||||
fbs.MailMessageAddSenderUserId(builder, senderUserIDOff)
|
||||
fbs.MailMessageAddSenderUsername(builder, senderUsernameOff)
|
||||
fbs.MailMessageAddSenderRaceName(builder, senderRaceNameOff)
|
||||
fbs.MailMessageAddSubject(builder, subjectOff)
|
||||
fbs.MailMessageAddBody(builder, bodyOff)
|
||||
fbs.MailMessageAddBodyLang(builder, bodyLangOff)
|
||||
fbs.MailMessageAddBroadcastScope(builder, broadcastScopeOff)
|
||||
fbs.MailMessageAddCreatedAtMs(builder, parseRFC3339Millis(m.CreatedAt))
|
||||
fbs.MailMessageAddRecipientUserId(builder, recipientUserIDOff)
|
||||
fbs.MailMessageAddRecipientUserName(builder, recipientUserNameOff)
|
||||
fbs.MailMessageAddRecipientRaceName(builder, recipientRaceNameOff)
|
||||
fbs.MailMessageAddReadAtMs(builder, parseRFC3339Millis(stringPtrValue(m.ReadAt)))
|
||||
fbs.MailMessageAddDeletedAtMs(builder, parseRFC3339Millis(stringPtrValue(m.DeletedAt)))
|
||||
fbs.MailMessageAddTranslatedSubject(builder, translatedSubjectOff)
|
||||
fbs.MailMessageAddTranslatedBody(builder, translatedBodyOff)
|
||||
fbs.MailMessageAddTranslationLang(builder, translationLangOff)
|
||||
fbs.MailMessageAddTranslator(builder, translatorOff)
|
||||
return fbs.MailMessageEnd(builder)
|
||||
}
|
||||
|
||||
// encodeMailRecipientState builds a MailRecipientState table.
|
||||
func encodeMailRecipientState(builder *flatbuffers.Builder, s *mailRecipientStateJSON) flatbuffers.UOffsetT {
|
||||
messageIDOff := builder.CreateString(s.MessageID)
|
||||
fbs.MailRecipientStateStart(builder)
|
||||
fbs.MailRecipientStateAddMessageId(builder, messageIDOff)
|
||||
fbs.MailRecipientStateAddReadAtMs(builder, parseRFC3339Millis(stringPtrValue(s.ReadAt)))
|
||||
fbs.MailRecipientStateAddDeletedAtMs(builder, parseRFC3339Millis(stringPtrValue(s.DeletedAt)))
|
||||
return fbs.MailRecipientStateEnd(builder)
|
||||
}
|
||||
|
||||
// encodeMailBroadcastReceipt builds a MailBroadcastReceipt table.
|
||||
func encodeMailBroadcastReceipt(builder *flatbuffers.Builder, r *mailBroadcastReceiptJSON) flatbuffers.UOffsetT {
|
||||
messageIDOff := builder.CreateString(r.MessageID)
|
||||
gameIDOff := builder.CreateString(r.GameID)
|
||||
gameNameOff := builder.CreateString(r.GameName)
|
||||
kindOff := builder.CreateString(r.Kind)
|
||||
senderKindOff := builder.CreateString(r.SenderKind)
|
||||
subjectOff := builder.CreateString(r.Subject)
|
||||
bodyOff := builder.CreateString(r.Body)
|
||||
bodyLangOff := builder.CreateString(r.BodyLang)
|
||||
broadcastScopeOff := builder.CreateString(r.BroadcastScope)
|
||||
fbs.MailBroadcastReceiptStart(builder)
|
||||
fbs.MailBroadcastReceiptAddMessageId(builder, messageIDOff)
|
||||
fbs.MailBroadcastReceiptAddGameId(builder, gameIDOff)
|
||||
fbs.MailBroadcastReceiptAddGameName(builder, gameNameOff)
|
||||
fbs.MailBroadcastReceiptAddKind(builder, kindOff)
|
||||
fbs.MailBroadcastReceiptAddSenderKind(builder, senderKindOff)
|
||||
fbs.MailBroadcastReceiptAddSubject(builder, subjectOff)
|
||||
fbs.MailBroadcastReceiptAddBody(builder, bodyOff)
|
||||
fbs.MailBroadcastReceiptAddBodyLang(builder, bodyLangOff)
|
||||
fbs.MailBroadcastReceiptAddBroadcastScope(builder, broadcastScopeOff)
|
||||
fbs.MailBroadcastReceiptAddCreatedAtMs(builder, parseRFC3339Millis(r.CreatedAt))
|
||||
fbs.MailBroadcastReceiptAddRecipientCount(builder, int32(r.RecipientCount))
|
||||
return fbs.MailBroadcastReceiptEnd(builder)
|
||||
}
|
||||
|
||||
// projectMailErrorResponse maps a non-2xx response into a UnaryResult
|
||||
// carrying the backend error envelope, reusing the shared user-mail
|
||||
// error-projection. 503 is bubbled as ErrDownstreamUnavailable.
|
||||
func projectMailErrorResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
if statusCode == http.StatusServiceUnavailable {
|
||||
return downstream.UnaryResult{}, downstream.ErrDownstreamUnavailable
|
||||
}
|
||||
if statusCode >= 400 && statusCode <= 599 {
|
||||
return projectUserBackendError(statusCode, payload)
|
||||
}
|
||||
return downstream.UnaryResult{}, fmt.Errorf("unexpected HTTP status %d", statusCode)
|
||||
}
|
||||
|
||||
// readUUID converts the common.UUID struct (or its absence) into a
|
||||
// google/uuid.UUID. Returns uuid.Nil when the input is nil.
|
||||
func readUUID(u *commonfbs.UUID) uuid.UUID {
|
||||
if u == nil {
|
||||
return uuid.Nil
|
||||
}
|
||||
var out uuid.UUID
|
||||
hi := u.Hi()
|
||||
lo := u.Lo()
|
||||
for i := 0; i < 8; i++ {
|
||||
out[i] = byte(hi >> (56 - 8*i))
|
||||
out[i+8] = byte(lo >> (56 - 8*i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// stringPtrValue returns "" for nil and the dereferenced value
|
||||
// otherwise. Used to flatten nullable JSON strings into the
|
||||
// always-present FlatBuffers string slot.
|
||||
func stringPtrValue(p *string) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
return *p
|
||||
}
|
||||
|
||||
// parseRFC3339Millis parses an RFC 3339 timestamp string (the format
|
||||
// the backend mail handler emits) into Unix milliseconds. Returns 0
|
||||
// when the input is empty or unparseable, matching the "absent"
|
||||
// convention for the *_at_ms wire fields.
|
||||
func parseRFC3339Millis(s string) int64 {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339Nano, s)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return t.UnixMilli()
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package backendclient_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"galaxy/gateway/internal/backendclient"
|
||||
diplomailmodel "galaxy/model/diplomail"
|
||||
commonfbs "galaxy/schema/fbs/common"
|
||||
fbs "galaxy/schema/fbs/diplomail"
|
||||
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExecuteMailInboxDecodesItems(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gameID := uuid.MustParse("11111111-2222-3333-4444-555555555555")
|
||||
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/games/"+gameID.String()+"/mail/inbox", 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{
|
||||
{
|
||||
"message_id": "00000000-0000-0000-0000-000000000001",
|
||||
"game_id": gameID.String(),
|
||||
"kind": "personal",
|
||||
"sender_kind": "player",
|
||||
"sender_user_id": "00000000-0000-0000-0000-000000000010",
|
||||
"sender_username": "alice",
|
||||
"sender_race_name": "AliceRace",
|
||||
"subject": "hi",
|
||||
"body": "hello there",
|
||||
"body_lang": "en",
|
||||
"broadcast_scope": "single",
|
||||
"created_at": "2026-05-15T12:00:00Z",
|
||||
"recipient_user_id": "00000000-0000-0000-0000-000000000020",
|
||||
"recipient_user_name": "bob",
|
||||
"recipient_race_name": "BobRace",
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := newRESTClient(t, server)
|
||||
payload := buildInboxRequest(gameID)
|
||||
cmd := newAuthCommand(t, diplomailmodel.MessageTypeUserGamesMailInbox, payload)
|
||||
result, err := client.ExecuteMailCommand(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "ok", result.ResultCode)
|
||||
|
||||
resp := fbs.GetRootAsInboxResponse(result.PayloadBytes, 0)
|
||||
require.Equal(t, 1, resp.ItemsLength())
|
||||
var item fbs.MailMessage
|
||||
require.True(t, resp.Items(&item, 0))
|
||||
assert.Equal(t, "00000000-0000-0000-0000-000000000001", string(item.MessageId()))
|
||||
assert.Equal(t, "AliceRace", string(item.SenderRaceName()))
|
||||
assert.Equal(t, "BobRace", string(item.RecipientRaceName()))
|
||||
}
|
||||
|
||||
func TestExecuteMailSendForwardsRaceName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gameID := uuid.MustParse("22222222-3333-4444-5555-666666666666")
|
||||
var captured struct {
|
||||
Body string
|
||||
RecipientUserID string
|
||||
RecipientRaceName string
|
||||
}
|
||||
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/games/"+gameID.String()+"/mail/messages", r.URL.Path)
|
||||
var req map[string]any
|
||||
raw, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(raw, &req))
|
||||
if v, ok := req["body"].(string); ok {
|
||||
captured.Body = v
|
||||
}
|
||||
if v, ok := req["recipient_user_id"].(string); ok {
|
||||
captured.RecipientUserID = v
|
||||
}
|
||||
if v, ok := req["recipient_race_name"].(string); ok {
|
||||
captured.RecipientRaceName = v
|
||||
}
|
||||
writeJSON(t, w, http.StatusCreated, map[string]any{
|
||||
"message_id": "00000000-0000-0000-0000-000000000099",
|
||||
"game_id": gameID.String(),
|
||||
"kind": "personal",
|
||||
"sender_kind": "player",
|
||||
"sender_user_id": "00000000-0000-0000-0000-000000000010",
|
||||
"sender_race_name": "Senders",
|
||||
"body": captured.Body,
|
||||
"body_lang": "en",
|
||||
"broadcast_scope": "single",
|
||||
"created_at": "2026-05-15T12:00:00Z",
|
||||
"recipient_user_id": "00000000-0000-0000-0000-000000000020",
|
||||
"recipient_race_name": "Receivers",
|
||||
})
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := newRESTClient(t, server)
|
||||
payload := buildSendRequestByRaceName(gameID, "Receivers", "let us talk")
|
||||
cmd := newAuthCommand(t, diplomailmodel.MessageTypeUserGamesMailSend, payload)
|
||||
result, err := client.ExecuteMailCommand(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "ok", result.ResultCode)
|
||||
|
||||
resp := fbs.GetRootAsSendResponse(result.PayloadBytes, 0)
|
||||
require.NotNil(t, resp.Message(nil))
|
||||
msg := resp.Message(nil)
|
||||
assert.Equal(t, "let us talk", string(msg.Body()))
|
||||
assert.Equal(t, "Senders", string(msg.SenderRaceName()))
|
||||
assert.Equal(t, "Receivers", string(msg.RecipientRaceName()))
|
||||
|
||||
assert.Empty(t, captured.RecipientUserID)
|
||||
assert.Equal(t, "Receivers", captured.RecipientRaceName)
|
||||
assert.Equal(t, "let us talk", captured.Body)
|
||||
}
|
||||
|
||||
func TestExecuteMailReadReturnsState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gameID := uuid.MustParse("33333333-4444-5555-6666-777777777777")
|
||||
messageID := uuid.MustParse("00000000-0000-0000-0000-0000000000aa")
|
||||
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/games/"+gameID.String()+"/mail/messages/"+messageID.String()+"/read", r.URL.Path)
|
||||
writeJSON(t, w, http.StatusOK, map[string]any{
|
||||
"message_id": messageID.String(),
|
||||
"read_at": "2026-05-15T12:34:56Z",
|
||||
})
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := newRESTClient(t, server)
|
||||
payload := buildReadRequest(gameID, messageID)
|
||||
cmd := newAuthCommand(t, diplomailmodel.MessageTypeUserGamesMailRead, payload)
|
||||
result, err := client.ExecuteMailCommand(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := fbs.GetRootAsReadResponse(result.PayloadBytes, 0)
|
||||
state := resp.State(nil)
|
||||
require.NotNil(t, state)
|
||||
assert.Equal(t, messageID.String(), string(state.MessageId()))
|
||||
assert.NotZero(t, state.ReadAtMs())
|
||||
}
|
||||
|
||||
// buildInboxRequest emits a FlatBuffers InboxRequest envelope with
|
||||
// the supplied game_id.
|
||||
func buildInboxRequest(gameID uuid.UUID) []byte {
|
||||
builder := flatbuffers.NewBuilder(64)
|
||||
hi, lo := uuidToHiLo(gameID)
|
||||
fbs.InboxRequestStart(builder)
|
||||
fbs.InboxRequestAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
|
||||
root := fbs.InboxRequestEnd(builder)
|
||||
fbs.FinishInboxRequestBuffer(builder, root)
|
||||
return builder.FinishedBytes()
|
||||
}
|
||||
|
||||
// buildSendRequestByRaceName emits a FlatBuffers SendRequest that
|
||||
// addresses the recipient by race name rather than user_id.
|
||||
func buildSendRequestByRaceName(gameID uuid.UUID, raceName, body string) []byte {
|
||||
builder := flatbuffers.NewBuilder(128)
|
||||
raceOff := builder.CreateString(raceName)
|
||||
bodyOff := builder.CreateString(body)
|
||||
hi, lo := uuidToHiLo(gameID)
|
||||
fbs.SendRequestStart(builder)
|
||||
fbs.SendRequestAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
|
||||
fbs.SendRequestAddRecipientRaceName(builder, raceOff)
|
||||
fbs.SendRequestAddBody(builder, bodyOff)
|
||||
root := fbs.SendRequestEnd(builder)
|
||||
fbs.FinishSendRequestBuffer(builder, root)
|
||||
return builder.FinishedBytes()
|
||||
}
|
||||
|
||||
// buildReadRequest emits a FlatBuffers ReadRequest envelope.
|
||||
func buildReadRequest(gameID, messageID uuid.UUID) []byte {
|
||||
builder := flatbuffers.NewBuilder(64)
|
||||
gameHi, gameLo := uuidToHiLo(gameID)
|
||||
msgHi, msgLo := uuidToHiLo(messageID)
|
||||
fbs.ReadRequestStart(builder)
|
||||
fbs.ReadRequestAddGameId(builder, commonfbs.CreateUUID(builder, gameHi, gameLo))
|
||||
fbs.ReadRequestAddMessageId(builder, commonfbs.CreateUUID(builder, msgHi, msgLo))
|
||||
root := fbs.ReadRequestEnd(builder)
|
||||
fbs.FinishReadRequestBuffer(builder, root)
|
||||
return builder.FinishedBytes()
|
||||
}
|
||||
|
||||
// uuidToHiLo splits a 16-byte UUID into the two big-endian uint64
|
||||
// halves the common.UUID struct uses.
|
||||
func uuidToHiLo(u uuid.UUID) (uint64, uint64) {
|
||||
var hi, lo uint64
|
||||
for i := 0; i < 8; i++ {
|
||||
hi = (hi << 8) | uint64(u[i])
|
||||
lo = (lo << 8) | uint64(u[i+8])
|
||||
}
|
||||
return hi, lo
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"galaxy/gateway/internal/downstream"
|
||||
diplomailmodel "galaxy/model/diplomail"
|
||||
lobbymodel "galaxy/model/lobby"
|
||||
ordermodel "galaxy/model/order"
|
||||
reportmodel "galaxy/model/report"
|
||||
@@ -64,6 +65,28 @@ func GameRoutes(client *RESTClient) map[string]downstream.Client {
|
||||
ordermodel.MessageTypeUserGamesOrder: target,
|
||||
ordermodel.MessageTypeUserGamesOrderGet: target,
|
||||
reportmodel.MessageTypeUserGamesReport: target,
|
||||
reportmodel.MessageTypeUserGamesBattle: target,
|
||||
}
|
||||
}
|
||||
|
||||
// MailRoutes returns the authenticated `user.games.mail.*` downstream
|
||||
// routes served by backend's diplomail subsystem. When client is nil
|
||||
// every route resolves to a dependency-unavailable client so the
|
||||
// static router still recognises the message types.
|
||||
func MailRoutes(client *RESTClient) map[string]downstream.Client {
|
||||
target := downstream.Client(unavailableClient{})
|
||||
if client != nil {
|
||||
target = mailCommandClient{rest: client}
|
||||
}
|
||||
return map[string]downstream.Client{
|
||||
diplomailmodel.MessageTypeUserGamesMailInbox: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailSent: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailMessageGet: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailSend: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailBroadcast: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailAdmin: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailRead: target,
|
||||
diplomailmodel.MessageTypeUserGamesMailDelete: target,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +120,18 @@ func (c gameCommandClient) ExecuteCommand(ctx context.Context, command downstrea
|
||||
return c.rest.ExecuteGameCommand(ctx, command)
|
||||
}
|
||||
|
||||
type mailCommandClient struct {
|
||||
rest *RESTClient
|
||||
}
|
||||
|
||||
func (c mailCommandClient) ExecuteCommand(ctx context.Context, command downstream.AuthenticatedCommand) (downstream.UnaryResult, error) {
|
||||
return c.rest.ExecuteMailCommand(ctx, command)
|
||||
}
|
||||
|
||||
var (
|
||||
_ downstream.Client = unavailableClient{}
|
||||
_ downstream.Client = userCommandClient{}
|
||||
_ downstream.Client = lobbyCommandClient{}
|
||||
_ downstream.Client = gameCommandClient{}
|
||||
_ downstream.Client = mailCommandClient{}
|
||||
)
|
||||
|
||||
@@ -60,6 +60,7 @@ func TestRoutesCoverAllAuthenticatedMessageTypes(t *testing.T) {
|
||||
ordermodel.MessageTypeUserGamesOrder,
|
||||
ordermodel.MessageTypeUserGamesOrderGet,
|
||||
reportmodel.MessageTypeUserGamesReport,
|
||||
reportmodel.MessageTypeUserGamesBattle,
|
||||
},
|
||||
actual: backendclient.GameRoutes(nil),
|
||||
},
|
||||
|
||||
@@ -101,6 +101,16 @@ const (
|
||||
// the authenticated gRPC listener address.
|
||||
authenticatedGRPCAddrEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ADDR"
|
||||
|
||||
// authenticatedGRPCCORSAllowedOriginsEnvVar names the environment
|
||||
// variable that configures the comma-separated list of browser
|
||||
// origins permitted to call the authenticated Connect-Web surface.
|
||||
// An empty value disables CORS entirely; the listener then refuses
|
||||
// to send Access-Control-* headers and browsers block cross-origin
|
||||
// fetches. Set this in any deployment that fronts the gateway
|
||||
// behind a different hostname than the SvelteKit bundle (e.g.
|
||||
// `https://www.galaxy.lan` calling `https://api.galaxy.lan`).
|
||||
authenticatedGRPCCORSAllowedOriginsEnvVar = "GATEWAY_AUTHENTICATED_GRPC_CORS_ALLOWED_ORIGINS"
|
||||
|
||||
// authenticatedGRPCConnectionTimeoutEnvVar names the environment variable
|
||||
// that configures the inbound connection handshake timeout for the
|
||||
// authenticated gRPC listener.
|
||||
@@ -542,6 +552,13 @@ type AuthenticatedGRPCConfig struct {
|
||||
// AntiAbuse configures the authenticated gRPC rate limits enforced after
|
||||
// the request passes the transport authenticity checks.
|
||||
AntiAbuse AuthenticatedGRPCAntiAbuseConfig
|
||||
|
||||
// CORSAllowedOrigins is the exact-match list of browser origins
|
||||
// permitted to call the authenticated Connect-Web surface. Empty
|
||||
// disables CORS — requests without an Access-Control-Allow-Origin
|
||||
// response will be blocked by the browser, which is the production
|
||||
// posture when the UI and the gateway share a single hostname.
|
||||
CORSAllowedOrigins []string
|
||||
}
|
||||
|
||||
// SessionCacheConfig describes the bounds of the gateway's in-memory
|
||||
@@ -836,6 +853,16 @@ func LoadFromEnv() (Config, error) {
|
||||
cfg.PublicHTTP.CORSAllowedOrigins = origins
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv(authenticatedGRPCCORSAllowedOriginsEnvVar); ok {
|
||||
origins := make([]string, 0)
|
||||
for part := range strings.SplitSeq(v, ",") {
|
||||
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
||||
origins = append(origins, trimmed)
|
||||
}
|
||||
}
|
||||
cfg.AuthenticatedGRPC.CORSAllowedOrigins = origins
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv(backendHTTPURLEnvVar); ok {
|
||||
cfg.Backend.HTTPBaseURL = v
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package grpcapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// withCORS wraps next so that CORS preflight (OPTIONS) requests with an
|
||||
// allow-listed Origin receive 204 plus the `Access-Control-Allow-*`
|
||||
// headers Connect-Web needs, and actual requests get the matching
|
||||
// `Access-Control-Allow-Origin` header echoed back. Origins are
|
||||
// compared exactly: scheme, host, and port must match. An empty
|
||||
// allow-list passes through untouched — the production posture when
|
||||
// the UI and the gateway share one hostname.
|
||||
//
|
||||
// The wrapper mirrors `restapi.withCORS` but speaks plain `net/http`
|
||||
// because the Connect handler is mounted on a `http.ServeMux`, not a
|
||||
// gin engine. Connect-Web POSTs use `Content-Type: application/connect+json`
|
||||
// which triggers a browser preflight; without these headers the
|
||||
// browser surfaces "Load failed" before the Connect handler even sees
|
||||
// the request.
|
||||
func withCORS(allowedOrigins []string, next http.Handler) http.Handler {
|
||||
allowed := make(map[string]struct{}, len(allowedOrigins))
|
||||
for _, origin := range allowedOrigins {
|
||||
allowed[origin] = struct{}{}
|
||||
}
|
||||
if len(allowed) == 0 {
|
||||
return next
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if _, ok := allowed[origin]; !ok {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Add("Vary", "Origin")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||
if reqHeaders := r.Header.Get("Access-Control-Request-Headers"); reqHeaders != "" {
|
||||
w.Header().Set("Access-Control-Allow-Headers", reqHeaders)
|
||||
} else {
|
||||
// Defaults cover the Connect-Web preflight set: protocol
|
||||
// version, content type, timeout, and the signed-request
|
||||
// metadata the gateway interceptor expects.
|
||||
w.Header().Set("Access-Control-Allow-Headers",
|
||||
"Content-Type, Connect-Protocol-Version, Connect-Timeout-Ms, Authorization")
|
||||
}
|
||||
// Expose the response headers Connect-Web needs to read on
|
||||
// the client (e.g. trailers folded into headers for unary).
|
||||
w.Header().Set("Access-Control-Expose-Headers", "Connect-Protocol-Version, Grpc-Status, Grpc-Message")
|
||||
w.Header().Set("Access-Control-Max-Age", "3600")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
// Expose the same response headers on the actual call.
|
||||
w.Header().Set("Access-Control-Expose-Headers", "Connect-Protocol-Version, Grpc-Status, Grpc-Message")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -169,7 +169,10 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
)
|
||||
mux.Handle(path, handler)
|
||||
|
||||
tracedHandler := otelhttp.NewHandler(mux, "authenticated_edge")
|
||||
// CORS runs OUTSIDE the otelhttp wrapper so preflight OPTIONS calls
|
||||
// answer with 204 immediately and never enter the trace path.
|
||||
corsMux := withCORS(s.cfg.CORSAllowedOrigins, mux)
|
||||
tracedHandler := otelhttp.NewHandler(corsMux, "authenticated_edge")
|
||||
http2Server := &http2.Server{IdleTimeout: s.cfg.ConnectionTimeout}
|
||||
httpServer := &http.Server{
|
||||
Handler: h2c.NewHandler(tracedHandler, http2Server),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Package diplomail defines the public typed command identifiers
|
||||
// exposed at the authenticated Gateway -> Diplomatic Mail boundary.
|
||||
//
|
||||
// The gateway routes each `user.games.mail.*` ExecuteCommand into the
|
||||
// matching `/api/v1/user/games/{game_id}/mail/*` REST endpoint on the
|
||||
// backend; the wire envelopes and payload tables live in
|
||||
// `pkg/schema/fbs/diplomail.fbs`.
|
||||
package diplomail
|
||||
|
||||
const (
|
||||
// MessageTypeUserGamesMailInbox is the authenticated gateway
|
||||
// message type used to read the caller's diplomatic-mail inbox
|
||||
// for one game. Backend filters out rows whose `available_at` is
|
||||
// still nil (translation in flight).
|
||||
MessageTypeUserGamesMailInbox = "user.games.mail.inbox"
|
||||
|
||||
// MessageTypeUserGamesMailSent is the authenticated gateway
|
||||
// message type used to read the caller's outgoing personal
|
||||
// messages for one game. Admin and system rows are not included.
|
||||
MessageTypeUserGamesMailSent = "user.games.mail.sent"
|
||||
|
||||
// MessageTypeUserGamesMailMessageGet is the authenticated
|
||||
// gateway message type used to read a single message detail
|
||||
// addressed to the caller. The response carries the translation
|
||||
// rendering when one is cached for the caller's preferred
|
||||
// language.
|
||||
MessageTypeUserGamesMailMessageGet = "user.games.mail.message.get"
|
||||
|
||||
// MessageTypeUserGamesMailSend is the authenticated gateway
|
||||
// message type used to send a single-recipient personal message.
|
||||
// Exactly one of `recipient_user_id` and `recipient_race_name`
|
||||
// must be supplied; the backend resolves the race-name shortcut
|
||||
// through `Memberships.ListMembers(gameID, "active")`.
|
||||
MessageTypeUserGamesMailSend = "user.games.mail.send"
|
||||
|
||||
// MessageTypeUserGamesMailBroadcast is the authenticated gateway
|
||||
// message type used by paid-tier callers to broadcast a personal
|
||||
// message to every other active member of the game.
|
||||
MessageTypeUserGamesMailBroadcast = "user.games.mail.broadcast"
|
||||
|
||||
// MessageTypeUserGamesMailAdmin is the authenticated gateway
|
||||
// message type used by the game owner to compose an admin-kind
|
||||
// notification. The wire shape is target-discriminated: `user`
|
||||
// addresses a single recipient (by id or race name); `all`
|
||||
// broadcasts to every member matching the requested scope.
|
||||
MessageTypeUserGamesMailAdmin = "user.games.mail.admin"
|
||||
|
||||
// MessageTypeUserGamesMailRead is the authenticated gateway
|
||||
// message type used to mark a single message as read. Idempotent.
|
||||
MessageTypeUserGamesMailRead = "user.games.mail.read"
|
||||
|
||||
// MessageTypeUserGamesMailDelete is the authenticated gateway
|
||||
// message type used to soft-delete a single message. The
|
||||
// recipient row must already be marked read.
|
||||
MessageTypeUserGamesMailDelete = "user.games.mail.delete"
|
||||
)
|
||||
@@ -9,6 +9,13 @@ import "github.com/google/uuid"
|
||||
// `Report`.
|
||||
const MessageTypeUserGamesReport = "user.games.report"
|
||||
|
||||
// MessageTypeUserGamesBattle is the authenticated gateway message type
|
||||
// used to fetch one battle report through
|
||||
// `GET /api/v1/user/games/{game_id}/battles/{turn}/{battle_id}`. The
|
||||
// signed payload is a FlatBuffers `GameBattleRequest`; the response is
|
||||
// a FlatBuffers `BattleReport`.
|
||||
const MessageTypeUserGamesBattle = "user.games.battle"
|
||||
|
||||
// GameReportRequest is the typed payload of MessageTypeUserGamesReport.
|
||||
// `GameID` selects the target game (the message_type alone is not
|
||||
// enough; this scope is per-game) and `Turn` selects the requested
|
||||
@@ -20,3 +27,19 @@ type GameReportRequest struct {
|
||||
// Turn is the zero-based turn number whose report is requested.
|
||||
Turn uint `json:"turn"`
|
||||
}
|
||||
|
||||
// GameBattleRequest is the typed payload of MessageTypeUserGamesBattle.
|
||||
// `GameID` selects the target game; `Turn` is the turn the battle
|
||||
// happened at (the engine partitions battles by turn for cheap lookup);
|
||||
// `BattleID` is the in-game identifier returned in the report's
|
||||
// battle-summary list. All three fields are required.
|
||||
type GameBattleRequest struct {
|
||||
// GameID identifies the game the battle belongs to.
|
||||
GameID uuid.UUID `json:"game_id"`
|
||||
|
||||
// Turn is the turn number the battle happened at.
|
||||
Turn uint `json:"turn"`
|
||||
|
||||
// BattleID is the engine-assigned id of the battle to fetch.
|
||||
BattleID uuid.UUID `json:"battle_id"`
|
||||
}
|
||||
|
||||
@@ -49,4 +49,14 @@ table BattleReport {
|
||||
protocol:[BattleActionReport];
|
||||
}
|
||||
|
||||
// GameBattleRequest is the signed-gRPC request payload for
|
||||
// `MessageTypeUserGamesBattle`. Gateway forwards this into the
|
||||
// backend's `GET /api/v1/user/games/{game_id}/battles/{turn}/{battle_id}`
|
||||
// endpoint after resolving the caller's runtime player mapping.
|
||||
table GameBattleRequest {
|
||||
game_id:UUID (required);
|
||||
turn:uint32;
|
||||
battle_id:UUID (required);
|
||||
}
|
||||
|
||||
root_type BattleReport;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package battle
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type GameBattleRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsGameBattleRequest(buf []byte, offset flatbuffers.UOffsetT) *GameBattleRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &GameBattleRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishGameBattleRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsGameBattleRequest(buf []byte, offset flatbuffers.UOffsetT) *GameBattleRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &GameBattleRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedGameBattleRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *GameBattleRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *GameBattleRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *GameBattleRequest) GameId(obj *UUID) *UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *GameBattleRequest) Turn() uint32 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetUint32(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *GameBattleRequest) MutateTurn(n uint32) bool {
|
||||
return rcv._tab.MutateUint32Slot(6, n)
|
||||
}
|
||||
|
||||
func (rcv *GameBattleRequest) BattleId(obj *UUID) *UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GameBattleRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(3)
|
||||
}
|
||||
func GameBattleRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func GameBattleRequestAddTurn(builder *flatbuffers.Builder, turn uint32) {
|
||||
builder.PrependUint32Slot(1, turn, 0)
|
||||
}
|
||||
func GameBattleRequestAddBattleId(builder *flatbuffers.Builder, battleId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(2, flatbuffers.UOffsetT(battleId), 0)
|
||||
}
|
||||
func GameBattleRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// diplomail contains FlatBuffers payloads used by the authenticated
|
||||
// gateway boundary for the in-game diplomatic-mail subsystem. The
|
||||
// wire shapes here mirror the trusted internal
|
||||
// `/api/v1/user/games/{game_id}/mail/*` REST surface; gateway derives
|
||||
// the calling `user_id` from the verified session and forwards it as
|
||||
// `X-User-Id` to backend.
|
||||
|
||||
include "common.fbs";
|
||||
|
||||
namespace diplomail;
|
||||
|
||||
// MailMessage stores one inbox / sent-list / message-detail row. The
|
||||
// fields mirror `UserMailMessageDetail` in `backend/openapi.yaml`
|
||||
// with the following encoding rules:
|
||||
//
|
||||
// - `*_user_id` fields are RFC 4122 string UUIDs ("" means absent
|
||||
// for nullable fields such as `sender_user_id`).
|
||||
// - `*_at_ms` fields carry Unix milliseconds; `0` means the
|
||||
// timestamp is absent (e.g. an unread message has
|
||||
// `read_at_ms == 0`).
|
||||
// - `translated_*`, `translation_lang`, and `translator` are set
|
||||
// when the backend served a cached rendering into the caller's
|
||||
// preferred language; empty otherwise.
|
||||
// - `sender_race_name` is the snapshot of the sender's race name
|
||||
// in this game at send time. Present for `sender_kind="player"`
|
||||
// messages when the sender had an active membership; absent for
|
||||
// admin and system messages. The in-game UI keys per-race
|
||||
// threading on this field.
|
||||
table MailMessage {
|
||||
message_id:string;
|
||||
game_id:string;
|
||||
game_name:string;
|
||||
kind:string;
|
||||
sender_kind:string;
|
||||
sender_user_id:string;
|
||||
sender_username:string;
|
||||
sender_race_name:string;
|
||||
subject:string;
|
||||
body:string;
|
||||
body_lang:string;
|
||||
broadcast_scope:string;
|
||||
created_at_ms:int64;
|
||||
recipient_user_id:string;
|
||||
recipient_user_name:string;
|
||||
recipient_race_name:string;
|
||||
read_at_ms:int64;
|
||||
deleted_at_ms:int64;
|
||||
translated_subject:string;
|
||||
translated_body:string;
|
||||
translation_lang:string;
|
||||
translator:string;
|
||||
}
|
||||
|
||||
// MailRecipientState mirrors the `UserMailRecipientState` payload
|
||||
// returned from mark-read and soft-delete endpoints. Same timestamp
|
||||
// conventions as `MailMessage`.
|
||||
table MailRecipientState {
|
||||
message_id:string;
|
||||
read_at_ms:int64;
|
||||
deleted_at_ms:int64;
|
||||
}
|
||||
|
||||
// MailBroadcastReceipt mirrors `UserMailBroadcastReceipt`. Returned
|
||||
// from broadcast sends (paid-tier and admin); `recipient_count` is
|
||||
// the number of recipient rows the server materialised.
|
||||
table MailBroadcastReceipt {
|
||||
message_id:string;
|
||||
game_id:string;
|
||||
game_name:string;
|
||||
kind:string;
|
||||
sender_kind:string;
|
||||
subject:string;
|
||||
body:string;
|
||||
body_lang:string;
|
||||
broadcast_scope:string;
|
||||
created_at_ms:int64;
|
||||
recipient_count:int32;
|
||||
}
|
||||
|
||||
// InboxRequest stores the read-side request for the caller's inbox
|
||||
// in `game_id`. Backend filters to messages with `available_at` set
|
||||
// (translation completed when the recipient's preferred language
|
||||
// differs from the body language).
|
||||
table InboxRequest {
|
||||
game_id:common.UUID (required);
|
||||
}
|
||||
|
||||
// InboxResponse stores the resulting inbox list, newest first.
|
||||
// `items` is empty when the caller has no available messages in
|
||||
// this game.
|
||||
table InboxResponse {
|
||||
items:[MailMessage];
|
||||
}
|
||||
|
||||
// SentRequest stores the read-side request for the caller's sent
|
||||
// personal messages in `game_id`. Admin / system rows are not
|
||||
// included.
|
||||
table SentRequest {
|
||||
game_id:common.UUID (required);
|
||||
}
|
||||
|
||||
// SentResponse stores the caller's outgoing personal-message list.
|
||||
// Each `MailMessage` carries the original recipient snapshot.
|
||||
table SentResponse {
|
||||
items:[MailMessage];
|
||||
}
|
||||
|
||||
// MessageGetRequest stores the read-side request for a single
|
||||
// message detail. The caller must be a recipient of the message.
|
||||
table MessageGetRequest {
|
||||
game_id:common.UUID (required);
|
||||
message_id:common.UUID (required);
|
||||
}
|
||||
|
||||
// MessageGetResponse stores the fully decorated message detail
|
||||
// including any cached translation into the caller's preferred
|
||||
// language.
|
||||
table MessageGetResponse {
|
||||
message:MailMessage;
|
||||
}
|
||||
|
||||
// SendRequest stores the write-side request for a single-recipient
|
||||
// personal send. Exactly one of `recipient_user_id` /
|
||||
// `recipient_race_name` must be supplied; the empty string means
|
||||
// "use the other field".
|
||||
table SendRequest {
|
||||
game_id:common.UUID (required);
|
||||
recipient_user_id:string;
|
||||
recipient_race_name:string;
|
||||
subject:string;
|
||||
body:string;
|
||||
}
|
||||
|
||||
// SendResponse echoes the freshly inserted message detail.
|
||||
table SendResponse {
|
||||
message:MailMessage;
|
||||
}
|
||||
|
||||
// BroadcastRequest stores the paid-tier player broadcast. The
|
||||
// recipient set is always "every other active member of the game".
|
||||
table BroadcastRequest {
|
||||
game_id:common.UUID (required);
|
||||
subject:string;
|
||||
body:string;
|
||||
}
|
||||
|
||||
// BroadcastResponse stores the receipt returned by the server.
|
||||
table BroadcastResponse {
|
||||
receipt:MailBroadcastReceipt;
|
||||
}
|
||||
|
||||
// AdminRequest stores the owner-only admin send. `target="user"`
|
||||
// requires exactly one of `recipient_user_id` / `recipient_race_name`;
|
||||
// `target="all"` accepts the optional `recipients` scope (default
|
||||
// `active`).
|
||||
table AdminRequest {
|
||||
game_id:common.UUID (required);
|
||||
target:string;
|
||||
recipient_user_id:string;
|
||||
recipient_race_name:string;
|
||||
recipients:string;
|
||||
subject:string;
|
||||
body:string;
|
||||
}
|
||||
|
||||
// AdminResponse carries the result of an admin send. When the
|
||||
// request had `target="user"`, `message` is set; when `target="all"`,
|
||||
// `receipt` is set. Callers branch on which field is present.
|
||||
table AdminResponse {
|
||||
message:MailMessage;
|
||||
receipt:MailBroadcastReceipt;
|
||||
}
|
||||
|
||||
// ReadRequest stores the mark-read intent for a single message. The
|
||||
// caller must be a recipient. Idempotent.
|
||||
table ReadRequest {
|
||||
game_id:common.UUID (required);
|
||||
message_id:common.UUID (required);
|
||||
}
|
||||
|
||||
// ReadResponse echoes the recipient state after the operation.
|
||||
table ReadResponse {
|
||||
state:MailRecipientState;
|
||||
}
|
||||
|
||||
// DeleteRequest stores the soft-delete intent for a single message.
|
||||
// The message must already be marked read (HTTP 409 otherwise).
|
||||
table DeleteRequest {
|
||||
game_id:common.UUID (required);
|
||||
message_id:common.UUID (required);
|
||||
}
|
||||
|
||||
// DeleteResponse echoes the recipient state after the operation.
|
||||
table DeleteResponse {
|
||||
state:MailRecipientState;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type AdminRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsAdminRequest(buf []byte, offset flatbuffers.UOffsetT) *AdminRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &AdminRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishAdminRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsAdminRequest(buf []byte, offset flatbuffers.UOffsetT) *AdminRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &AdminRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedAdminRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) Target() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) RecipientUserId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) RecipientRaceName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) Recipients() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(12))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) Subject() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(14))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminRequest) Body() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(16))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AdminRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(7)
|
||||
}
|
||||
func AdminRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func AdminRequestAddTarget(builder *flatbuffers.Builder, target flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(target), 0)
|
||||
}
|
||||
func AdminRequestAddRecipientUserId(builder *flatbuffers.Builder, recipientUserId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(recipientUserId), 0)
|
||||
}
|
||||
func AdminRequestAddRecipientRaceName(builder *flatbuffers.Builder, recipientRaceName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(recipientRaceName), 0)
|
||||
}
|
||||
func AdminRequestAddRecipients(builder *flatbuffers.Builder, recipients flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(recipients), 0)
|
||||
}
|
||||
func AdminRequestAddSubject(builder *flatbuffers.Builder, subject flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(subject), 0)
|
||||
}
|
||||
func AdminRequestAddBody(builder *flatbuffers.Builder, body flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(6, flatbuffers.UOffsetT(body), 0)
|
||||
}
|
||||
func AdminRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type AdminResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsAdminResponse(buf []byte, offset flatbuffers.UOffsetT) *AdminResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &AdminResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishAdminResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsAdminResponse(buf []byte, offset flatbuffers.UOffsetT) *AdminResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &AdminResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedAdminResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *AdminResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *AdminResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *AdminResponse) Message(obj *MailMessage) *MailMessage {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailMessage)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *AdminResponse) Receipt(obj *MailBroadcastReceipt) *MailBroadcastReceipt {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailBroadcastReceipt)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AdminResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(2)
|
||||
}
|
||||
func AdminResponseAddMessage(builder *flatbuffers.Builder, message flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(message), 0)
|
||||
}
|
||||
func AdminResponseAddReceipt(builder *flatbuffers.Builder, receipt flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(receipt), 0)
|
||||
}
|
||||
func AdminResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type BroadcastRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsBroadcastRequest(buf []byte, offset flatbuffers.UOffsetT) *BroadcastRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &BroadcastRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishBroadcastRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsBroadcastRequest(buf []byte, offset flatbuffers.UOffsetT) *BroadcastRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &BroadcastRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedBroadcastRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *BroadcastRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *BroadcastRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *BroadcastRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *BroadcastRequest) Subject() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *BroadcastRequest) Body() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BroadcastRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(3)
|
||||
}
|
||||
func BroadcastRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func BroadcastRequestAddSubject(builder *flatbuffers.Builder, subject flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(subject), 0)
|
||||
}
|
||||
func BroadcastRequestAddBody(builder *flatbuffers.Builder, body flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(body), 0)
|
||||
}
|
||||
func BroadcastRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type BroadcastResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsBroadcastResponse(buf []byte, offset flatbuffers.UOffsetT) *BroadcastResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &BroadcastResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishBroadcastResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsBroadcastResponse(buf []byte, offset flatbuffers.UOffsetT) *BroadcastResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &BroadcastResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedBroadcastResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *BroadcastResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *BroadcastResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *BroadcastResponse) Receipt(obj *MailBroadcastReceipt) *MailBroadcastReceipt {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailBroadcastReceipt)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BroadcastResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func BroadcastResponseAddReceipt(builder *flatbuffers.Builder, receipt flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(receipt), 0)
|
||||
}
|
||||
func BroadcastResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type DeleteRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsDeleteRequest(buf []byte, offset flatbuffers.UOffsetT) *DeleteRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &DeleteRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishDeleteRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsDeleteRequest(buf []byte, offset flatbuffers.UOffsetT) *DeleteRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &DeleteRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedDeleteRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *DeleteRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *DeleteRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *DeleteRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *DeleteRequest) MessageId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(2)
|
||||
}
|
||||
func DeleteRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func DeleteRequestAddMessageId(builder *flatbuffers.Builder, messageId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(1, flatbuffers.UOffsetT(messageId), 0)
|
||||
}
|
||||
func DeleteRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type DeleteResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsDeleteResponse(buf []byte, offset flatbuffers.UOffsetT) *DeleteResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &DeleteResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishDeleteResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsDeleteResponse(buf []byte, offset flatbuffers.UOffsetT) *DeleteResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &DeleteResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedDeleteResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *DeleteResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *DeleteResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *DeleteResponse) State(obj *MailRecipientState) *MailRecipientState {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailRecipientState)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func DeleteResponseAddState(builder *flatbuffers.Builder, state flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(state), 0)
|
||||
}
|
||||
func DeleteResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type InboxRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsInboxRequest(buf []byte, offset flatbuffers.UOffsetT) *InboxRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &InboxRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishInboxRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsInboxRequest(buf []byte, offset flatbuffers.UOffsetT) *InboxRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &InboxRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedInboxRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *InboxRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *InboxRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *InboxRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InboxRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func InboxRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func InboxRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type InboxResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsInboxResponse(buf []byte, offset flatbuffers.UOffsetT) *InboxResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &InboxResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishInboxResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsInboxResponse(buf []byte, offset flatbuffers.UOffsetT) *InboxResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &InboxResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedInboxResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *InboxResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *InboxResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *InboxResponse) Items(obj *MailMessage, 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 *InboxResponse) ItemsLength() int {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
return rcv._tab.VectorLen(o)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func InboxResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func InboxResponseAddItems(builder *flatbuffers.Builder, items flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(items), 0)
|
||||
}
|
||||
func InboxResponseStartItemsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
|
||||
return builder.StartVector(4, numElems, 4)
|
||||
}
|
||||
func InboxResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type MailBroadcastReceipt struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsMailBroadcastReceipt(buf []byte, offset flatbuffers.UOffsetT) *MailBroadcastReceipt {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &MailBroadcastReceipt{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishMailBroadcastReceiptBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsMailBroadcastReceipt(buf []byte, offset flatbuffers.UOffsetT) *MailBroadcastReceipt {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &MailBroadcastReceipt{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedMailBroadcastReceiptBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) MessageId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) GameId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) GameName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) Kind() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) SenderKind() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(12))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) Subject() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(14))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) Body() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(16))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) BodyLang() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(18))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) BroadcastScope() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(20))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) CreatedAtMs() int64 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(22))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt64(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) MutateCreatedAtMs(n int64) bool {
|
||||
return rcv._tab.MutateInt64Slot(22, n)
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) RecipientCount() int32 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(24))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt32(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailBroadcastReceipt) MutateRecipientCount(n int32) bool {
|
||||
return rcv._tab.MutateInt32Slot(24, n)
|
||||
}
|
||||
|
||||
func MailBroadcastReceiptStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(11)
|
||||
}
|
||||
func MailBroadcastReceiptAddMessageId(builder *flatbuffers.Builder, messageId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(messageId), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddGameName(builder *flatbuffers.Builder, gameName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(gameName), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddKind(builder *flatbuffers.Builder, kind flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(kind), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddSenderKind(builder *flatbuffers.Builder, senderKind flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(senderKind), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddSubject(builder *flatbuffers.Builder, subject flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(subject), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddBody(builder *flatbuffers.Builder, body flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(6, flatbuffers.UOffsetT(body), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddBodyLang(builder *flatbuffers.Builder, bodyLang flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(7, flatbuffers.UOffsetT(bodyLang), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddBroadcastScope(builder *flatbuffers.Builder, broadcastScope flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(8, flatbuffers.UOffsetT(broadcastScope), 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddCreatedAtMs(builder *flatbuffers.Builder, createdAtMs int64) {
|
||||
builder.PrependInt64Slot(9, createdAtMs, 0)
|
||||
}
|
||||
func MailBroadcastReceiptAddRecipientCount(builder *flatbuffers.Builder, recipientCount int32) {
|
||||
builder.PrependInt32Slot(10, recipientCount, 0)
|
||||
}
|
||||
func MailBroadcastReceiptEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type MailMessage struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsMailMessage(buf []byte, offset flatbuffers.UOffsetT) *MailMessage {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &MailMessage{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishMailMessageBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsMailMessage(buf []byte, offset flatbuffers.UOffsetT) *MailMessage {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &MailMessage{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedMailMessageBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) MessageId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) GameId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) GameName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) Kind() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) SenderKind() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(12))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) SenderUserId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(14))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) SenderUsername() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(16))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) SenderRaceName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(18))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) Subject() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(20))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) Body() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(22))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) BodyLang() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(24))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) BroadcastScope() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(26))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) CreatedAtMs() int64 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(28))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt64(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) MutateCreatedAtMs(n int64) bool {
|
||||
return rcv._tab.MutateInt64Slot(28, n)
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) RecipientUserId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(30))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) RecipientUserName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(32))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) RecipientRaceName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(34))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) ReadAtMs() int64 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(36))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt64(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) MutateReadAtMs(n int64) bool {
|
||||
return rcv._tab.MutateInt64Slot(36, n)
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) DeletedAtMs() int64 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(38))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt64(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) MutateDeletedAtMs(n int64) bool {
|
||||
return rcv._tab.MutateInt64Slot(38, n)
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) TranslatedSubject() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(40))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) TranslatedBody() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(42))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) TranslationLang() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(44))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailMessage) Translator() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(46))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MailMessageStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(22)
|
||||
}
|
||||
func MailMessageAddMessageId(builder *flatbuffers.Builder, messageId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(messageId), 0)
|
||||
}
|
||||
func MailMessageAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func MailMessageAddGameName(builder *flatbuffers.Builder, gameName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(gameName), 0)
|
||||
}
|
||||
func MailMessageAddKind(builder *flatbuffers.Builder, kind flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(kind), 0)
|
||||
}
|
||||
func MailMessageAddSenderKind(builder *flatbuffers.Builder, senderKind flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(senderKind), 0)
|
||||
}
|
||||
func MailMessageAddSenderUserId(builder *flatbuffers.Builder, senderUserId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(senderUserId), 0)
|
||||
}
|
||||
func MailMessageAddSenderUsername(builder *flatbuffers.Builder, senderUsername flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(6, flatbuffers.UOffsetT(senderUsername), 0)
|
||||
}
|
||||
func MailMessageAddSenderRaceName(builder *flatbuffers.Builder, senderRaceName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(7, flatbuffers.UOffsetT(senderRaceName), 0)
|
||||
}
|
||||
func MailMessageAddSubject(builder *flatbuffers.Builder, subject flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(8, flatbuffers.UOffsetT(subject), 0)
|
||||
}
|
||||
func MailMessageAddBody(builder *flatbuffers.Builder, body flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(9, flatbuffers.UOffsetT(body), 0)
|
||||
}
|
||||
func MailMessageAddBodyLang(builder *flatbuffers.Builder, bodyLang flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(10, flatbuffers.UOffsetT(bodyLang), 0)
|
||||
}
|
||||
func MailMessageAddBroadcastScope(builder *flatbuffers.Builder, broadcastScope flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(11, flatbuffers.UOffsetT(broadcastScope), 0)
|
||||
}
|
||||
func MailMessageAddCreatedAtMs(builder *flatbuffers.Builder, createdAtMs int64) {
|
||||
builder.PrependInt64Slot(12, createdAtMs, 0)
|
||||
}
|
||||
func MailMessageAddRecipientUserId(builder *flatbuffers.Builder, recipientUserId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(13, flatbuffers.UOffsetT(recipientUserId), 0)
|
||||
}
|
||||
func MailMessageAddRecipientUserName(builder *flatbuffers.Builder, recipientUserName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(14, flatbuffers.UOffsetT(recipientUserName), 0)
|
||||
}
|
||||
func MailMessageAddRecipientRaceName(builder *flatbuffers.Builder, recipientRaceName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(15, flatbuffers.UOffsetT(recipientRaceName), 0)
|
||||
}
|
||||
func MailMessageAddReadAtMs(builder *flatbuffers.Builder, readAtMs int64) {
|
||||
builder.PrependInt64Slot(16, readAtMs, 0)
|
||||
}
|
||||
func MailMessageAddDeletedAtMs(builder *flatbuffers.Builder, deletedAtMs int64) {
|
||||
builder.PrependInt64Slot(17, deletedAtMs, 0)
|
||||
}
|
||||
func MailMessageAddTranslatedSubject(builder *flatbuffers.Builder, translatedSubject flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(18, flatbuffers.UOffsetT(translatedSubject), 0)
|
||||
}
|
||||
func MailMessageAddTranslatedBody(builder *flatbuffers.Builder, translatedBody flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(19, flatbuffers.UOffsetT(translatedBody), 0)
|
||||
}
|
||||
func MailMessageAddTranslationLang(builder *flatbuffers.Builder, translationLang flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(20, flatbuffers.UOffsetT(translationLang), 0)
|
||||
}
|
||||
func MailMessageAddTranslator(builder *flatbuffers.Builder, translator flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(21, flatbuffers.UOffsetT(translator), 0)
|
||||
}
|
||||
func MailMessageEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type MailRecipientState struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsMailRecipientState(buf []byte, offset flatbuffers.UOffsetT) *MailRecipientState {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &MailRecipientState{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishMailRecipientStateBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsMailRecipientState(buf []byte, offset flatbuffers.UOffsetT) *MailRecipientState {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &MailRecipientState{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedMailRecipientStateBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) MessageId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) ReadAtMs() int64 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt64(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) MutateReadAtMs(n int64) bool {
|
||||
return rcv._tab.MutateInt64Slot(6, n)
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) DeletedAtMs() int64 {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
return rcv._tab.GetInt64(o + rcv._tab.Pos)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rcv *MailRecipientState) MutateDeletedAtMs(n int64) bool {
|
||||
return rcv._tab.MutateInt64Slot(8, n)
|
||||
}
|
||||
|
||||
func MailRecipientStateStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(3)
|
||||
}
|
||||
func MailRecipientStateAddMessageId(builder *flatbuffers.Builder, messageId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(messageId), 0)
|
||||
}
|
||||
func MailRecipientStateAddReadAtMs(builder *flatbuffers.Builder, readAtMs int64) {
|
||||
builder.PrependInt64Slot(1, readAtMs, 0)
|
||||
}
|
||||
func MailRecipientStateAddDeletedAtMs(builder *flatbuffers.Builder, deletedAtMs int64) {
|
||||
builder.PrependInt64Slot(2, deletedAtMs, 0)
|
||||
}
|
||||
func MailRecipientStateEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type MessageGetRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsMessageGetRequest(buf []byte, offset flatbuffers.UOffsetT) *MessageGetRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &MessageGetRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishMessageGetRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsMessageGetRequest(buf []byte, offset flatbuffers.UOffsetT) *MessageGetRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &MessageGetRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedMessageGetRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *MessageGetRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *MessageGetRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *MessageGetRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MessageGetRequest) MessageId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MessageGetRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(2)
|
||||
}
|
||||
func MessageGetRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func MessageGetRequestAddMessageId(builder *flatbuffers.Builder, messageId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(1, flatbuffers.UOffsetT(messageId), 0)
|
||||
}
|
||||
func MessageGetRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type MessageGetResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsMessageGetResponse(buf []byte, offset flatbuffers.UOffsetT) *MessageGetResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &MessageGetResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishMessageGetResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsMessageGetResponse(buf []byte, offset flatbuffers.UOffsetT) *MessageGetResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &MessageGetResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedMessageGetResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *MessageGetResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *MessageGetResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *MessageGetResponse) Message(obj *MailMessage) *MailMessage {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailMessage)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MessageGetResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func MessageGetResponseAddMessage(builder *flatbuffers.Builder, message flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(message), 0)
|
||||
}
|
||||
func MessageGetResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type ReadRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsReadRequest(buf []byte, offset flatbuffers.UOffsetT) *ReadRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &ReadRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishReadRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsReadRequest(buf []byte, offset flatbuffers.UOffsetT) *ReadRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &ReadRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedReadRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *ReadRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *ReadRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *ReadRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *ReadRequest) MessageId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(2)
|
||||
}
|
||||
func ReadRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func ReadRequestAddMessageId(builder *flatbuffers.Builder, messageId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(1, flatbuffers.UOffsetT(messageId), 0)
|
||||
}
|
||||
func ReadRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type ReadResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsReadResponse(buf []byte, offset flatbuffers.UOffsetT) *ReadResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &ReadResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishReadResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsReadResponse(buf []byte, offset flatbuffers.UOffsetT) *ReadResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &ReadResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedReadResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *ReadResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *ReadResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *ReadResponse) State(obj *MailRecipientState) *MailRecipientState {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailRecipientState)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func ReadResponseAddState(builder *flatbuffers.Builder, state flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(state), 0)
|
||||
}
|
||||
func ReadResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type SendRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsSendRequest(buf []byte, offset flatbuffers.UOffsetT) *SendRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &SendRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSendRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsSendRequest(buf []byte, offset flatbuffers.UOffsetT) *SendRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &SendRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedSendRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) RecipientUserId() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) RecipientRaceName() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) Subject() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *SendRequest) Body() []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(12))
|
||||
if o != 0 {
|
||||
return rcv._tab.ByteVector(o + rcv._tab.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(5)
|
||||
}
|
||||
func SendRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func SendRequestAddRecipientUserId(builder *flatbuffers.Builder, recipientUserId flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(recipientUserId), 0)
|
||||
}
|
||||
func SendRequestAddRecipientRaceName(builder *flatbuffers.Builder, recipientRaceName flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(recipientRaceName), 0)
|
||||
}
|
||||
func SendRequestAddSubject(builder *flatbuffers.Builder, subject flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(subject), 0)
|
||||
}
|
||||
func SendRequestAddBody(builder *flatbuffers.Builder, body flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(body), 0)
|
||||
}
|
||||
func SendRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type SendResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsSendResponse(buf []byte, offset flatbuffers.UOffsetT) *SendResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &SendResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSendResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsSendResponse(buf []byte, offset flatbuffers.UOffsetT) *SendResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &SendResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedSendResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *SendResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *SendResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *SendResponse) Message(obj *MailMessage) *MailMessage {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := rcv._tab.Indirect(o + rcv._tab.Pos)
|
||||
if obj == nil {
|
||||
obj = new(MailMessage)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func SendResponseAddMessage(builder *flatbuffers.Builder, message flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(message), 0)
|
||||
}
|
||||
func SendResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
|
||||
common "galaxy/schema/fbs/common"
|
||||
)
|
||||
|
||||
type SentRequest struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsSentRequest(buf []byte, offset flatbuffers.UOffsetT) *SentRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &SentRequest{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSentRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsSentRequest(buf []byte, offset flatbuffers.UOffsetT) *SentRequest {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &SentRequest{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedSentRequestBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *SentRequest) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *SentRequest) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *SentRequest) GameId(obj *common.UUID) *common.UUID {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
x := o + rcv._tab.Pos
|
||||
if obj == nil {
|
||||
obj = new(common.UUID)
|
||||
}
|
||||
obj.Init(rcv._tab.Bytes, x)
|
||||
return obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SentRequestStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func SentRequestAddGameId(builder *flatbuffers.Builder, gameId flatbuffers.UOffsetT) {
|
||||
builder.PrependStructSlot(0, flatbuffers.UOffsetT(gameId), 0)
|
||||
}
|
||||
func SentRequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
|
||||
|
||||
package diplomail
|
||||
|
||||
import (
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
)
|
||||
|
||||
type SentResponse struct {
|
||||
_tab flatbuffers.Table
|
||||
}
|
||||
|
||||
func GetRootAsSentResponse(buf []byte, offset flatbuffers.UOffsetT) *SentResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset:])
|
||||
x := &SentResponse{}
|
||||
x.Init(buf, n+offset)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSentResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.Finish(offset)
|
||||
}
|
||||
|
||||
func GetSizePrefixedRootAsSentResponse(buf []byte, offset flatbuffers.UOffsetT) *SentResponse {
|
||||
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
|
||||
x := &SentResponse{}
|
||||
x.Init(buf, n+offset+flatbuffers.SizeUint32)
|
||||
return x
|
||||
}
|
||||
|
||||
func FinishSizePrefixedSentResponseBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
|
||||
builder.FinishSizePrefixed(offset)
|
||||
}
|
||||
|
||||
func (rcv *SentResponse) Init(buf []byte, i flatbuffers.UOffsetT) {
|
||||
rcv._tab.Bytes = buf
|
||||
rcv._tab.Pos = i
|
||||
}
|
||||
|
||||
func (rcv *SentResponse) Table() flatbuffers.Table {
|
||||
return rcv._tab
|
||||
}
|
||||
|
||||
func (rcv *SentResponse) Items(obj *MailMessage, 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 *SentResponse) ItemsLength() int {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
|
||||
if o != 0 {
|
||||
return rcv._tab.VectorLen(o)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func SentResponseStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(1)
|
||||
}
|
||||
func SentResponseAddItems(builder *flatbuffers.Builder, items flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(items), 0)
|
||||
}
|
||||
func SentResponseStartItemsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
|
||||
return builder.StartVector(4, numElems, 4)
|
||||
}
|
||||
func SentResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
@@ -382,3 +382,55 @@ func encodeBattleOffsetVector(
|
||||
}
|
||||
return builder.EndVector(length)
|
||||
}
|
||||
|
||||
// GameBattleRequestToPayload converts model.GameBattleRequest to
|
||||
// FlatBuffers bytes suitable for the authenticated gateway transport.
|
||||
func GameBattleRequestToPayload(req *model.GameBattleRequest) ([]byte, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("encode game battle request payload: request is nil")
|
||||
}
|
||||
|
||||
builder := flatbuffers.NewBuilder(64)
|
||||
|
||||
gameHi, gameLo := uuidToHiLo(req.GameID)
|
||||
battleHi, battleLo := uuidToHiLo(req.BattleID)
|
||||
|
||||
fbs.GameBattleRequestStart(builder)
|
||||
fbs.GameBattleRequestAddGameId(builder, fbs.CreateUUID(builder, gameHi, gameLo))
|
||||
fbs.GameBattleRequestAddTurn(builder, uint32(req.Turn))
|
||||
fbs.GameBattleRequestAddBattleId(builder, fbs.CreateUUID(builder, battleHi, battleLo))
|
||||
offset := fbs.GameBattleRequestEnd(builder)
|
||||
fbs.FinishGameBattleRequestBuffer(builder, offset)
|
||||
|
||||
return builder.FinishedBytes(), nil
|
||||
}
|
||||
|
||||
// PayloadToGameBattleRequest converts FlatBuffers payload bytes into
|
||||
// model.GameBattleRequest.
|
||||
func PayloadToGameBattleRequest(data []byte) (result *model.GameBattleRequest, err error) {
|
||||
if len(data) == 0 {
|
||||
return nil, errors.New("decode game battle request payload: data is empty")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
result = nil
|
||||
err = fmt.Errorf("decode game battle request payload: panic recovered: %v", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
req := fbs.GetRootAsGameBattleRequest(data, 0)
|
||||
gameID := req.GameId(nil)
|
||||
if gameID == nil {
|
||||
return nil, errors.New("decode game battle request payload: game_id is missing")
|
||||
}
|
||||
battleID := req.BattleId(nil)
|
||||
if battleID == nil {
|
||||
return nil, errors.New("decode game battle request payload: battle_id is missing")
|
||||
}
|
||||
return &model.GameBattleRequest{
|
||||
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
|
||||
Turn: uint(req.Turn()),
|
||||
BattleID: uuidFromHiLo(battleID.Hi(), battleID.Lo()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ BACKEND_DEV_SANDBOX_ENGINE_VERSION=0.1.0
|
||||
BACKEND_DEV_SANDBOX_PLAYER_COUNT=20
|
||||
|
||||
# `123456` short-circuits the email-code path for the dev account.
|
||||
# Leave empty in environments where real Mailpit codes must be used.
|
||||
# This is also the docker-compose default — set the variable to an
|
||||
# empty string here when the environment must rely on real Mailpit
|
||||
# codes (e.g. mail-flow QA).
|
||||
BACKEND_AUTH_DEV_FIXED_CODE=123456
|
||||
|
||||
# Name of the external Docker bridge the host Caddy is attached to.
|
||||
|
||||
@@ -20,6 +20,15 @@
|
||||
@api host api.galaxy.lan
|
||||
handle @api {
|
||||
# Connect-Web (authenticated) lives on a separate listener
|
||||
# (`GATEWAY_AUTHENTICATED_GRPC_ADDR=:9090`). Anything else —
|
||||
# public auth, healthz — is the public REST listener on
|
||||
# `:8080`. The split mirrors the Vite dev-server proxy in
|
||||
# `ui/frontend/vite.config.ts`.
|
||||
@connect path /galaxy.gateway.v1.EdgeGateway/*
|
||||
handle @connect {
|
||||
reverse_proxy galaxy-api:9090
|
||||
}
|
||||
reverse_proxy galaxy-api:8080
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ seed-ui:
|
||||
@echo "building UI (vite build)…"
|
||||
(cd $(REPO_ROOT)/ui/frontend && \
|
||||
VITE_GATEWAY_BASE_URL=https://api.galaxy.lan \
|
||||
VITE_GALAXY_DEV_AFFORDANCES=true \
|
||||
VITE_GATEWAY_RESPONSE_PUBLIC_KEY=$$(cat $(REPO_ROOT)/ui/frontend/.env.development \
|
||||
| sed -n 's/^VITE_GATEWAY_RESPONSE_PUBLIC_KEY=//p') \
|
||||
pnpm build)
|
||||
|
||||
@@ -91,14 +91,16 @@ calls `make clean-data`.
|
||||
|
||||
## Logging in
|
||||
|
||||
The same dev-mode email-code override as `tools/local-dev/` applies:
|
||||
The same dev-mode email-code override as `tools/local-dev/` applies,
|
||||
and the dev-deploy compose ships with it enabled by default:
|
||||
|
||||
1. Enter `dev@galaxy.lan` (or whatever `BACKEND_DEV_SANDBOX_EMAIL`
|
||||
resolves to) in the login form.
|
||||
2. Submit `123456` as the code if `BACKEND_AUTH_DEV_FIXED_CODE` is
|
||||
non-empty. Otherwise open Mailpit at
|
||||
`http://galaxy-mailpit:8025/` from inside the network or proxy it
|
||||
through the host Caddy when needed.
|
||||
2. Submit `123456` as the code — the docker-compose default for
|
||||
`BACKEND_AUTH_DEV_FIXED_CODE` is `123456`, so the bcrypt-hashed
|
||||
email code stays a fallback. To force real Mailpit codes (e.g. for
|
||||
mail-flow QA), set `BACKEND_AUTH_DEV_FIXED_CODE=` (empty) in a
|
||||
local `.env` and `make rebuild`.
|
||||
|
||||
The fixed-code override is rejected by production env loaders, so it
|
||||
cannot leak into the prod environment.
|
||||
|
||||
@@ -101,8 +101,18 @@ services:
|
||||
BACKEND_NOTIFICATION_WORKER_INTERVAL: 500ms
|
||||
BACKEND_OTEL_TRACES_EXPORTER: none
|
||||
BACKEND_OTEL_METRICS_EXPORTER: none
|
||||
BACKEND_AUTH_DEV_FIXED_CODE: ${BACKEND_AUTH_DEV_FIXED_CODE:-}
|
||||
BACKEND_DEV_SANDBOX_EMAIL: ${BACKEND_DEV_SANDBOX_EMAIL:-}
|
||||
# Long-lived dev environment always opts into the fixed-code
|
||||
# override so a returning developer can sign in with `123456`
|
||||
# even after the matching browser session was cleared (the real
|
||||
# bcrypt-hashed code is single-use). Set the var to an empty
|
||||
# string in `.env` to disable.
|
||||
BACKEND_AUTH_DEV_FIXED_CODE: ${BACKEND_AUTH_DEV_FIXED_CODE:-123456}
|
||||
# Long-lived dev environment always bootstraps the "Dev Sandbox"
|
||||
# game owned by this email so a freshly redeployed stack already
|
||||
# has one ready-to-play game in the lobby. Set the variable to an
|
||||
# empty string in `.env` to disable the bootstrap (e.g. for a
|
||||
# cold-start QA pass).
|
||||
BACKEND_DEV_SANDBOX_EMAIL: ${BACKEND_DEV_SANDBOX_EMAIL:-dev@galaxy.lan}
|
||||
BACKEND_DEV_SANDBOX_ENGINE_IMAGE: ${BACKEND_DEV_SANDBOX_ENGINE_IMAGE:-galaxy-engine:dev}
|
||||
BACKEND_DEV_SANDBOX_ENGINE_VERSION: ${BACKEND_DEV_SANDBOX_ENGINE_VERSION:-0.1.0}
|
||||
BACKEND_DEV_SANDBOX_PLAYER_COUNT: ${BACKEND_DEV_SANDBOX_PLAYER_COUNT:-20}
|
||||
@@ -161,6 +171,7 @@ services:
|
||||
# https://api.galaxy.lan. Browsers therefore issue cross-origin
|
||||
# requests to the gateway and need an explicit allow-list.
|
||||
GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS: "https://www.galaxy.lan"
|
||||
GATEWAY_AUTHENTICATED_GRPC_CORS_ALLOWED_ORIGINS: "https://www.galaxy.lan"
|
||||
# Anti-abuse defaults are looser than production: the dev
|
||||
# environment is shared by a handful of trusted testers who
|
||||
# frequently hammer the same identity to reproduce flows.
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ 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/common.fbs ../pkg/schema/fbs/lobby.fbs ../pkg/schema/fbs/user.fbs ../pkg/schema/fbs/report.fbs ../pkg/schema/fbs/order.fbs
|
||||
FBS_INPUTS := ../pkg/schema/fbs/common.fbs ../pkg/schema/fbs/lobby.fbs ../pkg/schema/fbs/user.fbs ../pkg/schema/fbs/report.fbs ../pkg/schema/fbs/order.fbs ../pkg/schema/fbs/diplomail.fbs ../pkg/schema/fbs/battle.fbs
|
||||
|
||||
help:
|
||||
@echo "ui targets:"
|
||||
|
||||
+66
-2
@@ -3070,9 +3070,73 @@ bottom):
|
||||
- animated transitions when survivors re-distribute after an
|
||||
elimination (currently hard-jumps).
|
||||
|
||||
## Phase 28. Diplomatic Mail View
|
||||
## ~~Phase 28. Diplomatic Mail View~~
|
||||
|
||||
Status: pending.
|
||||
Status: done (CI gate passed on run 136 — go-unit / ui-test / integration all green at commit 6d0272b).
|
||||
|
||||
Decisions baked in during implementation:
|
||||
|
||||
1. **Transport: ConnectRPC `user.games.mail.*`.** Eight new
|
||||
authenticated commands (inbox / sent / message.get / send /
|
||||
broadcast / admin / read / delete) plumbed end-to-end through
|
||||
the existing gateway → backend REST surface. Schemas in
|
||||
`pkg/schema/fbs/diplomail.fbs`; constants in
|
||||
`pkg/model/diplomail/diplomail.go`; gateway translation in
|
||||
`gateway/internal/backendclient/mail_commands.go`.
|
||||
2. **Recipient by race name.** The send / admin endpoints accept
|
||||
an alternative `recipient_race_name` field; backend resolves it
|
||||
via `Memberships.ListMembers(gameID, "active")`. The UI feeds
|
||||
the picker straight off `report.races[].name` — no client-side
|
||||
memberships RPC.
|
||||
3. **`sender_race_name` snapshot.** New nullable column on
|
||||
`diplomail_messages`, populated for `sender_kind='player'`
|
||||
senders that have an active membership at send time. Drives the
|
||||
per-race threading on the client.
|
||||
4. **/sent returns full message detail.** Backend's bulk sent
|
||||
listing now returns the same `UserMailMessageDetail` shape as
|
||||
`/inbox`, one row per (message, recipient). The UI collapses
|
||||
broadcasts by `message_id` into a single stand-alone item.
|
||||
5. **Threading + stand-alones.** `MailStore.entries` groups
|
||||
personal messages by the other party's race name. System,
|
||||
admin, and outgoing broadcasts render as stand-alone items in
|
||||
the same list pane.
|
||||
6. **No read receipts.** `read_at` and `deleted_at` drive the
|
||||
badge counter and soft-delete affordance but are never shown
|
||||
to the user.
|
||||
7. **Header badge.** Inline pill on the view-menu "diplomatic
|
||||
mail" row, fed by `mailStore.unreadCount`. No always-visible
|
||||
chrome added.
|
||||
8. **Push event reuse.** A new
|
||||
`eventStream.on("diplomail.message.received", …)` handler in
|
||||
`routes/games/[id]/+layout.svelte` parses the verified payload,
|
||||
refreshes the inbox, and raises a `toast.show` with a "view"
|
||||
deep-link.
|
||||
9. **Translation toggle.** Per-message Show original / Show
|
||||
translation toggle inside both `thread-pane.svelte` and
|
||||
`system-item-pane.svelte`; the body defaults to the cached
|
||||
translation when present.
|
||||
|
||||
Artifacts (delivered):
|
||||
|
||||
- backend: `internal/postgres/migrations/00001_init.sql`,
|
||||
`internal/diplomail/{types.go,store.go,service.go,admin_send.go,diplomail_e2e_test.go,README.md}`,
|
||||
`internal/server/{handlers_user_mail.go,handlers_admin_diplomail.go}`,
|
||||
`openapi.yaml`;
|
||||
- wire: `pkg/schema/fbs/diplomail.fbs` + generated Go and TS
|
||||
bindings; `pkg/model/diplomail/diplomail.go`;
|
||||
- gateway: `gateway/internal/backendclient/{mail_commands.go,routes.go,mail_commands_test.go}`,
|
||||
`gateway/cmd/gateway/main.go`;
|
||||
- ui: `ui/frontend/src/api/diplomail.ts`,
|
||||
`ui/frontend/src/lib/mail-store.svelte.ts`,
|
||||
`ui/frontend/src/lib/active-view/mail.svelte` (+ subdir
|
||||
`mail/{thread-list,thread-pane,system-item-pane,compose,system-titles}.svelte|.ts`),
|
||||
`ui/frontend/src/lib/header/view-menu.svelte`,
|
||||
`ui/frontend/src/routes/games/[id]/+layout.svelte`,
|
||||
`ui/frontend/src/lib/i18n/locales/{en,ru}.ts`;
|
||||
- docs: `ui/docs/diplomail-ui.md`, `docs/FUNCTIONAL.md` §11.4 +
|
||||
mirror in `docs/FUNCTIONAL_ru.md`.
|
||||
|
||||
Original phase brief follows.
|
||||
|
||||
Goal: implement a mail inbox and compose flow as a dedicated view that
|
||||
replaces the map.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# In-game diplomatic mail UI
|
||||
|
||||
Phase 28 wires the in-game mail view that consumes the `diplomail`
|
||||
subsystem in the backend. The route lives at `/games/:id/mail`
|
||||
(registered in Phase 10) and replaces the active view when the user
|
||||
opens the "diplomatic mail" entry in the header menu.
|
||||
|
||||
## Wire surface
|
||||
|
||||
Eight ConnectRPC commands sit between UI and backend, all under the
|
||||
`user.games.mail.*` namespace:
|
||||
|
||||
| Command | Backend REST endpoint |
|
||||
|---|---|
|
||||
| `user.games.mail.inbox` | `GET /api/v1/user/games/{id}/mail/inbox` |
|
||||
| `user.games.mail.sent` | `GET …/mail/sent` |
|
||||
| `user.games.mail.message.get` | `GET …/mail/messages/{message_id}` |
|
||||
| `user.games.mail.send` | `POST …/mail/messages` |
|
||||
| `user.games.mail.broadcast` | `POST …/mail/broadcast` |
|
||||
| `user.games.mail.admin` | `POST …/mail/admin` |
|
||||
| `user.games.mail.read` | `POST …/mail/messages/{id}/read` |
|
||||
| `user.games.mail.delete` | `DELETE …/mail/messages/{id}` |
|
||||
|
||||
The FlatBuffers schemas live under
|
||||
[`pkg/schema/fbs/diplomail.fbs`](../../pkg/schema/fbs/diplomail.fbs);
|
||||
the gateway translation lives in
|
||||
[`gateway/internal/backendclient/mail_commands.go`](../../gateway/internal/backendclient/mail_commands.go).
|
||||
|
||||
## Recipient by race name
|
||||
|
||||
The compose flow does **not** consult a memberships listing. The
|
||||
recipient picker reads `gameState.report.races[].name` (the Phase 22
|
||||
projection of `report.player[]`), and the send request carries the
|
||||
chosen race name as `recipient_race_name`. The backend resolves it
|
||||
against `Memberships.ListMembers(gameID, "active")` and rejects with
|
||||
`forbidden` if the matching member is no longer active. This keeps
|
||||
the UI off the lobby surface for the common case.
|
||||
|
||||
## Threading model
|
||||
|
||||
`MailStore.entries` is the derived rune the active view consumes. It
|
||||
projects the union of inbox and sent into:
|
||||
|
||||
- **Per-race threads** — every personal message keyed by another
|
||||
race contributes to a thread keyed on that race name. Incoming is
|
||||
keyed on `sender_race_name`; outgoing is keyed on
|
||||
`recipient_race_name`. Thread messages are sorted oldest → newest
|
||||
for chat-style rendering; the unread badge counts incoming
|
||||
`read_at === null` rows only.
|
||||
- **Stand-alone items** — system mail (`sender_kind=system`), admin
|
||||
notifications (`sender_kind=admin`), and the caller's own
|
||||
paid-tier broadcasts (`broadcast_scope=game_broadcast`). Backend
|
||||
returns one row per recipient for paid-tier broadcasts; the UI
|
||||
collapses them by `message_id` into a single stand-alone item.
|
||||
|
||||
`read_at` and `deleted_at` are not surfaced to the user in any pane
|
||||
— they only drive the badge counter and the optimistic mark-read
|
||||
state. This is intentional (per Phase 28 decisions): the user-facing
|
||||
spec for diplomatic mail does not promise read receipts.
|
||||
|
||||
## Translation toggle
|
||||
|
||||
When a message detail carries `translated_body`, the body and (if
|
||||
non-empty) subject default to the translated rendering. Each message
|
||||
pane exposes a "Show original" / "Show translation" button that
|
||||
flips the per-message state. Messages without a cached translation
|
||||
render the original directly with no toggle.
|
||||
|
||||
## Push events
|
||||
|
||||
`diplomail.message.received` push frames are dispatched from
|
||||
`api/events.svelte.ts` via the singleton SubscribeEvents stream. The
|
||||
in-game layout (`routes/games/[id]/+layout.svelte`) parses the
|
||||
verified payload, calls `mailStore.applyPushEvent(gameId)` (which
|
||||
re-fetches the inbox — the payload only carries a preview), and
|
||||
raises a toast through `lib/toast.svelte.ts` with a "view"
|
||||
deep-link to `/games/:id/mail`.
|
||||
|
||||
The header view-menu's mail entry shows `mailStore.unreadCount` as
|
||||
an inline pill — the only chrome the badge needs.
|
||||
|
||||
## Layout
|
||||
|
||||
Desktop (≥ 768 px) renders a two-pane CSS grid: list on the left,
|
||||
detail on the right. Mobile flips to a single-pane stack; tapping a
|
||||
list row hides the list and shows the detail with a back button.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Bodies render through Svelte's default text-content path (no HTML
|
||||
parsing) per the backend rule of treating message text as plain
|
||||
UTF-8.
|
||||
- The compose dialog uses native form controls; the recipient
|
||||
picker is a `<select>` so screen-readers and keyboard users get
|
||||
the standard semantics.
|
||||
- The reply box and the compose body are real `<textarea>`s so
|
||||
shift-enter newlines, paste, and selection behave correctly.
|
||||
@@ -16,3 +16,9 @@ VITE_GATEWAY_BASE_URL=http://localhost:5173
|
||||
# key. Pairs with `tools/local-dev/keys/gateway-response.pem`. The pair
|
||||
# is dev-only — see `tools/local-dev/keys/README.md` before rotating.
|
||||
VITE_GATEWAY_RESPONSE_PUBLIC_KEY=nIG54tCuNiIKrazt8Hh7YxmmU/BhpseGhIIgj164Chw=
|
||||
|
||||
# Opt in to dev-time UI affordances that should never reach a
|
||||
# production bundle — currently the synthetic-report loader in the
|
||||
# lobby. Mirror this flag in any long-lived dev build (e.g.
|
||||
# `dev-deploy.yaml`); the prod build path leaves it unset.
|
||||
VITE_GALAXY_DEV_AFFORDANCES=true
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
// Battle-report fetcher used by the Battle Viewer page.
|
||||
//
|
||||
// Phase 27 ships the BattleViewer as a logically isolated component
|
||||
// that accepts a `BattleReport` matching `pkg/model/report/battle.go`.
|
||||
// This module owns the type mirror and a single `fetchBattle` entry
|
||||
// point. In synthetic mode (development & e2e fixtures), the loader
|
||||
// falls back to a local fixture so the UI tests don't depend on a
|
||||
// running engine; otherwise it issues a real `GET` against the
|
||||
// backend gateway route added in Phase 27 step 3.
|
||||
// Phase 28 migrates this surface off the raw REST passthrough onto the
|
||||
// `user.games.battle` ConnectRPC command — the same signed envelope the
|
||||
// other authenticated traffic rides. The synthetic-mode short-circuit
|
||||
// stays so DEV / e2e tests can render fixtures without a live gateway.
|
||||
|
||||
import { Builder, ByteBuffer } from "flatbuffers";
|
||||
|
||||
import type { GalaxyClient } from "./galaxy-client";
|
||||
import { uuidToHiLo } from "./game-state";
|
||||
import { isSyntheticGameId } from "./synthetic-report";
|
||||
import { lookupSyntheticBattle } from "./synthetic-battle";
|
||||
import {
|
||||
BattleActionReport as FbsBattleActionReport,
|
||||
BattleReport as FbsBattleReport,
|
||||
BattleReportGroup as FbsBattleReportGroup,
|
||||
GameBattleRequest,
|
||||
RaceEntry,
|
||||
ShipEntry,
|
||||
UUID,
|
||||
} from "../proto/galaxy/fbs/battle";
|
||||
import { ErrorResponse as FbsErrorResponse } from "../proto/galaxy/fbs/lobby";
|
||||
|
||||
/**
|
||||
* BattleReport is the wire shape returned by the engine endpoint
|
||||
* `GET /api/v1/battle/:turn/:uuid` and forwarded by the backend
|
||||
* gateway as `GET /api/v1/user/games/{game_id}/battles/{turn}/{battle_id}`.
|
||||
* Fields mirror `pkg/model/report/battle.go`.
|
||||
* BattleReport mirrors the on-wire battle shape the BattleViewer
|
||||
* renders. Fields match `pkg/model/report/battle.go`; integer-keyed
|
||||
* maps from the underlying model are surfaced as string-keyed
|
||||
* `Record`s so the existing components (race / ship lookup, mass
|
||||
* scaling, timeline) keep their current types.
|
||||
*/
|
||||
export interface BattleReport {
|
||||
id: string;
|
||||
@@ -46,20 +58,28 @@ export interface BattleActionReport {
|
||||
}
|
||||
|
||||
export class BattleFetchError extends Error {
|
||||
constructor(public readonly status: number, message: string) {
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "BattleFetchError";
|
||||
}
|
||||
}
|
||||
|
||||
const MESSAGE_TYPE = "user.games.battle";
|
||||
const RESULT_CODE_OK = "ok";
|
||||
|
||||
/**
|
||||
* fetchBattle returns the `BattleReport` for the supplied game, turn,
|
||||
* and battle id. In synthetic-report mode (DEV / e2e) the lookup is
|
||||
* served from `synthetic-battle.ts`; otherwise the function calls the
|
||||
* backend gateway route. Throws `BattleFetchError` with the upstream
|
||||
* status on validation or transport failure.
|
||||
* `user.games.battle` ConnectRPC command through the supplied
|
||||
* `GalaxyClient`. Throws `BattleFetchError` with the upstream HTTP
|
||||
* status (or `0` for transport-level failures) on error.
|
||||
*/
|
||||
export async function fetchBattle(
|
||||
client: GalaxyClient,
|
||||
gameId: string,
|
||||
turn: number,
|
||||
battleId: string,
|
||||
@@ -71,18 +91,171 @@ export async function fetchBattle(
|
||||
}
|
||||
return fixture;
|
||||
}
|
||||
const path = `/api/v1/user/games/${encodeURIComponent(gameId)}/battles/${turn}/${encodeURIComponent(battleId)}`;
|
||||
const response = await fetch(path, {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
if (response.status === 404) {
|
||||
throw new BattleFetchError(404, "battle not found");
|
||||
|
||||
const payload = encodeRequest(gameId, turn, battleId);
|
||||
const result = await client.executeCommand(MESSAGE_TYPE, payload);
|
||||
if (result.resultCode !== RESULT_CODE_OK) {
|
||||
throw decodeError(result.resultCode, result.payloadBytes);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new BattleFetchError(
|
||||
response.status,
|
||||
`battle fetch failed: ${response.status}`,
|
||||
);
|
||||
}
|
||||
return (await response.json()) as BattleReport;
|
||||
return decodeBattleReport(result.payloadBytes);
|
||||
}
|
||||
|
||||
function encodeRequest(
|
||||
gameId: string,
|
||||
turn: number,
|
||||
battleId: string,
|
||||
): Uint8Array {
|
||||
const builder = new Builder(96);
|
||||
const [gameHi, gameLo] = uuidToHiLo(gameId);
|
||||
const [battleHi, battleLo] = uuidToHiLo(battleId);
|
||||
GameBattleRequest.startGameBattleRequest(builder);
|
||||
GameBattleRequest.addGameId(
|
||||
builder,
|
||||
UUID.createUUID(builder, gameHi, gameLo),
|
||||
);
|
||||
GameBattleRequest.addTurn(builder, turn);
|
||||
GameBattleRequest.addBattleId(
|
||||
builder,
|
||||
UUID.createUUID(builder, battleHi, battleLo),
|
||||
);
|
||||
builder.finish(GameBattleRequest.endGameBattleRequest(builder));
|
||||
return builder.asUint8Array();
|
||||
}
|
||||
|
||||
function decodeError(resultCode: string, payload: Uint8Array): BattleFetchError {
|
||||
let message = resultCode;
|
||||
try {
|
||||
const errorResponse = FbsErrorResponse.getRootAsErrorResponse(
|
||||
new ByteBuffer(payload),
|
||||
);
|
||||
const body = errorResponse.error();
|
||||
if (body) {
|
||||
message = body.message() ?? resultCode;
|
||||
}
|
||||
} catch (_err) {
|
||||
// fall through to the raw result code
|
||||
}
|
||||
const status = mapResultCodeToStatus(resultCode);
|
||||
return new BattleFetchError(status, message);
|
||||
}
|
||||
|
||||
function mapResultCodeToStatus(resultCode: string): number {
|
||||
switch (resultCode) {
|
||||
case "not_found":
|
||||
return 404;
|
||||
case "invalid_request":
|
||||
return 400;
|
||||
case "forbidden":
|
||||
return 403;
|
||||
case "conflict":
|
||||
return 409;
|
||||
case "service_unavailable":
|
||||
return 503;
|
||||
default:
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeBattleReport(bytes: Uint8Array): BattleReport {
|
||||
const fb = FbsBattleReport.getRootAsBattleReport(new ByteBuffer(bytes));
|
||||
const id = uuidStringFromFB(fb.id());
|
||||
if (id === null) {
|
||||
throw new BattleFetchError(500, "battle response missing id");
|
||||
}
|
||||
return {
|
||||
id,
|
||||
planet: Number(fb.planet()),
|
||||
planetName: fb.planetName() ?? "",
|
||||
races: decodeRaces(fb),
|
||||
ships: decodeShips(fb),
|
||||
protocol: decodeProtocol(fb),
|
||||
};
|
||||
}
|
||||
|
||||
function decodeRaces(fb: FbsBattleReport): Record<string, string> {
|
||||
const out: Record<string, string> = {};
|
||||
const total = fb.racesLength();
|
||||
const item = new RaceEntry();
|
||||
for (let i = 0; i < total; i++) {
|
||||
if (!fb.races(i, item)) continue;
|
||||
const valueUUID = item.value();
|
||||
const value = uuidStringFromFB(valueUUID);
|
||||
if (value === null) continue;
|
||||
out[item.key().toString()] = value;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function decodeShips(fb: FbsBattleReport): Record<string, BattleReportGroup> {
|
||||
const out: Record<string, BattleReportGroup> = {};
|
||||
const total = fb.shipsLength();
|
||||
const entry = new ShipEntry();
|
||||
for (let i = 0; i < total; i++) {
|
||||
if (!fb.ships(i, entry)) continue;
|
||||
const group = entry.value();
|
||||
if (group === null) continue;
|
||||
out[entry.key().toString()] = decodeGroup(group);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function decodeGroup(group: FbsBattleReportGroup): BattleReportGroup {
|
||||
const tech: Record<string, number> = {};
|
||||
const techLen = group.techLength();
|
||||
for (let i = 0; i < techLen; i++) {
|
||||
const t = group.tech(i);
|
||||
if (!t) continue;
|
||||
const key = t.key();
|
||||
if (key === null) continue;
|
||||
tech[key] = t.value();
|
||||
}
|
||||
return {
|
||||
race: (group.race() ?? "") as string,
|
||||
className: (group.className() ?? "") as string,
|
||||
tech,
|
||||
num: Number(group.number()),
|
||||
numLeft: Number(group.numberLeft()),
|
||||
loadType: (group.loadType() ?? "") as string,
|
||||
loadQuantity: group.loadQuantity(),
|
||||
inBattle: group.inBattle(),
|
||||
};
|
||||
}
|
||||
|
||||
function decodeProtocol(fb: FbsBattleReport): BattleActionReport[] {
|
||||
const out: BattleActionReport[] = [];
|
||||
const total = fb.protocolLength();
|
||||
const item = new FbsBattleActionReport();
|
||||
for (let i = 0; i < total; i++) {
|
||||
if (!fb.protocol(i, item)) continue;
|
||||
out.push({
|
||||
a: Number(item.attacker()),
|
||||
sa: Number(item.attackerShipClass()),
|
||||
d: Number(item.defender()),
|
||||
sd: Number(item.defenderShipClass()),
|
||||
x: item.destroyed(),
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function uuidStringFromFB(uuid: UUID | null): string | null {
|
||||
if (uuid === null) return null;
|
||||
const hi = uuid.hi();
|
||||
const lo = uuid.lo();
|
||||
const hex = bigUintTo16Hex(hi) + bigUintTo16Hex(lo);
|
||||
return (
|
||||
hex.slice(0, 8) +
|
||||
"-" +
|
||||
hex.slice(8, 12) +
|
||||
"-" +
|
||||
hex.slice(12, 16) +
|
||||
"-" +
|
||||
hex.slice(16, 20) +
|
||||
"-" +
|
||||
hex.slice(20, 32)
|
||||
);
|
||||
}
|
||||
|
||||
function bigUintTo16Hex(value: bigint): string {
|
||||
return value.toString(16).padStart(16, "0");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
// Typed wrappers around `GalaxyClient.executeCommand` for the eight
|
||||
// `user.games.mail.*` Phase 28 ConnectRPC commands. Each wrapper
|
||||
// builds the matching FlatBuffers request, decodes the FlatBuffers
|
||||
// response, and surfaces backend errors through `MailError` so callers
|
||||
// branch on canonical codes (`invalid_request`, `forbidden`,
|
||||
// `not_found`, `conflict`).
|
||||
|
||||
import { Builder, ByteBuffer } from "flatbuffers";
|
||||
|
||||
import type { GalaxyClient } from "./galaxy-client";
|
||||
import { uuidToHiLo } from "./game-state";
|
||||
import {
|
||||
AdminRequest,
|
||||
AdminResponse,
|
||||
BroadcastRequest,
|
||||
BroadcastResponse,
|
||||
DeleteRequest,
|
||||
DeleteResponse,
|
||||
InboxRequest,
|
||||
InboxResponse,
|
||||
MailMessage as FbsMailMessage,
|
||||
MailRecipientState as FbsMailRecipientState,
|
||||
MailBroadcastReceipt as FbsMailBroadcastReceipt,
|
||||
MessageGetRequest,
|
||||
MessageGetResponse,
|
||||
ReadRequest,
|
||||
ReadResponse,
|
||||
SendRequest,
|
||||
SendResponse,
|
||||
SentRequest,
|
||||
SentResponse,
|
||||
} from "../proto/galaxy/fbs/diplomail";
|
||||
import { UUID } from "../proto/galaxy/fbs/common";
|
||||
import { ErrorResponse as FbsErrorResponse } from "../proto/galaxy/fbs/lobby";
|
||||
|
||||
/**
|
||||
* MailError represents a non-`ok` response from a mail RPC. Callers
|
||||
* branch on `code` for canonical error handling and use `message` for
|
||||
* inline UI surfacing.
|
||||
*/
|
||||
export class MailError extends Error {
|
||||
readonly resultCode: string;
|
||||
readonly code: string;
|
||||
|
||||
constructor(resultCode: string, code: string, message: string) {
|
||||
super(message);
|
||||
this.name = "MailError";
|
||||
this.resultCode = resultCode;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MailMessage is the typed UI view of a `MailMessage` FlatBuffers row.
|
||||
* Nullable wire fields (`sender_user_id`, timestamps, translation
|
||||
* slots) become `null` here; the empty string from FB readers is
|
||||
* normalised to either `""` or `null` based on field semantics.
|
||||
*/
|
||||
export interface MailMessage {
|
||||
messageId: string;
|
||||
gameId: string;
|
||||
gameName: string;
|
||||
kind: string;
|
||||
senderKind: string;
|
||||
senderUserId: string | null;
|
||||
senderUsername: string | null;
|
||||
senderRaceName: string | null;
|
||||
subject: string;
|
||||
body: string;
|
||||
bodyLang: string;
|
||||
broadcastScope: string;
|
||||
createdAt: Date;
|
||||
recipientUserId: string;
|
||||
recipientUserName: string;
|
||||
recipientRaceName: string | null;
|
||||
readAt: Date | null;
|
||||
deletedAt: Date | null;
|
||||
translatedSubject: string | null;
|
||||
translatedBody: string | null;
|
||||
translationLang: string | null;
|
||||
translator: string | null;
|
||||
}
|
||||
|
||||
export interface MailRecipientState {
|
||||
messageId: string;
|
||||
readAt: Date | null;
|
||||
deletedAt: Date | null;
|
||||
}
|
||||
|
||||
export interface MailBroadcastReceipt {
|
||||
messageId: string;
|
||||
gameId: string;
|
||||
gameName: string;
|
||||
kind: string;
|
||||
senderKind: string;
|
||||
subject: string;
|
||||
body: string;
|
||||
bodyLang: string;
|
||||
broadcastScope: string;
|
||||
createdAt: Date;
|
||||
recipientCount: number;
|
||||
}
|
||||
|
||||
export interface SendPersonalArgs {
|
||||
gameId: string;
|
||||
raceName: string;
|
||||
subject?: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface SendBroadcastArgs {
|
||||
gameId: string;
|
||||
subject?: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export type AdminTarget = "user" | "all";
|
||||
|
||||
export interface SendAdminArgs {
|
||||
gameId: string;
|
||||
target: AdminTarget;
|
||||
raceName?: string;
|
||||
recipientUserId?: string;
|
||||
recipients?: string;
|
||||
subject?: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
const MESSAGE_TYPE_INBOX = "user.games.mail.inbox";
|
||||
const MESSAGE_TYPE_SENT = "user.games.mail.sent";
|
||||
const MESSAGE_TYPE_GET = "user.games.mail.message.get";
|
||||
const MESSAGE_TYPE_SEND = "user.games.mail.send";
|
||||
const MESSAGE_TYPE_BROADCAST = "user.games.mail.broadcast";
|
||||
const MESSAGE_TYPE_ADMIN = "user.games.mail.admin";
|
||||
const MESSAGE_TYPE_READ = "user.games.mail.read";
|
||||
const MESSAGE_TYPE_DELETE = "user.games.mail.delete";
|
||||
|
||||
const RESULT_CODE_OK = "ok";
|
||||
|
||||
export async function fetchInbox(
|
||||
client: GalaxyClient,
|
||||
gameId: string,
|
||||
): Promise<MailMessage[]> {
|
||||
const builder = new Builder(64);
|
||||
const [hi, lo] = uuidToHiLo(gameId);
|
||||
InboxRequest.startInboxRequest(builder);
|
||||
InboxRequest.addGameId(builder, UUID.createUUID(builder, hi, lo));
|
||||
builder.finish(InboxRequest.endInboxRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_INBOX, builder.asUint8Array());
|
||||
const response = InboxResponse.getRootAsInboxResponse(new ByteBuffer(payload));
|
||||
return readMessageList(response.itemsLength.bind(response), (i) => response.items(i));
|
||||
}
|
||||
|
||||
export async function fetchSent(
|
||||
client: GalaxyClient,
|
||||
gameId: string,
|
||||
): Promise<MailMessage[]> {
|
||||
const builder = new Builder(64);
|
||||
const [hi, lo] = uuidToHiLo(gameId);
|
||||
SentRequest.startSentRequest(builder);
|
||||
SentRequest.addGameId(builder, UUID.createUUID(builder, hi, lo));
|
||||
builder.finish(SentRequest.endSentRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_SENT, builder.asUint8Array());
|
||||
const response = SentResponse.getRootAsSentResponse(new ByteBuffer(payload));
|
||||
return readMessageList(response.itemsLength.bind(response), (i) => response.items(i));
|
||||
}
|
||||
|
||||
export async function fetchMessage(
|
||||
client: GalaxyClient,
|
||||
gameId: string,
|
||||
messageId: string,
|
||||
): Promise<MailMessage> {
|
||||
const builder = new Builder(64);
|
||||
const [ghi, glo] = uuidToHiLo(gameId);
|
||||
const [mhi, mlo] = uuidToHiLo(messageId);
|
||||
MessageGetRequest.startMessageGetRequest(builder);
|
||||
MessageGetRequest.addGameId(builder, UUID.createUUID(builder, ghi, glo));
|
||||
MessageGetRequest.addMessageId(builder, UUID.createUUID(builder, mhi, mlo));
|
||||
builder.finish(MessageGetRequest.endMessageGetRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_GET, builder.asUint8Array());
|
||||
const response = MessageGetResponse.getRootAsMessageGetResponse(new ByteBuffer(payload));
|
||||
const fb = response.message();
|
||||
if (fb === null) {
|
||||
throw new MailError("internal_error", "internal_error", "message missing in response");
|
||||
}
|
||||
return decodeMailMessage(fb);
|
||||
}
|
||||
|
||||
export async function sendPersonal(
|
||||
client: GalaxyClient,
|
||||
input: SendPersonalArgs,
|
||||
): Promise<MailMessage> {
|
||||
const builder = new Builder(256);
|
||||
const [hi, lo] = uuidToHiLo(input.gameId);
|
||||
const raceOff = builder.createString(input.raceName);
|
||||
const subjectOff = builder.createString(input.subject ?? "");
|
||||
const bodyOff = builder.createString(input.body);
|
||||
SendRequest.startSendRequest(builder);
|
||||
SendRequest.addGameId(builder, UUID.createUUID(builder, hi, lo));
|
||||
SendRequest.addRecipientRaceName(builder, raceOff);
|
||||
SendRequest.addSubject(builder, subjectOff);
|
||||
SendRequest.addBody(builder, bodyOff);
|
||||
builder.finish(SendRequest.endSendRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_SEND, builder.asUint8Array());
|
||||
const response = SendResponse.getRootAsSendResponse(new ByteBuffer(payload));
|
||||
const fb = response.message();
|
||||
if (fb === null) {
|
||||
throw new MailError("internal_error", "internal_error", "message missing in response");
|
||||
}
|
||||
return decodeMailMessage(fb);
|
||||
}
|
||||
|
||||
export async function sendBroadcast(
|
||||
client: GalaxyClient,
|
||||
input: SendBroadcastArgs,
|
||||
): Promise<MailBroadcastReceipt> {
|
||||
const builder = new Builder(256);
|
||||
const [hi, lo] = uuidToHiLo(input.gameId);
|
||||
const subjectOff = builder.createString(input.subject ?? "");
|
||||
const bodyOff = builder.createString(input.body);
|
||||
BroadcastRequest.startBroadcastRequest(builder);
|
||||
BroadcastRequest.addGameId(builder, UUID.createUUID(builder, hi, lo));
|
||||
BroadcastRequest.addSubject(builder, subjectOff);
|
||||
BroadcastRequest.addBody(builder, bodyOff);
|
||||
builder.finish(BroadcastRequest.endBroadcastRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_BROADCAST, builder.asUint8Array());
|
||||
const response = BroadcastResponse.getRootAsBroadcastResponse(new ByteBuffer(payload));
|
||||
const fb = response.receipt();
|
||||
if (fb === null) {
|
||||
throw new MailError("internal_error", "internal_error", "receipt missing in response");
|
||||
}
|
||||
return decodeMailBroadcastReceipt(fb);
|
||||
}
|
||||
|
||||
export async function sendAdmin(
|
||||
client: GalaxyClient,
|
||||
input: SendAdminArgs,
|
||||
): Promise<MailMessage | MailBroadcastReceipt> {
|
||||
const builder = new Builder(256);
|
||||
const [hi, lo] = uuidToHiLo(input.gameId);
|
||||
const targetOff = builder.createString(input.target);
|
||||
const recipientUserOff = builder.createString(input.recipientUserId ?? "");
|
||||
const recipientRaceOff = builder.createString(input.raceName ?? "");
|
||||
const recipientsOff = builder.createString(input.recipients ?? "");
|
||||
const subjectOff = builder.createString(input.subject ?? "");
|
||||
const bodyOff = builder.createString(input.body);
|
||||
AdminRequest.startAdminRequest(builder);
|
||||
AdminRequest.addGameId(builder, UUID.createUUID(builder, hi, lo));
|
||||
AdminRequest.addTarget(builder, targetOff);
|
||||
AdminRequest.addRecipientUserId(builder, recipientUserOff);
|
||||
AdminRequest.addRecipientRaceName(builder, recipientRaceOff);
|
||||
AdminRequest.addRecipients(builder, recipientsOff);
|
||||
AdminRequest.addSubject(builder, subjectOff);
|
||||
AdminRequest.addBody(builder, bodyOff);
|
||||
builder.finish(AdminRequest.endAdminRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_ADMIN, builder.asUint8Array());
|
||||
const response = AdminResponse.getRootAsAdminResponse(new ByteBuffer(payload));
|
||||
const receipt = response.receipt();
|
||||
if (receipt !== null) {
|
||||
return decodeMailBroadcastReceipt(receipt);
|
||||
}
|
||||
const message = response.message();
|
||||
if (message !== null) {
|
||||
return decodeMailMessage(message);
|
||||
}
|
||||
throw new MailError("internal_error", "internal_error", "admin response carried neither message nor receipt");
|
||||
}
|
||||
|
||||
export async function markRead(
|
||||
client: GalaxyClient,
|
||||
gameId: string,
|
||||
messageId: string,
|
||||
): Promise<MailRecipientState> {
|
||||
const builder = new Builder(64);
|
||||
const [ghi, glo] = uuidToHiLo(gameId);
|
||||
const [mhi, mlo] = uuidToHiLo(messageId);
|
||||
ReadRequest.startReadRequest(builder);
|
||||
ReadRequest.addGameId(builder, UUID.createUUID(builder, ghi, glo));
|
||||
ReadRequest.addMessageId(builder, UUID.createUUID(builder, mhi, mlo));
|
||||
builder.finish(ReadRequest.endReadRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_READ, builder.asUint8Array());
|
||||
const response = ReadResponse.getRootAsReadResponse(new ByteBuffer(payload));
|
||||
const fb = response.state();
|
||||
if (fb === null) {
|
||||
throw new MailError("internal_error", "internal_error", "state missing in response");
|
||||
}
|
||||
return decodeMailRecipientState(fb);
|
||||
}
|
||||
|
||||
export async function deleteMessage(
|
||||
client: GalaxyClient,
|
||||
gameId: string,
|
||||
messageId: string,
|
||||
): Promise<MailRecipientState> {
|
||||
const builder = new Builder(64);
|
||||
const [ghi, glo] = uuidToHiLo(gameId);
|
||||
const [mhi, mlo] = uuidToHiLo(messageId);
|
||||
DeleteRequest.startDeleteRequest(builder);
|
||||
DeleteRequest.addGameId(builder, UUID.createUUID(builder, ghi, glo));
|
||||
DeleteRequest.addMessageId(builder, UUID.createUUID(builder, mhi, mlo));
|
||||
builder.finish(DeleteRequest.endDeleteRequest(builder));
|
||||
const payload = await execute(client, MESSAGE_TYPE_DELETE, builder.asUint8Array());
|
||||
const response = DeleteResponse.getRootAsDeleteResponse(new ByteBuffer(payload));
|
||||
const fb = response.state();
|
||||
if (fb === null) {
|
||||
throw new MailError("internal_error", "internal_error", "state missing in response");
|
||||
}
|
||||
return decodeMailRecipientState(fb);
|
||||
}
|
||||
|
||||
async function execute(
|
||||
client: GalaxyClient,
|
||||
messageType: string,
|
||||
payloadBytes: Uint8Array,
|
||||
): Promise<Uint8Array> {
|
||||
const result = await client.executeCommand(messageType, payloadBytes);
|
||||
if (result.resultCode !== RESULT_CODE_OK) {
|
||||
throw decodeMailError(result.resultCode, result.payloadBytes);
|
||||
}
|
||||
return result.payloadBytes;
|
||||
}
|
||||
|
||||
function decodeMailError(resultCode: string, payload: Uint8Array): MailError {
|
||||
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
|
||||
}
|
||||
return new MailError(resultCode, code, message);
|
||||
}
|
||||
|
||||
function readMessageList(
|
||||
lengthFn: () => number,
|
||||
getFn: (i: number) => FbsMailMessage | null,
|
||||
): MailMessage[] {
|
||||
const total = lengthFn();
|
||||
const out: MailMessage[] = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
const item = getFn(i);
|
||||
if (item) {
|
||||
out.push(decodeMailMessage(item));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function decodeMailMessage(fb: FbsMailMessage): MailMessage {
|
||||
return {
|
||||
messageId: fb.messageId() ?? "",
|
||||
gameId: fb.gameId() ?? "",
|
||||
gameName: fb.gameName() ?? "",
|
||||
kind: fb.kind() ?? "",
|
||||
senderKind: fb.senderKind() ?? "",
|
||||
senderUserId: optionalString(fb.senderUserId()),
|
||||
senderUsername: optionalString(fb.senderUsername()),
|
||||
senderRaceName: optionalString(fb.senderRaceName()),
|
||||
subject: fb.subject() ?? "",
|
||||
body: fb.body() ?? "",
|
||||
bodyLang: fb.bodyLang() ?? "",
|
||||
broadcastScope: fb.broadcastScope() ?? "",
|
||||
createdAt: dateFromMs(fb.createdAtMs()),
|
||||
recipientUserId: fb.recipientUserId() ?? "",
|
||||
recipientUserName: fb.recipientUserName() ?? "",
|
||||
recipientRaceName: optionalString(fb.recipientRaceName()),
|
||||
readAt: optionalDateFromMs(fb.readAtMs()),
|
||||
deletedAt: optionalDateFromMs(fb.deletedAtMs()),
|
||||
translatedSubject: optionalString(fb.translatedSubject()),
|
||||
translatedBody: optionalString(fb.translatedBody()),
|
||||
translationLang: optionalString(fb.translationLang()),
|
||||
translator: optionalString(fb.translator()),
|
||||
};
|
||||
}
|
||||
|
||||
function decodeMailRecipientState(fb: FbsMailRecipientState): MailRecipientState {
|
||||
return {
|
||||
messageId: fb.messageId() ?? "",
|
||||
readAt: optionalDateFromMs(fb.readAtMs()),
|
||||
deletedAt: optionalDateFromMs(fb.deletedAtMs()),
|
||||
};
|
||||
}
|
||||
|
||||
function decodeMailBroadcastReceipt(fb: FbsMailBroadcastReceipt): MailBroadcastReceipt {
|
||||
return {
|
||||
messageId: fb.messageId() ?? "",
|
||||
gameId: fb.gameId() ?? "",
|
||||
gameName: fb.gameName() ?? "",
|
||||
kind: fb.kind() ?? "",
|
||||
senderKind: fb.senderKind() ?? "",
|
||||
subject: fb.subject() ?? "",
|
||||
body: fb.body() ?? "",
|
||||
bodyLang: fb.bodyLang() ?? "",
|
||||
broadcastScope: fb.broadcastScope() ?? "",
|
||||
createdAt: dateFromMs(fb.createdAtMs()),
|
||||
recipientCount: fb.recipientCount(),
|
||||
};
|
||||
}
|
||||
|
||||
function optionalString(value: string | null | undefined): string | null {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -25,6 +25,10 @@ viewer keeps its prop-driven contract.
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
type RenderedReportSource,
|
||||
} from "$lib/rendered-report.svelte";
|
||||
import {
|
||||
GALAXY_CLIENT_CONTEXT_KEY,
|
||||
type GalaxyClientHandle,
|
||||
} from "$lib/galaxy-client-context.svelte";
|
||||
import {
|
||||
MapShipClassLookup,
|
||||
type ShipClassLookup,
|
||||
@@ -46,6 +50,9 @@ viewer keeps its prop-driven contract.
|
||||
const rendered = getContext<RenderedReportSource | undefined>(
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
);
|
||||
const galaxyClient = getContext<GalaxyClientHandle | undefined>(
|
||||
GALAXY_CLIENT_CONTEXT_KEY,
|
||||
);
|
||||
|
||||
const shipClassLookup = $derived.by<ShipClassLookup>(() => {
|
||||
const map = new Map<string, ShipClassRef>();
|
||||
@@ -85,8 +92,16 @@ viewer keeps its prop-driven contract.
|
||||
state = { kind: "not_found" };
|
||||
return;
|
||||
}
|
||||
const client = galaxyClient?.client ?? null;
|
||||
if (!client) {
|
||||
// Layout populates the client after the boot Promise.all
|
||||
// resolves; stay in `loading` so the effect re-runs once
|
||||
// the handle becomes non-null.
|
||||
state = { kind: "loading" };
|
||||
return;
|
||||
}
|
||||
state = { kind: "loading" };
|
||||
fetchBattle(gameId, turn, battleId)
|
||||
fetchBattle(client, gameId, turn, battleId)
|
||||
.then((report) => {
|
||||
state = { kind: "ready", report };
|
||||
})
|
||||
|
||||
@@ -1,27 +1,207 @@
|
||||
<!--
|
||||
Phase 10 stub for the diplomatic-mail active view. Phase 28 wires the
|
||||
real mail listing.
|
||||
Phase 28 active-view for the diplomatic mail. Replaces the Phase 10
|
||||
stub. Renders a two-pane list/detail layout on desktop and a
|
||||
one-pane stack on mobile; the inner pieces (thread list, thread
|
||||
pane, system-item pane, compose form) live under
|
||||
`./mail/*.svelte`.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import { mailStore, type MailListEntry } from "$lib/mail-store.svelte";
|
||||
|
||||
import ThreadList from "./mail/thread-list.svelte";
|
||||
import ThreadPane from "./mail/thread-pane.svelte";
|
||||
import SystemItemPane from "./mail/system-item-pane.svelte";
|
||||
import Compose from "./mail/compose.svelte";
|
||||
|
||||
let selectedKey = $state<string | null>(null);
|
||||
let composeOpen = $state(false);
|
||||
|
||||
const gameId = $derived(page.params.id ?? "");
|
||||
|
||||
const entries = $derived(mailStore.entries);
|
||||
|
||||
const selected = $derived.by<MailListEntry | null>(() => {
|
||||
if (selectedKey === null) {
|
||||
return null;
|
||||
}
|
||||
return entries.find((entry) => entryKey(entry) === selectedKey) ?? null;
|
||||
});
|
||||
|
||||
function entryKey(entry: MailListEntry): string {
|
||||
return entry.kind === "thread"
|
||||
? `thread:${entry.raceName}`
|
||||
: `standalone:${entry.message.messageId}`;
|
||||
}
|
||||
|
||||
function openEntry(entry: MailListEntry): void {
|
||||
selectedKey = entryKey(entry);
|
||||
}
|
||||
|
||||
function closePane(): void {
|
||||
selectedKey = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="active-view" data-testid="active-view-mail">
|
||||
<h2>{i18n.t("game.view.mail")}</h2>
|
||||
<p>{i18n.t("game.shell.coming_soon")}</p>
|
||||
<section class="mail" data-testid="active-view-mail">
|
||||
<header class="mail-header">
|
||||
<h2>{i18n.t("game.view.mail")}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="compose-btn"
|
||||
data-testid="mail-compose-open"
|
||||
onclick={() => (composeOpen = true)}
|
||||
disabled={mailStore.status !== "ready"}
|
||||
>
|
||||
{i18n.t("game.mail.compose_action")}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if mailStore.status === "loading"}
|
||||
<p class="status" data-testid="mail-loading">
|
||||
{i18n.t("game.mail.loading")}
|
||||
</p>
|
||||
{:else if mailStore.status === "error"}
|
||||
<p class="status error" data-testid="mail-error">
|
||||
{mailStore.error ?? i18n.t("game.mail.load_failed")}
|
||||
</p>
|
||||
{:else if entries.length === 0}
|
||||
<p class="status" data-testid="mail-empty">
|
||||
{i18n.t("game.mail.empty")}
|
||||
</p>
|
||||
{:else}
|
||||
<div class="panes" class:detail-open={selected !== null}>
|
||||
<div class="list-pane">
|
||||
<ThreadList
|
||||
{entries}
|
||||
selectedKey={selectedKey}
|
||||
onSelect={openEntry}
|
||||
/>
|
||||
</div>
|
||||
<div class="detail-pane">
|
||||
<button
|
||||
type="button"
|
||||
class="back-btn"
|
||||
data-testid="mail-back"
|
||||
onclick={closePane}
|
||||
>
|
||||
{i18n.t("game.mail.back")}
|
||||
</button>
|
||||
{#if selected === null}
|
||||
<p class="status empty-detail">
|
||||
{i18n.t("game.mail.select_thread")}
|
||||
</p>
|
||||
{:else if selected.kind === "thread"}
|
||||
<ThreadPane thread={selected} {gameId} />
|
||||
{:else}
|
||||
<SystemItemPane entry={selected} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if composeOpen}
|
||||
<Compose
|
||||
onClose={() => (composeOpen = false)}
|
||||
onSent={(raceName: string | null) => {
|
||||
composeOpen = false;
|
||||
if (raceName !== null) {
|
||||
selectedKey = `thread:${raceName}`;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.active-view {
|
||||
padding: 1.5rem;
|
||||
.mail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
.active-view h2 {
|
||||
margin: 0 0 0.5rem;
|
||||
.mail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.mail-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.active-view p {
|
||||
margin: 0;
|
||||
color: #555;
|
||||
.compose-btn {
|
||||
font: inherit;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid #444;
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.compose-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.status {
|
||||
color: #888;
|
||||
}
|
||||
.status.error {
|
||||
color: #c62828;
|
||||
}
|
||||
.panes {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 280px) 1fr;
|
||||
gap: 1rem;
|
||||
min-height: 320px;
|
||||
}
|
||||
.list-pane,
|
||||
.detail-pane {
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
background: #111;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list-pane {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.back-btn {
|
||||
display: none;
|
||||
font: inherit;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #444;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.empty-detail {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.panes {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.list-pane {
|
||||
display: block;
|
||||
}
|
||||
.detail-pane {
|
||||
display: none;
|
||||
}
|
||||
.panes.detail-open .list-pane {
|
||||
display: none;
|
||||
}
|
||||
.panes.detail-open .detail-pane {
|
||||
display: block;
|
||||
}
|
||||
.panes.detail-open .back-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
<!--
|
||||
Phase 28 — compose dialog for diplomatic mail. The recipient picker
|
||||
reads `gameState.report.races[]` (Phase 22); the kind toggle exposes
|
||||
personal / broadcast. The admin compose path lives in the
|
||||
server-side admin tooling, not in the in-game UI, so it is not
|
||||
surfaced here. Broadcast sends are gated server-side; the UI
|
||||
surfaces the resulting 403 inline.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import { mailStore } from "$lib/mail-store.svelte";
|
||||
import {
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
type RenderedReportSource,
|
||||
} from "$lib/rendered-report.svelte";
|
||||
|
||||
type ComposeKind = "personal" | "broadcast";
|
||||
|
||||
let {
|
||||
onClose,
|
||||
onSent,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onSent: (raceName: string | null) => void;
|
||||
} = $props();
|
||||
|
||||
const rendered = getContext<RenderedReportSource | undefined>(
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
);
|
||||
|
||||
const races = $derived.by<string[]>(() => {
|
||||
const r = rendered?.report;
|
||||
if (!r) {
|
||||
return [];
|
||||
}
|
||||
return r.races.map((race) => race.name);
|
||||
});
|
||||
|
||||
let kind = $state<ComposeKind>("personal");
|
||||
let raceName = $state("");
|
||||
let subject = $state("");
|
||||
let body = $state("");
|
||||
let error = $state<string | null>(null);
|
||||
let sending = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (raceName === "" && races.length > 0) {
|
||||
raceName = races[0];
|
||||
}
|
||||
});
|
||||
|
||||
async function submit(event: SubmitEvent): Promise<void> {
|
||||
event.preventDefault();
|
||||
error = null;
|
||||
const bodyText = body.trim();
|
||||
if (bodyText === "") {
|
||||
error = i18n.t("game.mail.body_required");
|
||||
return;
|
||||
}
|
||||
if (kind === "personal" && raceName === "") {
|
||||
error = i18n.t("game.mail.recipient_required");
|
||||
return;
|
||||
}
|
||||
sending = true;
|
||||
try {
|
||||
if (kind === "personal") {
|
||||
await mailStore.composePersonal({
|
||||
raceName,
|
||||
subject,
|
||||
body: bodyText,
|
||||
});
|
||||
onSent(raceName);
|
||||
return;
|
||||
}
|
||||
await mailStore.composeBroadcast({ subject, body: bodyText });
|
||||
onSent(null);
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
sending = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="overlay" data-testid="mail-compose">
|
||||
<form class="dialog" onsubmit={submit}>
|
||||
<header>
|
||||
<h3>{i18n.t("game.mail.compose_action")}</h3>
|
||||
<button type="button" class="close" onclick={onClose}>×</button>
|
||||
</header>
|
||||
|
||||
<label>
|
||||
{i18n.t("game.mail.compose.target_label")}
|
||||
<select bind:value={kind} data-testid="mail-compose-kind">
|
||||
<option value="personal">{i18n.t("game.mail.compose.target_personal")}</option>
|
||||
<option value="broadcast">{i18n.t("game.mail.compose.target_broadcast")}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{#if kind === "personal"}
|
||||
<label>
|
||||
{i18n.t("game.mail.recipient_label")}
|
||||
<select bind:value={raceName} data-testid="mail-compose-recipient">
|
||||
{#each races as race (race)}
|
||||
<option value={race}>{race}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<label>
|
||||
<span class="visually-hidden">{i18n.t("game.mail.subject_placeholder")}</span>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={subject}
|
||||
placeholder={i18n.t("game.mail.subject_placeholder")}
|
||||
data-testid="mail-compose-subject"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span class="visually-hidden">{i18n.t("game.mail.body_placeholder")}</span>
|
||||
<textarea
|
||||
bind:value={body}
|
||||
placeholder={i18n.t("game.mail.body_placeholder")}
|
||||
rows="6"
|
||||
data-testid="mail-compose-body"
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
{#if error}
|
||||
<p class="error" data-testid="mail-compose-error">{error}</p>
|
||||
{/if}
|
||||
|
||||
<footer>
|
||||
<button type="button" onclick={onClose}>
|
||||
{i18n.t("game.mail.compose.cancel")}
|
||||
</button>
|
||||
<button type="submit" disabled={sending} data-testid="mail-compose-send">
|
||||
{i18n.t("game.mail.compose.send")}
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
}
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem 1.25rem;
|
||||
background: #161616;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 8px;
|
||||
min-width: min(420px, 90vw);
|
||||
max-width: min(560px, 95vw);
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.close {
|
||||
font: inherit;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
font-size: 0.85rem;
|
||||
color: #ccc;
|
||||
}
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid #444;
|
||||
background: #111;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
}
|
||||
footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
footer button {
|
||||
font: inherit;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid #444;
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
footer button[type="submit"] {
|
||||
background: #2a4d7d;
|
||||
border-color: #2a4d7d;
|
||||
}
|
||||
footer button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.error {
|
||||
color: #c62828;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
}
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<!--
|
||||
Phase 28 — right-pane for stand-alone messages (system mail, admin
|
||||
notifications, and the caller's own paid-tier broadcasts). The pane
|
||||
is read-only: no reply box, no per-recipient context. Soft-delete is
|
||||
available for incoming rows that the caller has read.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import { mailStore, type MailStandalone } from "$lib/mail-store.svelte";
|
||||
import { systemTitleKey } from "./system-titles";
|
||||
|
||||
let { entry }: { entry: MailStandalone } = $props();
|
||||
|
||||
let showOriginal = $state(false);
|
||||
|
||||
const incoming = $derived(entry.message.recipientUserName !== "" && entry.message.senderKind !== "player");
|
||||
|
||||
onMount(() => {
|
||||
if (incoming && entry.message.readAt === null) {
|
||||
void mailStore.markRead(entry.message.messageId);
|
||||
}
|
||||
});
|
||||
|
||||
const displayBody = $derived(
|
||||
entry.message.translatedBody && !showOriginal
|
||||
? entry.message.translatedBody
|
||||
: entry.message.body,
|
||||
);
|
||||
const displaySubject = $derived(
|
||||
entry.message.translatedSubject && !showOriginal
|
||||
? entry.message.translatedSubject
|
||||
: entry.message.subject,
|
||||
);
|
||||
|
||||
const headerKey = $derived.by(() => {
|
||||
const m = entry.message;
|
||||
if (m.senderKind === "system") {
|
||||
return systemTitleKey(m);
|
||||
}
|
||||
if (m.senderKind === "admin") {
|
||||
return "game.mail.admin.title" as const;
|
||||
}
|
||||
return "game.mail.broadcast.title" as const;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="standalone" data-testid="mail-system-item">
|
||||
<h3 class="title">{i18n.t(headerKey)}</h3>
|
||||
{#if displaySubject}
|
||||
<div class="subject">{displaySubject}</div>
|
||||
{/if}
|
||||
<p class="body">{displayBody}</p>
|
||||
{#if entry.message.translatedBody}
|
||||
<button
|
||||
type="button"
|
||||
class="toggle"
|
||||
onclick={() => (showOriginal = !showOriginal)}
|
||||
>
|
||||
{showOriginal ? i18n.t("game.mail.show_translation") : i18n.t("game.mail.show_original")}
|
||||
</button>
|
||||
{/if}
|
||||
{#if incoming}
|
||||
<button
|
||||
type="button"
|
||||
class="delete"
|
||||
onclick={() => mailStore.softDelete(entry.message.messageId)}
|
||||
data-testid="mail-system-delete"
|
||||
>
|
||||
{i18n.t("game.mail.delete_action")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.standalone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
color: #b3a14c;
|
||||
}
|
||||
.subject {
|
||||
font-weight: 700;
|
||||
}
|
||||
.body {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.toggle,
|
||||
.delete {
|
||||
align-self: flex-start;
|
||||
font: inherit;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border: 1px solid #444;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
// Maps a system-mail message (lifecycle hook) to its i18n title key.
|
||||
// Kept as a typed helper so the thread-list and detail panes pick the
|
||||
// same title even when the body templates evolve.
|
||||
|
||||
import type { TranslationKey } from "$lib/i18n/index.svelte";
|
||||
import type { MailMessage } from "../../../api/diplomail";
|
||||
|
||||
const KEYWORDS: Array<{ test: RegExp; key: TranslationKey }> = [
|
||||
{ test: /game[._ ]paused/i, key: "game.mail.system.game_paused.title" },
|
||||
{ test: /game[._ ]cancelled|cancelled/i, key: "game.mail.system.game_cancelled.title" },
|
||||
{ test: /membership[._ ]removed|kicked/i, key: "game.mail.system.membership_removed.title" },
|
||||
{ test: /membership[._ ]blocked|blocked/i, key: "game.mail.system.membership_blocked.title" },
|
||||
];
|
||||
|
||||
/**
|
||||
* systemTitleKey returns the localised title key for a system mail
|
||||
* row. The lobby renders these messages through templated subjects;
|
||||
* the UI matches on the subject to pick a canonical title regardless
|
||||
* of language. Falls back to a generic system-mail title when no
|
||||
* pattern matches.
|
||||
*/
|
||||
export function systemTitleKey(message: MailMessage): TranslationKey {
|
||||
const subject = message.subject ?? "";
|
||||
for (const { test, key } of KEYWORDS) {
|
||||
if (test.test(subject)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return "game.mail.system.generic.title";
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<!--
|
||||
Phase 28 — left-pane list of mail entries. Each entry is either a
|
||||
per-race thread (collapsed to the latest message) or a stand-alone
|
||||
system / admin / outgoing-broadcast item. The list is virtual only
|
||||
inside its scroll container; PixiJS / canvas concerns do not apply
|
||||
here.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import type { MailListEntry } from "$lib/mail-store.svelte";
|
||||
import { systemTitleKey } from "./system-titles";
|
||||
|
||||
let {
|
||||
entries,
|
||||
selectedKey,
|
||||
onSelect,
|
||||
}: {
|
||||
entries: MailListEntry[];
|
||||
selectedKey: string | null;
|
||||
onSelect: (entry: MailListEntry) => void;
|
||||
} = $props();
|
||||
|
||||
function entryKey(entry: MailListEntry): string {
|
||||
return entry.kind === "thread"
|
||||
? `thread:${entry.raceName}`
|
||||
: `standalone:${entry.message.messageId}`;
|
||||
}
|
||||
|
||||
function snippet(entry: MailListEntry): string {
|
||||
if (entry.kind === "thread") {
|
||||
const last = entry.messages[entry.messages.length - 1];
|
||||
return last.subject || last.body;
|
||||
}
|
||||
return entry.message.subject || entry.message.body;
|
||||
}
|
||||
</script>
|
||||
|
||||
<ul class="list" data-testid="mail-thread-list">
|
||||
{#each entries as entry (entryKey(entry))}
|
||||
<li
|
||||
class="row"
|
||||
class:active={selectedKey === entryKey(entry)}
|
||||
class:standalone={entry.kind === "standalone"}
|
||||
class:has-unread={entry.kind === "thread" && entry.unreadCount > 0}
|
||||
data-testid="mail-list-row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
onclick={() => onSelect(entry)}
|
||||
data-thread-key={entryKey(entry)}
|
||||
>
|
||||
<span class="title">
|
||||
{#if entry.kind === "thread"}
|
||||
{entry.raceName}
|
||||
{:else if entry.message.senderKind === "system"}
|
||||
{i18n.t(systemTitleKey(entry.message))}
|
||||
{:else if entry.message.senderKind === "admin"}
|
||||
{i18n.t("game.mail.admin.title")}
|
||||
{:else}
|
||||
{i18n.t("game.mail.broadcast.title")}
|
||||
{/if}
|
||||
</span>
|
||||
{#if entry.kind === "thread" && entry.unreadCount > 0}
|
||||
<span class="badge" data-testid="mail-row-unread">{entry.unreadCount}</span>
|
||||
{/if}
|
||||
<span class="snippet">{snippet(entry)}</span>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.row-btn {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: auto auto;
|
||||
gap: 0.25rem 0.5rem;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font: inherit;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.row.active .row-btn {
|
||||
border-color: #555;
|
||||
background: #1c1c1c;
|
||||
}
|
||||
.row.has-unread .title {
|
||||
font-weight: 700;
|
||||
}
|
||||
.row.standalone .title {
|
||||
color: #b3a14c;
|
||||
}
|
||||
.title {
|
||||
grid-column: 1 / span 1;
|
||||
}
|
||||
.badge {
|
||||
grid-column: 2 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
justify-self: end;
|
||||
min-width: 1.5rem;
|
||||
padding: 0 0.4rem;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
border-radius: 999px;
|
||||
background: #2a4d7d;
|
||||
color: #fff;
|
||||
}
|
||||
.snippet {
|
||||
grid-column: 1 / span 2;
|
||||
color: #999;
|
||||
font-size: 0.85rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,255 @@
|
||||
<!--
|
||||
Phase 28 — right-pane transcript for a single per-race thread.
|
||||
Renders messages oldest → newest, with outgoing messages visually
|
||||
distinct from incoming. Each message body goes through `textContent`
|
||||
(no HTML parsing); the optional translation has a per-message
|
||||
"show original" / "show translation" toggle. A persistent reply box
|
||||
sits at the bottom of the pane.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { i18n } from "$lib/i18n/index.svelte";
|
||||
import { mailStore, type MailThread } from "$lib/mail-store.svelte";
|
||||
import type { MailMessage } from "../../../api/diplomail";
|
||||
|
||||
let {
|
||||
thread,
|
||||
gameId,
|
||||
}: {
|
||||
thread: MailThread;
|
||||
gameId: string;
|
||||
} = $props();
|
||||
|
||||
let replyBody = $state("");
|
||||
let replyError = $state<string | null>(null);
|
||||
let sending = $state(false);
|
||||
const showOriginal = $state<Map<string, boolean>>(new Map());
|
||||
|
||||
// Mark every still-unread incoming message in this thread as read
|
||||
// when the pane mounts. Idempotent on the server; the store
|
||||
// optimistically flips `readAt` so the header badge updates
|
||||
// without waiting for the round-trip.
|
||||
onMount(() => {
|
||||
for (const m of thread.messages) {
|
||||
const incoming = m.senderRaceName === thread.raceName;
|
||||
if (incoming && m.readAt === null) {
|
||||
void mailStore.markRead(m.messageId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function isOutgoing(m: MailMessage): boolean {
|
||||
// Outgoing messages have the local user as sender, which
|
||||
// corresponds to `recipientRaceName === thread.raceName` (the
|
||||
// thread is keyed on the other party's race name).
|
||||
return m.recipientRaceName === thread.raceName;
|
||||
}
|
||||
|
||||
function displayBody(m: MailMessage): string {
|
||||
if (m.translatedBody && !showOriginal.get(m.messageId)) {
|
||||
return m.translatedBody;
|
||||
}
|
||||
return m.body;
|
||||
}
|
||||
|
||||
function displaySubject(m: MailMessage): string {
|
||||
if (m.translatedSubject && !showOriginal.get(m.messageId)) {
|
||||
return m.translatedSubject ?? "";
|
||||
}
|
||||
return m.subject;
|
||||
}
|
||||
|
||||
function toggleTranslation(messageId: string): void {
|
||||
showOriginal.set(messageId, !(showOriginal.get(messageId) ?? false));
|
||||
// Trigger reactivity on the map proxy.
|
||||
showOriginal.size; // eslint-disable-line @typescript-eslint/no-unused-expressions
|
||||
}
|
||||
|
||||
async function submitReply(event: SubmitEvent): Promise<void> {
|
||||
event.preventDefault();
|
||||
replyError = null;
|
||||
const body = replyBody.trim();
|
||||
if (body === "") {
|
||||
replyError = i18n.t("game.mail.body_required");
|
||||
return;
|
||||
}
|
||||
sending = true;
|
||||
try {
|
||||
await mailStore.composePersonal({
|
||||
raceName: thread.raceName,
|
||||
subject: "",
|
||||
body,
|
||||
});
|
||||
replyBody = "";
|
||||
} catch (err) {
|
||||
replyError = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
sending = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
// Force the component to depend on the gameId rune so a game
|
||||
// switch re-mounts the pane (the parent unmounts the old one
|
||||
// implicitly via the entry-key change, but this keeps the
|
||||
// dependency explicit for SSR-disabled hot reloads).
|
||||
void gameId;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="thread" data-testid="mail-thread-pane">
|
||||
<h3 class="title">{thread.raceName}</h3>
|
||||
<ol class="messages">
|
||||
{#each thread.messages as m (m.messageId)}
|
||||
<li class="message" class:outgoing={isOutgoing(m)}>
|
||||
<div class="meta">
|
||||
<span class="from">
|
||||
{#if isOutgoing(m)}
|
||||
{i18n.t("game.mail.outgoing_label")}
|
||||
{:else}
|
||||
{thread.raceName}
|
||||
{/if}
|
||||
</span>
|
||||
<time>{m.createdAt.toISOString().slice(0, 19).replace("T", " ")}</time>
|
||||
</div>
|
||||
{#if displaySubject(m)}
|
||||
<div class="subject">{displaySubject(m)}</div>
|
||||
{/if}
|
||||
<p class="body">{displayBody(m)}</p>
|
||||
{#if m.translatedBody}
|
||||
<button
|
||||
type="button"
|
||||
class="toggle"
|
||||
onclick={() => toggleTranslation(m.messageId)}
|
||||
>
|
||||
{#if showOriginal.get(m.messageId)}
|
||||
{i18n.t("game.mail.show_translation")}
|
||||
{:else}
|
||||
{i18n.t("game.mail.show_original")}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{#if !isOutgoing(m)}
|
||||
<button
|
||||
type="button"
|
||||
class="delete"
|
||||
onclick={() => mailStore.softDelete(m.messageId)}
|
||||
data-testid="mail-delete"
|
||||
>
|
||||
{i18n.t("game.mail.delete_action")}
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
|
||||
<form class="reply" onsubmit={submitReply}>
|
||||
<label for="mail-reply-body">{i18n.t("game.mail.reply_label")}</label>
|
||||
<textarea
|
||||
id="mail-reply-body"
|
||||
bind:value={replyBody}
|
||||
placeholder={i18n.t("game.mail.body_placeholder")}
|
||||
rows="3"
|
||||
data-testid="mail-reply-body"
|
||||
></textarea>
|
||||
{#if replyError}
|
||||
<p class="error" data-testid="mail-reply-error">{replyError}</p>
|
||||
{/if}
|
||||
<button type="submit" disabled={sending} data-testid="mail-reply-send">
|
||||
{i18n.t("game.mail.compose.send")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.thread {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: 100%;
|
||||
}
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.messages {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
overflow-y: auto;
|
||||
max-height: 50vh;
|
||||
}
|
||||
.message {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
background: #1c1c1c;
|
||||
border: 1px solid #2a2a2a;
|
||||
}
|
||||
.message.outgoing {
|
||||
background: #15252e;
|
||||
}
|
||||
.meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.8rem;
|
||||
color: #999;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.subject {
|
||||
font-weight: 700;
|
||||
}
|
||||
.body {
|
||||
margin: 0.25rem 0 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.toggle,
|
||||
.delete {
|
||||
margin-top: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
font: inherit;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border: 1px solid #444;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.reply {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.reply textarea {
|
||||
font: inherit;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #444;
|
||||
background: #111;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
}
|
||||
.reply button {
|
||||
align-self: flex-end;
|
||||
font: inherit;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid #444;
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.reply button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.error {
|
||||
color: #c62828;
|
||||
font-size: 0.85rem;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -15,10 +15,13 @@ polishes microcopy.
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
|
||||
import { mailStore } from "$lib/mail-store.svelte";
|
||||
|
||||
type Props = { gameId: string };
|
||||
let { gameId }: Props = $props();
|
||||
|
||||
const mailUnread = $derived(mailStore.unreadCount);
|
||||
|
||||
let open = $state(false);
|
||||
let rootEl: HTMLDivElement | null = $state(null);
|
||||
|
||||
@@ -122,9 +125,15 @@ polishes microcopy.
|
||||
type="button"
|
||||
role="menuitem"
|
||||
data-testid="view-menu-item-mail"
|
||||
class="with-badge"
|
||||
onclick={() => go(`/games/${gameId}/mail`)}
|
||||
>
|
||||
{i18n.t("game.view.mail")}
|
||||
<span>{i18n.t("game.view.mail")}</span>
|
||||
{#if mailUnread > 0}
|
||||
<span class="badge" data-testid="view-menu-item-mail-badge">
|
||||
{i18n.t("game.view.mail.badge", { count: String(mailUnread) })}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -200,6 +209,21 @@ polishes microcopy.
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.surface > button.with-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.surface > button.with-badge .badge {
|
||||
min-width: 1.5rem;
|
||||
padding: 0 0.4rem;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
border-radius: 999px;
|
||||
background: #2a4d7d;
|
||||
color: #fff;
|
||||
}
|
||||
.surface > button:hover,
|
||||
.surface > details > summary:hover {
|
||||
background: #1c2238;
|
||||
|
||||
@@ -123,6 +123,46 @@ const en = {
|
||||
"game.view.report": "turn report",
|
||||
"game.view.battle": "battle log",
|
||||
"game.view.mail": "diplomatic mail",
|
||||
"game.view.mail.badge": "{count}",
|
||||
"game.events.mail_new.message": "new mail from {from}",
|
||||
"game.events.mail_new.action": "view",
|
||||
"game.mail.loading": "loading mail…",
|
||||
"game.mail.load_failed": "could not load mail",
|
||||
"game.mail.empty": "no diplomatic messages yet",
|
||||
"game.mail.back": "back",
|
||||
"game.mail.compose_action": "compose",
|
||||
"game.mail.select_thread": "pick a thread on the left to read it",
|
||||
"game.mail.broadcast.title": "your broadcast",
|
||||
"game.mail.admin.title": "admin notification",
|
||||
"game.mail.system.generic.title": "system message",
|
||||
"game.mail.system.game_paused.title": "game paused",
|
||||
"game.mail.system.game_cancelled.title": "game cancelled",
|
||||
"game.mail.system.membership_removed.title": "membership removed",
|
||||
"game.mail.system.membership_blocked.title": "membership blocked",
|
||||
"game.mail.subject_placeholder": "subject (optional)",
|
||||
"game.mail.body_placeholder": "your message…",
|
||||
"game.mail.recipient_label": "race",
|
||||
"game.mail.recipient_required": "pick a recipient race",
|
||||
"game.mail.body_required": "the message body cannot be empty",
|
||||
"game.mail.body_too_long": "the body exceeds the {limit} byte limit",
|
||||
"game.mail.subject_too_long": "the subject exceeds the {limit} byte limit",
|
||||
"game.mail.compose.send": "send",
|
||||
"game.mail.compose.cancel": "cancel",
|
||||
"game.mail.compose.target_personal": "personal",
|
||||
"game.mail.compose.target_broadcast": "broadcast",
|
||||
"game.mail.compose.target_admin": "admin",
|
||||
"game.mail.compose.recipients_active": "active members",
|
||||
"game.mail.compose.recipients_active_and_removed": "active + removed",
|
||||
"game.mail.compose.recipients_all_members": "all members",
|
||||
"game.mail.compose.target_label": "kind",
|
||||
"game.mail.compose.recipients_label": "audience",
|
||||
"game.mail.compose.send_failed": "send failed",
|
||||
"game.mail.show_original": "show original",
|
||||
"game.mail.show_translation": "show translation",
|
||||
"game.mail.translation_unavailable": "translation unavailable",
|
||||
"game.mail.reply_label": "reply",
|
||||
"game.mail.delete_action": "delete",
|
||||
"game.mail.outgoing_label": "you",
|
||||
"game.view.designer.ship_class": "ship-class designer",
|
||||
"game.view.designer.science": "science designer",
|
||||
"game.sidebar.tab.calculator": "calculator",
|
||||
|
||||
@@ -124,6 +124,46 @@ const ru: Record<keyof typeof en, string> = {
|
||||
"game.view.report": "отчёт хода",
|
||||
"game.view.battle": "журнал боёв",
|
||||
"game.view.mail": "дипломатическая почта",
|
||||
"game.view.mail.badge": "{count}",
|
||||
"game.events.mail_new.message": "новое письмо от {from}",
|
||||
"game.events.mail_new.action": "открыть",
|
||||
"game.mail.loading": "загрузка почты…",
|
||||
"game.mail.load_failed": "не удалось загрузить почту",
|
||||
"game.mail.empty": "дипломатических сообщений пока нет",
|
||||
"game.mail.back": "назад",
|
||||
"game.mail.compose_action": "написать",
|
||||
"game.mail.select_thread": "выбери ветку слева",
|
||||
"game.mail.broadcast.title": "твоя рассылка",
|
||||
"game.mail.admin.title": "административное уведомление",
|
||||
"game.mail.system.generic.title": "системное сообщение",
|
||||
"game.mail.system.game_paused.title": "игра поставлена на паузу",
|
||||
"game.mail.system.game_cancelled.title": "игра отменена",
|
||||
"game.mail.system.membership_removed.title": "членство удалено",
|
||||
"game.mail.system.membership_blocked.title": "членство заблокировано",
|
||||
"game.mail.subject_placeholder": "тема (необязательно)",
|
||||
"game.mail.body_placeholder": "твоё сообщение…",
|
||||
"game.mail.recipient_label": "раса",
|
||||
"game.mail.recipient_required": "выбери расу-получателя",
|
||||
"game.mail.body_required": "тело сообщения не может быть пустым",
|
||||
"game.mail.body_too_long": "длина тела превышает лимит {limit} байт",
|
||||
"game.mail.subject_too_long": "длина темы превышает лимит {limit} байт",
|
||||
"game.mail.compose.send": "отправить",
|
||||
"game.mail.compose.cancel": "отмена",
|
||||
"game.mail.compose.target_personal": "личное",
|
||||
"game.mail.compose.target_broadcast": "рассылка",
|
||||
"game.mail.compose.target_admin": "админ.",
|
||||
"game.mail.compose.recipients_active": "активным членам",
|
||||
"game.mail.compose.recipients_active_and_removed": "активным + удалённым",
|
||||
"game.mail.compose.recipients_all_members": "всем членам",
|
||||
"game.mail.compose.target_label": "тип",
|
||||
"game.mail.compose.recipients_label": "адресаты",
|
||||
"game.mail.compose.send_failed": "отправка не удалась",
|
||||
"game.mail.show_original": "показать оригинал",
|
||||
"game.mail.show_translation": "показать перевод",
|
||||
"game.mail.translation_unavailable": "перевод недоступен",
|
||||
"game.mail.reply_label": "ответить",
|
||||
"game.mail.delete_action": "удалить",
|
||||
"game.mail.outgoing_label": "ты",
|
||||
"game.view.designer.ship_class": "конструктор класса кораблей",
|
||||
"game.view.designer.science": "редактор наук",
|
||||
"game.sidebar.tab.calculator": "калькулятор",
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
// Phase 28 reactive store for the in-game diplomatic-mail view. Owns
|
||||
// the inbox / sent listings, the per-race threading projection, the
|
||||
// unread badge counter, and the imperative compose / mark-read /
|
||||
// delete actions. The companion API wrappers live in
|
||||
// `src/api/diplomail.ts`; this store coordinates them with the rest
|
||||
// of the in-game shell.
|
||||
|
||||
import type { GalaxyClient } from "../api/galaxy-client";
|
||||
import type { Cache } from "../platform/store/index";
|
||||
import {
|
||||
deleteMessage,
|
||||
fetchInbox,
|
||||
fetchMessage,
|
||||
fetchSent,
|
||||
markRead,
|
||||
sendAdmin,
|
||||
sendBroadcast,
|
||||
sendPersonal,
|
||||
type MailMessage,
|
||||
type SendAdminArgs,
|
||||
type SendBroadcastArgs,
|
||||
type SendPersonalArgs,
|
||||
} from "../api/diplomail";
|
||||
|
||||
/**
|
||||
* MailThread groups personal messages exchanged with a single other
|
||||
* race into one entry. The local player's outgoing messages live
|
||||
* alongside incoming messages from the same race so the UI renders a
|
||||
* chat-style transcript. `unreadCount` counts only incoming messages
|
||||
* with `readAt === null`.
|
||||
*/
|
||||
export interface MailThread {
|
||||
kind: "thread";
|
||||
raceName: string;
|
||||
messages: MailMessage[];
|
||||
unreadCount: number;
|
||||
latestAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* MailStandalone wraps a single message that does not participate in
|
||||
* a race-thread: system mail, admin notifications, and the caller's
|
||||
* own paid-tier broadcasts. The UI renders these as read-only items
|
||||
* in the same list as the per-race threads.
|
||||
*/
|
||||
export interface MailStandalone {
|
||||
kind: "standalone";
|
||||
message: MailMessage;
|
||||
latestAt: Date;
|
||||
}
|
||||
|
||||
export type MailListEntry = MailThread | MailStandalone;
|
||||
|
||||
const CACHE_NAMESPACE = "diplomail";
|
||||
|
||||
/**
|
||||
* MailStore is the reactive surface consumed by the active view, the
|
||||
* header badge, and the push-event handler. One instance per signed-
|
||||
* in session is enough — the rune fields are scoped to the current
|
||||
* game and replaced on every `setGame` call so navigating between
|
||||
* games stays clean.
|
||||
*/
|
||||
export class MailStore {
|
||||
gameId = $state("");
|
||||
status: "idle" | "loading" | "ready" | "error" = $state("idle");
|
||||
error: string | null = $state(null);
|
||||
inbox: MailMessage[] = $state([]);
|
||||
sent: MailMessage[] = $state([]);
|
||||
|
||||
private client: GalaxyClient | null = null;
|
||||
private cache: Cache | null = null;
|
||||
|
||||
/**
|
||||
* entries surfaces the unified list-pane projection: per-race
|
||||
* threads built from incoming + outgoing personal messages plus
|
||||
* stand-alone items for system / admin / own-broadcast rows.
|
||||
* Sorted newest-first by the latest message inside each entry.
|
||||
*/
|
||||
entries: MailListEntry[] = $derived.by(() => buildEntries(this.inbox, this.sent));
|
||||
|
||||
/**
|
||||
* unreadCount drives the header view-menu badge. Counts only
|
||||
* incoming personal / admin / system messages with `readAt === null`.
|
||||
* `read_at` is not surfaced to the user in the UI but still
|
||||
* drives this counter.
|
||||
*/
|
||||
unreadCount = $derived.by(() => this.inbox.reduce((acc, m) => (m.readAt === null ? acc + 1 : acc), 0));
|
||||
|
||||
/**
|
||||
* init configures the dependencies and fires the initial fetch.
|
||||
* Safe to call multiple times — calls after the first one are
|
||||
* routed to `setGame`. `localUserId` is captured so the threading
|
||||
* projection can tell outgoing messages from incoming when the
|
||||
* inbox and sent lists are unified.
|
||||
*/
|
||||
async init(opts: {
|
||||
client: GalaxyClient;
|
||||
cache: Cache;
|
||||
gameId: string;
|
||||
}): Promise<void> {
|
||||
this.client = opts.client;
|
||||
this.cache = opts.cache;
|
||||
await this.setGame(opts.gameId);
|
||||
}
|
||||
|
||||
/**
|
||||
* setGame switches the store to a different game id and refreshes
|
||||
* its inbox / sent state. Idempotent on the same id — the network
|
||||
* fetch fires only when the id actually changed or the previous
|
||||
* load ended in `error`.
|
||||
*/
|
||||
async setGame(gameId: string): Promise<void> {
|
||||
if (this.client === null) {
|
||||
throw new Error("mail-store: setGame called before init");
|
||||
}
|
||||
if (this.gameId === gameId && this.status === "ready") {
|
||||
return;
|
||||
}
|
||||
this.gameId = gameId;
|
||||
this.status = "loading";
|
||||
this.error = null;
|
||||
this.inbox = [];
|
||||
this.sent = [];
|
||||
try {
|
||||
const [inbox, sent] = await Promise.all([
|
||||
fetchInbox(this.client, gameId),
|
||||
fetchSent(this.client, gameId),
|
||||
]);
|
||||
this.inbox = inbox;
|
||||
this.sent = sent;
|
||||
this.status = "ready";
|
||||
await this.rememberLastSeen();
|
||||
} catch (err) {
|
||||
this.status = "error";
|
||||
this.error = errorMessage(err);
|
||||
}
|
||||
}
|
||||
|
||||
/** refresh re-fetches inbox + sent for the active game. */
|
||||
async refresh(): Promise<void> {
|
||||
if (this.gameId === "") {
|
||||
return;
|
||||
}
|
||||
await this.setGame(this.gameId);
|
||||
}
|
||||
|
||||
/**
|
||||
* applyPushEvent reacts to a verified `diplomail.message.received`
|
||||
* push frame by refetching the inbox for the active game. The
|
||||
* payload carries only a preview, so the store hits the server for
|
||||
* the canonical row.
|
||||
*/
|
||||
async applyPushEvent(payloadGameId: string): Promise<void> {
|
||||
if (payloadGameId !== this.gameId || this.client === null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.inbox = await fetchInbox(this.client, this.gameId);
|
||||
await this.rememberLastSeen();
|
||||
} catch (err) {
|
||||
this.error = errorMessage(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* markRead transitions an incoming message to `read`. The local
|
||||
* inbox row is flipped optimistically; on failure the previous
|
||||
* state is restored and the error surfaces via `error`.
|
||||
*/
|
||||
async markRead(messageId: string): Promise<void> {
|
||||
if (this.client === null) {
|
||||
return;
|
||||
}
|
||||
const before = this.inbox;
|
||||
this.inbox = before.map((m) => {
|
||||
if (m.messageId !== messageId) {
|
||||
return m;
|
||||
}
|
||||
return { ...m, readAt: m.readAt ?? new Date() };
|
||||
});
|
||||
try {
|
||||
await markRead(this.client, this.gameId, messageId);
|
||||
} catch (err) {
|
||||
this.inbox = before;
|
||||
this.error = errorMessage(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* softDelete removes a read incoming message from the inbox. The
|
||||
* server enforces "read before delete"; on conflict the row is
|
||||
* restored and the error surfaces.
|
||||
*/
|
||||
async softDelete(messageId: string): Promise<void> {
|
||||
if (this.client === null) {
|
||||
return;
|
||||
}
|
||||
const before = this.inbox;
|
||||
this.inbox = before.filter((m) => m.messageId !== messageId);
|
||||
try {
|
||||
await deleteMessage(this.client, this.gameId, messageId);
|
||||
} catch (err) {
|
||||
this.inbox = before;
|
||||
this.error = errorMessage(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* composePersonal sends a single-recipient personal message,
|
||||
* addressed by race name (resolved server-side). On success the
|
||||
* resulting row is appended to the sent list so the matching
|
||||
* thread surfaces it immediately. Throws on failure so callers
|
||||
* can render inline form errors.
|
||||
*/
|
||||
async composePersonal(input: Omit<SendPersonalArgs, "gameId">): Promise<MailMessage> {
|
||||
if (this.client === null) {
|
||||
throw new Error("mail-store: composePersonal called before init");
|
||||
}
|
||||
const created = await sendPersonal(this.client, { ...input, gameId: this.gameId });
|
||||
this.sent = [created, ...this.sent];
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* composeBroadcast posts a paid-tier player broadcast. The sent
|
||||
* list is refreshed to surface the new entries.
|
||||
*/
|
||||
async composeBroadcast(input: Omit<SendBroadcastArgs, "gameId">): Promise<void> {
|
||||
if (this.client === null) {
|
||||
throw new Error("mail-store: composeBroadcast called before init");
|
||||
}
|
||||
await sendBroadcast(this.client, { ...input, gameId: this.gameId });
|
||||
this.sent = await fetchSent(this.client, this.gameId);
|
||||
}
|
||||
|
||||
/**
|
||||
* composeAdmin posts an owner-only admin notification. Single
|
||||
* sends refresh the sent list; broadcasts also refresh the sent
|
||||
* list (the author does not appear as a recipient and is excluded
|
||||
* from the resulting fan-out).
|
||||
*/
|
||||
async composeAdmin(input: Omit<SendAdminArgs, "gameId">): Promise<void> {
|
||||
if (this.client === null) {
|
||||
throw new Error("mail-store: composeAdmin called before init");
|
||||
}
|
||||
await sendAdmin(this.client, { ...input, gameId: this.gameId });
|
||||
this.sent = await fetchSent(this.client, this.gameId);
|
||||
}
|
||||
|
||||
/**
|
||||
* loadMessage fetches a single message detail (used when the UI
|
||||
* needs the freshest translation status for a specific row).
|
||||
* The returned row is merged into the inbox copy if it lives
|
||||
* there; sent rows are not refreshed here.
|
||||
*/
|
||||
async loadMessage(messageId: string): Promise<MailMessage | null> {
|
||||
if (this.client === null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const fresh = await fetchMessage(this.client, this.gameId, messageId);
|
||||
this.inbox = this.inbox.map((m) => (m.messageId === messageId ? fresh : m));
|
||||
return fresh;
|
||||
} catch (err) {
|
||||
this.error = errorMessage(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async rememberLastSeen(): Promise<void> {
|
||||
if (this.cache === null || this.gameId === "" || this.inbox.length === 0) {
|
||||
return;
|
||||
}
|
||||
const last = this.inbox[0];
|
||||
await this.cache.put(CACHE_NAMESPACE, `${this.gameId}/last-seen`, last.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
function buildEntries(inbox: MailMessage[], sent: MailMessage[]): MailListEntry[] {
|
||||
// Each personal message keyed by another race contributes to a
|
||||
// race thread. Other shapes become stand-alone entries.
|
||||
const threadsByRace = new Map<string, MailThread>();
|
||||
const standalones: MailStandalone[] = [];
|
||||
|
||||
for (const m of inbox) {
|
||||
if (isStandaloneIncoming(m)) {
|
||||
standalones.push({ kind: "standalone", message: m, latestAt: m.createdAt });
|
||||
continue;
|
||||
}
|
||||
const race = m.senderRaceName ?? "";
|
||||
if (race === "") {
|
||||
standalones.push({ kind: "standalone", message: m, latestAt: m.createdAt });
|
||||
continue;
|
||||
}
|
||||
mergeIntoThread(threadsByRace, race, m, /*isIncoming=*/true);
|
||||
}
|
||||
|
||||
for (const m of sent) {
|
||||
if (isStandaloneOutgoing(m)) {
|
||||
standalones.push({ kind: "standalone", message: m, latestAt: m.createdAt });
|
||||
continue;
|
||||
}
|
||||
const race = m.recipientRaceName ?? "";
|
||||
if (race === "") {
|
||||
standalones.push({ kind: "standalone", message: m, latestAt: m.createdAt });
|
||||
continue;
|
||||
}
|
||||
mergeIntoThread(threadsByRace, race, m, /*isIncoming=*/false);
|
||||
}
|
||||
|
||||
// Sort each thread's messages oldest → newest for chat-style
|
||||
// rendering; the entry list itself sorts newest-first by the
|
||||
// most-recent message timestamp.
|
||||
for (const thread of threadsByRace.values()) {
|
||||
thread.messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
||||
const last = thread.messages[thread.messages.length - 1];
|
||||
thread.latestAt = last.createdAt;
|
||||
}
|
||||
|
||||
// Broadcast and admin fan-outs return one row per recipient from
|
||||
// the `/sent` endpoint (so the admin UI sees the materialised
|
||||
// audience). The in-game list pane collapses them by `message_id`
|
||||
// — without this dedupe the {#each} key in `thread-list.svelte`
|
||||
// repeats and Svelte 5 aborts the render with `each_key_duplicate`.
|
||||
const seen = new Set<string>();
|
||||
const dedupedStandalones: MailStandalone[] = [];
|
||||
for (const s of standalones) {
|
||||
if (seen.has(s.message.messageId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(s.message.messageId);
|
||||
dedupedStandalones.push(s);
|
||||
}
|
||||
|
||||
const entries: MailListEntry[] = [
|
||||
...Array.from(threadsByRace.values()),
|
||||
...dedupedStandalones,
|
||||
];
|
||||
entries.sort((a, b) => b.latestAt.getTime() - a.latestAt.getTime());
|
||||
return entries;
|
||||
}
|
||||
|
||||
function mergeIntoThread(
|
||||
threads: Map<string, MailThread>,
|
||||
race: string,
|
||||
message: MailMessage,
|
||||
isIncoming: boolean,
|
||||
): void {
|
||||
let thread = threads.get(race);
|
||||
if (thread === undefined) {
|
||||
thread = {
|
||||
kind: "thread",
|
||||
raceName: race,
|
||||
messages: [],
|
||||
unreadCount: 0,
|
||||
latestAt: message.createdAt,
|
||||
};
|
||||
threads.set(race, thread);
|
||||
}
|
||||
thread.messages.push(message);
|
||||
if (isIncoming && message.readAt === null) {
|
||||
thread.unreadCount += 1;
|
||||
}
|
||||
if (message.createdAt.getTime() > thread.latestAt.getTime()) {
|
||||
thread.latestAt = message.createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
function isStandaloneIncoming(m: MailMessage): boolean {
|
||||
// System / admin notifications never thread by race even when a
|
||||
// snapshot is available — they are one-way operational mail.
|
||||
return m.senderKind !== "player";
|
||||
}
|
||||
|
||||
function isStandaloneOutgoing(m: MailMessage): boolean {
|
||||
// Paid-tier broadcasts that the caller authored target many
|
||||
// recipients; the UI renders them once as a stand-alone item.
|
||||
return m.broadcastScope !== "single";
|
||||
}
|
||||
|
||||
function errorMessage(err: unknown): string {
|
||||
if (err instanceof Error) {
|
||||
return err.message;
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
|
||||
export const mailStore = new MailStore();
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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 { BattleActionReport, BattleActionReportT } from './battle/battle-action-report.js';
|
||||
export { BattleReport, BattleReportT } from './battle/battle-report.js';
|
||||
export { BattleReportGroup, BattleReportGroupT } from './battle/battle-report-group.js';
|
||||
export { GameBattleRequest, GameBattleRequestT } from './battle/game-battle-request.js';
|
||||
export { RaceEntry, RaceEntryT } from './battle/race-entry.js';
|
||||
export { ShipEntry, ShipEntryT } from './battle/ship-entry.js';
|
||||
export { TechEntry, TechEntryT } from './battle/tech-entry.js';
|
||||
export { UUID, UUIDT } from './battle/uuid.js';
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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 BattleActionReport implements flatbuffers.IUnpackableObject<BattleActionReportT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):BattleActionReport {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsBattleActionReport(bb:flatbuffers.ByteBuffer, obj?:BattleActionReport):BattleActionReport {
|
||||
return (obj || new BattleActionReport()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsBattleActionReport(bb:flatbuffers.ByteBuffer, obj?:BattleActionReport):BattleActionReport {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new BattleActionReport()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
attacker():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
attackerShipClass():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
defender():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
defenderShipClass():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 10);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
destroyed():boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 12);
|
||||
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
|
||||
}
|
||||
|
||||
static startBattleActionReport(builder:flatbuffers.Builder) {
|
||||
builder.startObject(5);
|
||||
}
|
||||
|
||||
static addAttacker(builder:flatbuffers.Builder, attacker:bigint) {
|
||||
builder.addFieldInt64(0, attacker, BigInt('0'));
|
||||
}
|
||||
|
||||
static addAttackerShipClass(builder:flatbuffers.Builder, attackerShipClass:bigint) {
|
||||
builder.addFieldInt64(1, attackerShipClass, BigInt('0'));
|
||||
}
|
||||
|
||||
static addDefender(builder:flatbuffers.Builder, defender:bigint) {
|
||||
builder.addFieldInt64(2, defender, BigInt('0'));
|
||||
}
|
||||
|
||||
static addDefenderShipClass(builder:flatbuffers.Builder, defenderShipClass:bigint) {
|
||||
builder.addFieldInt64(3, defenderShipClass, BigInt('0'));
|
||||
}
|
||||
|
||||
static addDestroyed(builder:flatbuffers.Builder, destroyed:boolean) {
|
||||
builder.addFieldInt8(4, +destroyed, +false);
|
||||
}
|
||||
|
||||
static endBattleActionReport(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createBattleActionReport(builder:flatbuffers.Builder, attacker:bigint, attackerShipClass:bigint, defender:bigint, defenderShipClass:bigint, destroyed:boolean):flatbuffers.Offset {
|
||||
BattleActionReport.startBattleActionReport(builder);
|
||||
BattleActionReport.addAttacker(builder, attacker);
|
||||
BattleActionReport.addAttackerShipClass(builder, attackerShipClass);
|
||||
BattleActionReport.addDefender(builder, defender);
|
||||
BattleActionReport.addDefenderShipClass(builder, defenderShipClass);
|
||||
BattleActionReport.addDestroyed(builder, destroyed);
|
||||
return BattleActionReport.endBattleActionReport(builder);
|
||||
}
|
||||
|
||||
unpack(): BattleActionReportT {
|
||||
return new BattleActionReportT(
|
||||
this.attacker(),
|
||||
this.attackerShipClass(),
|
||||
this.defender(),
|
||||
this.defenderShipClass(),
|
||||
this.destroyed()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: BattleActionReportT): void {
|
||||
_o.attacker = this.attacker();
|
||||
_o.attackerShipClass = this.attackerShipClass();
|
||||
_o.defender = this.defender();
|
||||
_o.defenderShipClass = this.defenderShipClass();
|
||||
_o.destroyed = this.destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
export class BattleActionReportT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public attacker: bigint = BigInt('0'),
|
||||
public attackerShipClass: bigint = BigInt('0'),
|
||||
public defender: bigint = BigInt('0'),
|
||||
public defenderShipClass: bigint = BigInt('0'),
|
||||
public destroyed: boolean = false
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return BattleActionReport.createBattleActionReport(builder,
|
||||
this.attacker,
|
||||
this.attackerShipClass,
|
||||
this.defender,
|
||||
this.defenderShipClass,
|
||||
this.destroyed
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// 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 { TechEntry, TechEntryT } from '../battle/tech-entry.js';
|
||||
|
||||
|
||||
export class BattleReportGroup implements flatbuffers.IUnpackableObject<BattleReportGroupT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):BattleReportGroup {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsBattleReportGroup(bb:flatbuffers.ByteBuffer, obj?:BattleReportGroup):BattleReportGroup {
|
||||
return (obj || new BattleReportGroup()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsBattleReportGroup(bb:flatbuffers.ByteBuffer, obj?:BattleReportGroup):BattleReportGroup {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new BattleReportGroup()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
inBattle():boolean {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false;
|
||||
}
|
||||
|
||||
number():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
numberLeft():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
loadQuantity():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 10);
|
||||
return offset ? this.bb!.readFloat32(this.bb_pos + offset) : 0.0;
|
||||
}
|
||||
|
||||
tech(index: number, obj?:TechEntry):TechEntry|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 12);
|
||||
return offset ? (obj || new TechEntry()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
|
||||
}
|
||||
|
||||
techLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 12);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
race():string|null
|
||||
race(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
race(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;
|
||||
}
|
||||
|
||||
className():string|null
|
||||
className(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
className(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;
|
||||
}
|
||||
|
||||
loadType():string|null
|
||||
loadType(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
loadType(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;
|
||||
}
|
||||
|
||||
static startBattleReportGroup(builder:flatbuffers.Builder) {
|
||||
builder.startObject(8);
|
||||
}
|
||||
|
||||
static addInBattle(builder:flatbuffers.Builder, inBattle:boolean) {
|
||||
builder.addFieldInt8(0, +inBattle, +false);
|
||||
}
|
||||
|
||||
static addNumber(builder:flatbuffers.Builder, number:bigint) {
|
||||
builder.addFieldInt64(1, number, BigInt('0'));
|
||||
}
|
||||
|
||||
static addNumberLeft(builder:flatbuffers.Builder, numberLeft:bigint) {
|
||||
builder.addFieldInt64(2, numberLeft, BigInt('0'));
|
||||
}
|
||||
|
||||
static addLoadQuantity(builder:flatbuffers.Builder, loadQuantity:number) {
|
||||
builder.addFieldFloat32(3, loadQuantity, 0.0);
|
||||
}
|
||||
|
||||
static addTech(builder:flatbuffers.Builder, techOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(4, techOffset, 0);
|
||||
}
|
||||
|
||||
static createTechVector(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 startTechVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(4, numElems, 4);
|
||||
}
|
||||
|
||||
static addRace(builder:flatbuffers.Builder, raceOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(5, raceOffset, 0);
|
||||
}
|
||||
|
||||
static addClassName(builder:flatbuffers.Builder, classNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(6, classNameOffset, 0);
|
||||
}
|
||||
|
||||
static addLoadType(builder:flatbuffers.Builder, loadTypeOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(7, loadTypeOffset, 0);
|
||||
}
|
||||
|
||||
static endBattleReportGroup(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createBattleReportGroup(builder:flatbuffers.Builder, inBattle:boolean, number:bigint, numberLeft:bigint, loadQuantity:number, techOffset:flatbuffers.Offset, raceOffset:flatbuffers.Offset, classNameOffset:flatbuffers.Offset, loadTypeOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
BattleReportGroup.startBattleReportGroup(builder);
|
||||
BattleReportGroup.addInBattle(builder, inBattle);
|
||||
BattleReportGroup.addNumber(builder, number);
|
||||
BattleReportGroup.addNumberLeft(builder, numberLeft);
|
||||
BattleReportGroup.addLoadQuantity(builder, loadQuantity);
|
||||
BattleReportGroup.addTech(builder, techOffset);
|
||||
BattleReportGroup.addRace(builder, raceOffset);
|
||||
BattleReportGroup.addClassName(builder, classNameOffset);
|
||||
BattleReportGroup.addLoadType(builder, loadTypeOffset);
|
||||
return BattleReportGroup.endBattleReportGroup(builder);
|
||||
}
|
||||
|
||||
unpack(): BattleReportGroupT {
|
||||
return new BattleReportGroupT(
|
||||
this.inBattle(),
|
||||
this.number(),
|
||||
this.numberLeft(),
|
||||
this.loadQuantity(),
|
||||
this.bb!.createObjList<TechEntry, TechEntryT>(this.tech.bind(this), this.techLength()),
|
||||
this.race(),
|
||||
this.className(),
|
||||
this.loadType()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: BattleReportGroupT): void {
|
||||
_o.inBattle = this.inBattle();
|
||||
_o.number = this.number();
|
||||
_o.numberLeft = this.numberLeft();
|
||||
_o.loadQuantity = this.loadQuantity();
|
||||
_o.tech = this.bb!.createObjList<TechEntry, TechEntryT>(this.tech.bind(this), this.techLength());
|
||||
_o.race = this.race();
|
||||
_o.className = this.className();
|
||||
_o.loadType = this.loadType();
|
||||
}
|
||||
}
|
||||
|
||||
export class BattleReportGroupT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public inBattle: boolean = false,
|
||||
public number: bigint = BigInt('0'),
|
||||
public numberLeft: bigint = BigInt('0'),
|
||||
public loadQuantity: number = 0.0,
|
||||
public tech: (TechEntryT)[] = [],
|
||||
public race: string|Uint8Array|null = null,
|
||||
public className: string|Uint8Array|null = null,
|
||||
public loadType: string|Uint8Array|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const tech = BattleReportGroup.createTechVector(builder, builder.createObjectOffsetList(this.tech));
|
||||
const race = (this.race !== null ? builder.createString(this.race!) : 0);
|
||||
const className = (this.className !== null ? builder.createString(this.className!) : 0);
|
||||
const loadType = (this.loadType !== null ? builder.createString(this.loadType!) : 0);
|
||||
|
||||
return BattleReportGroup.createBattleReportGroup(builder,
|
||||
this.inBattle,
|
||||
this.number,
|
||||
this.numberLeft,
|
||||
this.loadQuantity,
|
||||
tech,
|
||||
race,
|
||||
className,
|
||||
loadType
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// 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 { BattleActionReport, BattleActionReportT } from '../battle/battle-action-report.js';
|
||||
import { RaceEntry, RaceEntryT } from '../battle/race-entry.js';
|
||||
import { ShipEntry, ShipEntryT } from '../battle/ship-entry.js';
|
||||
import { UUID, UUIDT } from '../battle/uuid.js';
|
||||
|
||||
|
||||
export class BattleReport implements flatbuffers.IUnpackableObject<BattleReportT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):BattleReport {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsBattleReport(bb:flatbuffers.ByteBuffer, obj?:BattleReport):BattleReport {
|
||||
return (obj || new BattleReport()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsBattleReport(bb:flatbuffers.ByteBuffer, obj?:BattleReport):BattleReport {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new BattleReport()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
id(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
planet():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
planetName():string|null
|
||||
planetName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
planetName(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;
|
||||
}
|
||||
|
||||
races(index: number, obj?:RaceEntry):RaceEntry|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 10);
|
||||
return offset ? (obj || new RaceEntry()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
|
||||
}
|
||||
|
||||
racesLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 10);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
ships(index: number, obj?:ShipEntry):ShipEntry|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 12);
|
||||
return offset ? (obj || new ShipEntry()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
|
||||
}
|
||||
|
||||
shipsLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 12);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
protocol(index: number, obj?:BattleActionReport):BattleActionReport|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 14);
|
||||
return offset ? (obj || new BattleActionReport()).__init(this.bb!.__indirect(this.bb!.__vector(this.bb_pos + offset) + index * 4), this.bb!) : null;
|
||||
}
|
||||
|
||||
protocolLength():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 14);
|
||||
return offset ? this.bb!.__vector_len(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
static startBattleReport(builder:flatbuffers.Builder) {
|
||||
builder.startObject(6);
|
||||
}
|
||||
|
||||
static addId(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, idOffset, 0);
|
||||
}
|
||||
|
||||
static addPlanet(builder:flatbuffers.Builder, planet:bigint) {
|
||||
builder.addFieldInt64(1, planet, BigInt('0'));
|
||||
}
|
||||
|
||||
static addPlanetName(builder:flatbuffers.Builder, planetNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(2, planetNameOffset, 0);
|
||||
}
|
||||
|
||||
static addRaces(builder:flatbuffers.Builder, racesOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(3, racesOffset, 0);
|
||||
}
|
||||
|
||||
static createRacesVector(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 startRacesVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(4, numElems, 4);
|
||||
}
|
||||
|
||||
static addShips(builder:flatbuffers.Builder, shipsOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(4, shipsOffset, 0);
|
||||
}
|
||||
|
||||
static createShipsVector(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 startShipsVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(4, numElems, 4);
|
||||
}
|
||||
|
||||
static addProtocol(builder:flatbuffers.Builder, protocolOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(5, protocolOffset, 0);
|
||||
}
|
||||
|
||||
static createProtocolVector(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 startProtocolVector(builder:flatbuffers.Builder, numElems:number) {
|
||||
builder.startVector(4, numElems, 4);
|
||||
}
|
||||
|
||||
static endBattleReport(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // id
|
||||
return offset;
|
||||
}
|
||||
|
||||
static finishBattleReportBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) {
|
||||
builder.finish(offset);
|
||||
}
|
||||
|
||||
static finishSizePrefixedBattleReportBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) {
|
||||
builder.finish(offset, undefined, true);
|
||||
}
|
||||
|
||||
static createBattleReport(builder:flatbuffers.Builder, idOffset:flatbuffers.Offset, planet:bigint, planetNameOffset:flatbuffers.Offset, racesOffset:flatbuffers.Offset, shipsOffset:flatbuffers.Offset, protocolOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
BattleReport.startBattleReport(builder);
|
||||
BattleReport.addId(builder, idOffset);
|
||||
BattleReport.addPlanet(builder, planet);
|
||||
BattleReport.addPlanetName(builder, planetNameOffset);
|
||||
BattleReport.addRaces(builder, racesOffset);
|
||||
BattleReport.addShips(builder, shipsOffset);
|
||||
BattleReport.addProtocol(builder, protocolOffset);
|
||||
return BattleReport.endBattleReport(builder);
|
||||
}
|
||||
|
||||
unpack(): BattleReportT {
|
||||
return new BattleReportT(
|
||||
(this.id() !== null ? this.id()!.unpack() : null),
|
||||
this.planet(),
|
||||
this.planetName(),
|
||||
this.bb!.createObjList<RaceEntry, RaceEntryT>(this.races.bind(this), this.racesLength()),
|
||||
this.bb!.createObjList<ShipEntry, ShipEntryT>(this.ships.bind(this), this.shipsLength()),
|
||||
this.bb!.createObjList<BattleActionReport, BattleActionReportT>(this.protocol.bind(this), this.protocolLength())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: BattleReportT): void {
|
||||
_o.id = (this.id() !== null ? this.id()!.unpack() : null);
|
||||
_o.planet = this.planet();
|
||||
_o.planetName = this.planetName();
|
||||
_o.races = this.bb!.createObjList<RaceEntry, RaceEntryT>(this.races.bind(this), this.racesLength());
|
||||
_o.ships = this.bb!.createObjList<ShipEntry, ShipEntryT>(this.ships.bind(this), this.shipsLength());
|
||||
_o.protocol = this.bb!.createObjList<BattleActionReport, BattleActionReportT>(this.protocol.bind(this), this.protocolLength());
|
||||
}
|
||||
}
|
||||
|
||||
export class BattleReportT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public id: UUIDT|null = null,
|
||||
public planet: bigint = BigInt('0'),
|
||||
public planetName: string|Uint8Array|null = null,
|
||||
public races: (RaceEntryT)[] = [],
|
||||
public ships: (ShipEntryT)[] = [],
|
||||
public protocol: (BattleActionReportT)[] = []
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const planetName = (this.planetName !== null ? builder.createString(this.planetName!) : 0);
|
||||
const races = BattleReport.createRacesVector(builder, builder.createObjectOffsetList(this.races));
|
||||
const ships = BattleReport.createShipsVector(builder, builder.createObjectOffsetList(this.ships));
|
||||
const protocol = BattleReport.createProtocolVector(builder, builder.createObjectOffsetList(this.protocol));
|
||||
|
||||
return BattleReport.createBattleReport(builder,
|
||||
(this.id !== null ? this.id!.pack(builder) : 0),
|
||||
this.planet,
|
||||
planetName,
|
||||
races,
|
||||
ships,
|
||||
protocol
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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 { UUID, UUIDT } from '../battle/uuid.js';
|
||||
|
||||
|
||||
export class GameBattleRequest implements flatbuffers.IUnpackableObject<GameBattleRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):GameBattleRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsGameBattleRequest(bb:flatbuffers.ByteBuffer, obj?:GameBattleRequest):GameBattleRequest {
|
||||
return (obj || new GameBattleRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsGameBattleRequest(bb:flatbuffers.ByteBuffer, obj?:GameBattleRequest):GameBattleRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new GameBattleRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
turn():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
battleId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
static startGameBattleRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(3);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addTurn(builder:flatbuffers.Builder, turn:number) {
|
||||
builder.addFieldInt32(1, turn, 0);
|
||||
}
|
||||
|
||||
static addBattleId(builder:flatbuffers.Builder, battleIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(2, battleIdOffset, 0);
|
||||
}
|
||||
|
||||
static endGameBattleRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
builder.requiredField(offset, 8) // battle_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): GameBattleRequestT {
|
||||
return new GameBattleRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
this.turn(),
|
||||
(this.battleId() !== null ? this.battleId()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: GameBattleRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.turn = this.turn();
|
||||
_o.battleId = (this.battleId() !== null ? this.battleId()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class GameBattleRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public turn: number = 0,
|
||||
public battleId: UUIDT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
GameBattleRequest.startGameBattleRequest(builder);
|
||||
GameBattleRequest.addGameId(builder, (this.gameId !== null ? this.gameId!.pack(builder) : 0));
|
||||
GameBattleRequest.addTurn(builder, this.turn);
|
||||
GameBattleRequest.addBattleId(builder, (this.battleId !== null ? this.battleId!.pack(builder) : 0));
|
||||
|
||||
return GameBattleRequest.endGameBattleRequest(builder);
|
||||
}
|
||||
}
|
||||
@@ -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 { UUID, UUIDT } from '../battle/uuid.js';
|
||||
|
||||
|
||||
export class RaceEntry implements flatbuffers.IUnpackableObject<RaceEntryT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):RaceEntry {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsRaceEntry(bb:flatbuffers.ByteBuffer, obj?:RaceEntry):RaceEntry {
|
||||
return (obj || new RaceEntry()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsRaceEntry(bb:flatbuffers.ByteBuffer, obj?:RaceEntry):RaceEntry {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new RaceEntry()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
key():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
value(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
static startRaceEntry(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addKey(builder:flatbuffers.Builder, key:bigint) {
|
||||
builder.addFieldInt64(0, key, BigInt('0'));
|
||||
}
|
||||
|
||||
static addValue(builder:flatbuffers.Builder, valueOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(1, valueOffset, 0);
|
||||
}
|
||||
|
||||
static endRaceEntry(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 6) // value
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): RaceEntryT {
|
||||
return new RaceEntryT(
|
||||
this.key(),
|
||||
(this.value() !== null ? this.value()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: RaceEntryT): void {
|
||||
_o.key = this.key();
|
||||
_o.value = (this.value() !== null ? this.value()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class RaceEntryT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public key: bigint = BigInt('0'),
|
||||
public value: UUIDT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
RaceEntry.startRaceEntry(builder);
|
||||
RaceEntry.addKey(builder, this.key);
|
||||
RaceEntry.addValue(builder, (this.value !== null ? this.value!.pack(builder) : 0));
|
||||
|
||||
return RaceEntry.endRaceEntry(builder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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 { BattleReportGroup, BattleReportGroupT } from '../battle/battle-report-group.js';
|
||||
|
||||
|
||||
export class ShipEntry implements flatbuffers.IUnpackableObject<ShipEntryT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ShipEntry {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsShipEntry(bb:flatbuffers.ByteBuffer, obj?:ShipEntry):ShipEntry {
|
||||
return (obj || new ShipEntry()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsShipEntry(bb:flatbuffers.ByteBuffer, obj?:ShipEntry):ShipEntry {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ShipEntry()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
key():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
value(obj?:BattleReportGroup):BattleReportGroup|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? (obj || new BattleReportGroup()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
static startShipEntry(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addKey(builder:flatbuffers.Builder, key:bigint) {
|
||||
builder.addFieldInt64(0, key, BigInt('0'));
|
||||
}
|
||||
|
||||
static addValue(builder:flatbuffers.Builder, valueOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, valueOffset, 0);
|
||||
}
|
||||
|
||||
static endShipEntry(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): ShipEntryT {
|
||||
return new ShipEntryT(
|
||||
this.key(),
|
||||
(this.value() !== null ? this.value()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ShipEntryT): void {
|
||||
_o.key = this.key();
|
||||
_o.value = (this.value() !== null ? this.value()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShipEntryT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public key: bigint = BigInt('0'),
|
||||
public value: BattleReportGroupT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const value = (this.value !== null ? this.value!.pack(builder) : 0);
|
||||
|
||||
ShipEntry.startShipEntry(builder);
|
||||
ShipEntry.addKey(builder, this.key);
|
||||
ShipEntry.addValue(builder, value);
|
||||
|
||||
return ShipEntry.endShipEntry(builder);
|
||||
}
|
||||
}
|
||||
@@ -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 TechEntry implements flatbuffers.IUnpackableObject<TechEntryT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):TechEntry {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsTechEntry(bb:flatbuffers.ByteBuffer, obj?:TechEntry):TechEntry {
|
||||
return (obj || new TechEntry()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsTechEntry(bb:flatbuffers.ByteBuffer, obj?:TechEntry):TechEntry {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new TechEntry()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
key():string|null
|
||||
key(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
key(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():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readFloat32(this.bb_pos + offset) : 0.0;
|
||||
}
|
||||
|
||||
static startTechEntry(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addKey(builder:flatbuffers.Builder, keyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, keyOffset, 0);
|
||||
}
|
||||
|
||||
static addValue(builder:flatbuffers.Builder, value:number) {
|
||||
builder.addFieldFloat32(1, value, 0.0);
|
||||
}
|
||||
|
||||
static endTechEntry(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createTechEntry(builder:flatbuffers.Builder, keyOffset:flatbuffers.Offset, value:number):flatbuffers.Offset {
|
||||
TechEntry.startTechEntry(builder);
|
||||
TechEntry.addKey(builder, keyOffset);
|
||||
TechEntry.addValue(builder, value);
|
||||
return TechEntry.endTechEntry(builder);
|
||||
}
|
||||
|
||||
unpack(): TechEntryT {
|
||||
return new TechEntryT(
|
||||
this.key(),
|
||||
this.value()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: TechEntryT): void {
|
||||
_o.key = this.key();
|
||||
_o.value = this.value();
|
||||
}
|
||||
}
|
||||
|
||||
export class TechEntryT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public key: string|Uint8Array|null = null,
|
||||
public value: number = 0.0
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const key = (this.key !== null ? builder.createString(this.key!) : 0);
|
||||
|
||||
return TechEntry.createTechEntry(builder,
|
||||
key,
|
||||
this.value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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 UUID implements flatbuffers.IUnpackableObject<UUIDT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):UUID {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
hi():bigint {
|
||||
return this.bb!.readUint64(this.bb_pos);
|
||||
}
|
||||
|
||||
lo():bigint {
|
||||
return this.bb!.readUint64(this.bb_pos + 8);
|
||||
}
|
||||
|
||||
static sizeOf():number {
|
||||
return 16;
|
||||
}
|
||||
|
||||
static createUUID(builder:flatbuffers.Builder, hi: bigint, lo: bigint):flatbuffers.Offset {
|
||||
builder.prep(8, 16);
|
||||
builder.writeInt64(BigInt(lo ?? 0));
|
||||
builder.writeInt64(BigInt(hi ?? 0));
|
||||
return builder.offset();
|
||||
}
|
||||
|
||||
|
||||
unpack(): UUIDT {
|
||||
return new UUIDT(
|
||||
this.hi(),
|
||||
this.lo()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: UUIDT): void {
|
||||
_o.hi = this.hi();
|
||||
_o.lo = this.lo();
|
||||
}
|
||||
}
|
||||
|
||||
export class UUIDT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public hi: bigint = BigInt('0'),
|
||||
public lo: bigint = BigInt('0')
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return UUID.createUUID(builder,
|
||||
this.hi,
|
||||
this.lo
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 { AdminRequest, AdminRequestT } from './diplomail/admin-request.js';
|
||||
export { AdminResponse, AdminResponseT } from './diplomail/admin-response.js';
|
||||
export { BroadcastRequest, BroadcastRequestT } from './diplomail/broadcast-request.js';
|
||||
export { BroadcastResponse, BroadcastResponseT } from './diplomail/broadcast-response.js';
|
||||
export { DeleteRequest, DeleteRequestT } from './diplomail/delete-request.js';
|
||||
export { DeleteResponse, DeleteResponseT } from './diplomail/delete-response.js';
|
||||
export { InboxRequest, InboxRequestT } from './diplomail/inbox-request.js';
|
||||
export { InboxResponse, InboxResponseT } from './diplomail/inbox-response.js';
|
||||
export { MailBroadcastReceipt, MailBroadcastReceiptT } from './diplomail/mail-broadcast-receipt.js';
|
||||
export { MailMessage, MailMessageT } from './diplomail/mail-message.js';
|
||||
export { MailRecipientState, MailRecipientStateT } from './diplomail/mail-recipient-state.js';
|
||||
export { MessageGetRequest, MessageGetRequestT } from './diplomail/message-get-request.js';
|
||||
export { MessageGetResponse, MessageGetResponseT } from './diplomail/message-get-response.js';
|
||||
export { ReadRequest, ReadRequestT } from './diplomail/read-request.js';
|
||||
export { ReadResponse, ReadResponseT } from './diplomail/read-response.js';
|
||||
export { SendRequest, SendRequestT } from './diplomail/send-request.js';
|
||||
export { SendResponse, SendResponseT } from './diplomail/send-response.js';
|
||||
export { SentRequest, SentRequestT } from './diplomail/sent-request.js';
|
||||
export { SentResponse, SentResponseT } from './diplomail/sent-response.js';
|
||||
@@ -0,0 +1,179 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class AdminRequest implements flatbuffers.IUnpackableObject<AdminRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):AdminRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsAdminRequest(bb:flatbuffers.ByteBuffer, obj?:AdminRequest):AdminRequest {
|
||||
return (obj || new AdminRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsAdminRequest(bb:flatbuffers.ByteBuffer, obj?:AdminRequest):AdminRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new AdminRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
target():string|null
|
||||
target(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
target(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;
|
||||
}
|
||||
|
||||
recipientUserId():string|null
|
||||
recipientUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientUserId(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;
|
||||
}
|
||||
|
||||
recipientRaceName():string|null
|
||||
recipientRaceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientRaceName(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;
|
||||
}
|
||||
|
||||
recipients():string|null
|
||||
recipients(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipients(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;
|
||||
}
|
||||
|
||||
subject():string|null
|
||||
subject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
subject(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;
|
||||
}
|
||||
|
||||
body():string|null
|
||||
body(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
body(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;
|
||||
}
|
||||
|
||||
static startAdminRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(7);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addTarget(builder:flatbuffers.Builder, targetOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, targetOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipientUserId(builder:flatbuffers.Builder, recipientUserIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(2, recipientUserIdOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipientRaceName(builder:flatbuffers.Builder, recipientRaceNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(3, recipientRaceNameOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipients(builder:flatbuffers.Builder, recipientsOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(4, recipientsOffset, 0);
|
||||
}
|
||||
|
||||
static addSubject(builder:flatbuffers.Builder, subjectOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(5, subjectOffset, 0);
|
||||
}
|
||||
|
||||
static addBody(builder:flatbuffers.Builder, bodyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(6, bodyOffset, 0);
|
||||
}
|
||||
|
||||
static endAdminRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createAdminRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, targetOffset:flatbuffers.Offset, recipientUserIdOffset:flatbuffers.Offset, recipientRaceNameOffset:flatbuffers.Offset, recipientsOffset:flatbuffers.Offset, subjectOffset:flatbuffers.Offset, bodyOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
AdminRequest.startAdminRequest(builder);
|
||||
AdminRequest.addGameId(builder, gameIdOffset);
|
||||
AdminRequest.addTarget(builder, targetOffset);
|
||||
AdminRequest.addRecipientUserId(builder, recipientUserIdOffset);
|
||||
AdminRequest.addRecipientRaceName(builder, recipientRaceNameOffset);
|
||||
AdminRequest.addRecipients(builder, recipientsOffset);
|
||||
AdminRequest.addSubject(builder, subjectOffset);
|
||||
AdminRequest.addBody(builder, bodyOffset);
|
||||
return AdminRequest.endAdminRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): AdminRequestT {
|
||||
return new AdminRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
this.target(),
|
||||
this.recipientUserId(),
|
||||
this.recipientRaceName(),
|
||||
this.recipients(),
|
||||
this.subject(),
|
||||
this.body()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: AdminRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.target = this.target();
|
||||
_o.recipientUserId = this.recipientUserId();
|
||||
_o.recipientRaceName = this.recipientRaceName();
|
||||
_o.recipients = this.recipients();
|
||||
_o.subject = this.subject();
|
||||
_o.body = this.body();
|
||||
}
|
||||
}
|
||||
|
||||
export class AdminRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public target: string|Uint8Array|null = null,
|
||||
public recipientUserId: string|Uint8Array|null = null,
|
||||
public recipientRaceName: string|Uint8Array|null = null,
|
||||
public recipients: string|Uint8Array|null = null,
|
||||
public subject: string|Uint8Array|null = null,
|
||||
public body: string|Uint8Array|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const target = (this.target !== null ? builder.createString(this.target!) : 0);
|
||||
const recipientUserId = (this.recipientUserId !== null ? builder.createString(this.recipientUserId!) : 0);
|
||||
const recipientRaceName = (this.recipientRaceName !== null ? builder.createString(this.recipientRaceName!) : 0);
|
||||
const recipients = (this.recipients !== null ? builder.createString(this.recipients!) : 0);
|
||||
const subject = (this.subject !== null ? builder.createString(this.subject!) : 0);
|
||||
const body = (this.body !== null ? builder.createString(this.body!) : 0);
|
||||
|
||||
return AdminRequest.createAdminRequest(builder,
|
||||
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
|
||||
target,
|
||||
recipientUserId,
|
||||
recipientRaceName,
|
||||
recipients,
|
||||
subject,
|
||||
body
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
import { MailBroadcastReceipt, MailBroadcastReceiptT } from '../diplomail/mail-broadcast-receipt.js';
|
||||
import { MailMessage, MailMessageT } from '../diplomail/mail-message.js';
|
||||
|
||||
|
||||
export class AdminResponse implements flatbuffers.IUnpackableObject<AdminResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):AdminResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsAdminResponse(bb:flatbuffers.ByteBuffer, obj?:AdminResponse):AdminResponse {
|
||||
return (obj || new AdminResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsAdminResponse(bb:flatbuffers.ByteBuffer, obj?:AdminResponse):AdminResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new AdminResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
message(obj?:MailMessage):MailMessage|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new MailMessage()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
receipt(obj?:MailBroadcastReceipt):MailBroadcastReceipt|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? (obj || new MailBroadcastReceipt()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
static startAdminResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addMessage(builder:flatbuffers.Builder, messageOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, messageOffset, 0);
|
||||
}
|
||||
|
||||
static addReceipt(builder:flatbuffers.Builder, receiptOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, receiptOffset, 0);
|
||||
}
|
||||
|
||||
static endAdminResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): AdminResponseT {
|
||||
return new AdminResponseT(
|
||||
(this.message() !== null ? this.message()!.unpack() : null),
|
||||
(this.receipt() !== null ? this.receipt()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: AdminResponseT): void {
|
||||
_o.message = (this.message() !== null ? this.message()!.unpack() : null);
|
||||
_o.receipt = (this.receipt() !== null ? this.receipt()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class AdminResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public message: MailMessageT|null = null,
|
||||
public receipt: MailBroadcastReceiptT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const message = (this.message !== null ? this.message!.pack(builder) : 0);
|
||||
const receipt = (this.receipt !== null ? this.receipt!.pack(builder) : 0);
|
||||
|
||||
AdminResponse.startAdminResponse(builder);
|
||||
AdminResponse.addMessage(builder, message);
|
||||
AdminResponse.addReceipt(builder, receipt);
|
||||
|
||||
return AdminResponse.endAdminResponse(builder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class BroadcastRequest implements flatbuffers.IUnpackableObject<BroadcastRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):BroadcastRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsBroadcastRequest(bb:flatbuffers.ByteBuffer, obj?:BroadcastRequest):BroadcastRequest {
|
||||
return (obj || new BroadcastRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsBroadcastRequest(bb:flatbuffers.ByteBuffer, obj?:BroadcastRequest):BroadcastRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new BroadcastRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
subject():string|null
|
||||
subject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
subject(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;
|
||||
}
|
||||
|
||||
body():string|null
|
||||
body(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
body(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;
|
||||
}
|
||||
|
||||
static startBroadcastRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(3);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addSubject(builder:flatbuffers.Builder, subjectOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, subjectOffset, 0);
|
||||
}
|
||||
|
||||
static addBody(builder:flatbuffers.Builder, bodyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(2, bodyOffset, 0);
|
||||
}
|
||||
|
||||
static endBroadcastRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createBroadcastRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, subjectOffset:flatbuffers.Offset, bodyOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
BroadcastRequest.startBroadcastRequest(builder);
|
||||
BroadcastRequest.addGameId(builder, gameIdOffset);
|
||||
BroadcastRequest.addSubject(builder, subjectOffset);
|
||||
BroadcastRequest.addBody(builder, bodyOffset);
|
||||
return BroadcastRequest.endBroadcastRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): BroadcastRequestT {
|
||||
return new BroadcastRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
this.subject(),
|
||||
this.body()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: BroadcastRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.subject = this.subject();
|
||||
_o.body = this.body();
|
||||
}
|
||||
}
|
||||
|
||||
export class BroadcastRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public subject: string|Uint8Array|null = null,
|
||||
public body: string|Uint8Array|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const subject = (this.subject !== null ? builder.createString(this.subject!) : 0);
|
||||
const body = (this.body !== null ? builder.createString(this.body!) : 0);
|
||||
|
||||
return BroadcastRequest.createBroadcastRequest(builder,
|
||||
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
|
||||
subject,
|
||||
body
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 { MailBroadcastReceipt, MailBroadcastReceiptT } from '../diplomail/mail-broadcast-receipt.js';
|
||||
|
||||
|
||||
export class BroadcastResponse implements flatbuffers.IUnpackableObject<BroadcastResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):BroadcastResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsBroadcastResponse(bb:flatbuffers.ByteBuffer, obj?:BroadcastResponse):BroadcastResponse {
|
||||
return (obj || new BroadcastResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsBroadcastResponse(bb:flatbuffers.ByteBuffer, obj?:BroadcastResponse):BroadcastResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new BroadcastResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
receipt(obj?:MailBroadcastReceipt):MailBroadcastReceipt|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new MailBroadcastReceipt()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
static startBroadcastResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addReceipt(builder:flatbuffers.Builder, receiptOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, receiptOffset, 0);
|
||||
}
|
||||
|
||||
static endBroadcastResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createBroadcastResponse(builder:flatbuffers.Builder, receiptOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
BroadcastResponse.startBroadcastResponse(builder);
|
||||
BroadcastResponse.addReceipt(builder, receiptOffset);
|
||||
return BroadcastResponse.endBroadcastResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): BroadcastResponseT {
|
||||
return new BroadcastResponseT(
|
||||
(this.receipt() !== null ? this.receipt()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: BroadcastResponseT): void {
|
||||
_o.receipt = (this.receipt() !== null ? this.receipt()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class BroadcastResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public receipt: MailBroadcastReceiptT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const receipt = (this.receipt !== null ? this.receipt!.pack(builder) : 0);
|
||||
|
||||
return BroadcastResponse.createBroadcastResponse(builder,
|
||||
receipt
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class DeleteRequest implements flatbuffers.IUnpackableObject<DeleteRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):DeleteRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsDeleteRequest(bb:flatbuffers.ByteBuffer, obj?:DeleteRequest):DeleteRequest {
|
||||
return (obj || new DeleteRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsDeleteRequest(bb:flatbuffers.ByteBuffer, obj?:DeleteRequest):DeleteRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new DeleteRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
messageId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
static startDeleteRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addMessageId(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(1, messageIdOffset, 0);
|
||||
}
|
||||
|
||||
static endDeleteRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
builder.requiredField(offset, 6) // message_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): DeleteRequestT {
|
||||
return new DeleteRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
(this.messageId() !== null ? this.messageId()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: DeleteRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.messageId = (this.messageId() !== null ? this.messageId()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public messageId: UUIDT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
DeleteRequest.startDeleteRequest(builder);
|
||||
DeleteRequest.addGameId(builder, (this.gameId !== null ? this.gameId!.pack(builder) : 0));
|
||||
DeleteRequest.addMessageId(builder, (this.messageId !== null ? this.messageId!.pack(builder) : 0));
|
||||
|
||||
return DeleteRequest.endDeleteRequest(builder);
|
||||
}
|
||||
}
|
||||
@@ -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 { MailRecipientState, MailRecipientStateT } from '../diplomail/mail-recipient-state.js';
|
||||
|
||||
|
||||
export class DeleteResponse implements flatbuffers.IUnpackableObject<DeleteResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):DeleteResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsDeleteResponse(bb:flatbuffers.ByteBuffer, obj?:DeleteResponse):DeleteResponse {
|
||||
return (obj || new DeleteResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsDeleteResponse(bb:flatbuffers.ByteBuffer, obj?:DeleteResponse):DeleteResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new DeleteResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
state(obj?:MailRecipientState):MailRecipientState|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new MailRecipientState()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
static startDeleteResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addState(builder:flatbuffers.Builder, stateOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, stateOffset, 0);
|
||||
}
|
||||
|
||||
static endDeleteResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createDeleteResponse(builder:flatbuffers.Builder, stateOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
DeleteResponse.startDeleteResponse(builder);
|
||||
DeleteResponse.addState(builder, stateOffset);
|
||||
return DeleteResponse.endDeleteResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): DeleteResponseT {
|
||||
return new DeleteResponseT(
|
||||
(this.state() !== null ? this.state()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: DeleteResponseT): void {
|
||||
_o.state = (this.state() !== null ? this.state()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public state: MailRecipientStateT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const state = (this.state !== null ? this.state!.pack(builder) : 0);
|
||||
|
||||
return DeleteResponse.createDeleteResponse(builder,
|
||||
state
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class InboxRequest implements flatbuffers.IUnpackableObject<InboxRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):InboxRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsInboxRequest(bb:flatbuffers.ByteBuffer, obj?:InboxRequest):InboxRequest {
|
||||
return (obj || new InboxRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsInboxRequest(bb:flatbuffers.ByteBuffer, obj?:InboxRequest):InboxRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new InboxRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
static startInboxRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static endInboxRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createInboxRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
InboxRequest.startInboxRequest(builder);
|
||||
InboxRequest.addGameId(builder, gameIdOffset);
|
||||
return InboxRequest.endInboxRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): InboxRequestT {
|
||||
return new InboxRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: InboxRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class InboxRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
return InboxRequest.createInboxRequest(builder,
|
||||
(this.gameId !== null ? this.gameId!.pack(builder) : 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 { MailMessage, MailMessageT } from '../diplomail/mail-message.js';
|
||||
|
||||
|
||||
export class InboxResponse implements flatbuffers.IUnpackableObject<InboxResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):InboxResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsInboxResponse(bb:flatbuffers.ByteBuffer, obj?:InboxResponse):InboxResponse {
|
||||
return (obj || new InboxResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsInboxResponse(bb:flatbuffers.ByteBuffer, obj?:InboxResponse):InboxResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new InboxResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
items(index: number, obj?:MailMessage):MailMessage|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new MailMessage()).__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 startInboxResponse(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 endInboxResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createInboxResponse(builder:flatbuffers.Builder, itemsOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
InboxResponse.startInboxResponse(builder);
|
||||
InboxResponse.addItems(builder, itemsOffset);
|
||||
return InboxResponse.endInboxResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): InboxResponseT {
|
||||
return new InboxResponseT(
|
||||
this.bb!.createObjList<MailMessage, MailMessageT>(this.items.bind(this), this.itemsLength())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: InboxResponseT): void {
|
||||
_o.items = this.bb!.createObjList<MailMessage, MailMessageT>(this.items.bind(this), this.itemsLength());
|
||||
}
|
||||
}
|
||||
|
||||
export class InboxResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public items: (MailMessageT)[] = []
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const items = InboxResponse.createItemsVector(builder, builder.createObjectOffsetList(this.items));
|
||||
|
||||
return InboxResponse.createInboxResponse(builder,
|
||||
items
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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 MailBroadcastReceipt implements flatbuffers.IUnpackableObject<MailBroadcastReceiptT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):MailBroadcastReceipt {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsMailBroadcastReceipt(bb:flatbuffers.ByteBuffer, obj?:MailBroadcastReceipt):MailBroadcastReceipt {
|
||||
return (obj || new MailBroadcastReceipt()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsMailBroadcastReceipt(bb:flatbuffers.ByteBuffer, obj?:MailBroadcastReceipt):MailBroadcastReceipt {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new MailBroadcastReceipt()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
messageId():string|null
|
||||
messageId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
messageId(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;
|
||||
}
|
||||
|
||||
gameName():string|null
|
||||
gameName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
gameName(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;
|
||||
}
|
||||
|
||||
kind():string|null
|
||||
kind(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
kind(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;
|
||||
}
|
||||
|
||||
senderKind():string|null
|
||||
senderKind(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
senderKind(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;
|
||||
}
|
||||
|
||||
subject():string|null
|
||||
subject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
subject(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;
|
||||
}
|
||||
|
||||
body():string|null
|
||||
body(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
body(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;
|
||||
}
|
||||
|
||||
bodyLang():string|null
|
||||
bodyLang(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
bodyLang(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;
|
||||
}
|
||||
|
||||
broadcastScope():string|null
|
||||
broadcastScope(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
broadcastScope(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;
|
||||
}
|
||||
|
||||
createdAtMs():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 22);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
recipientCount():number {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 24);
|
||||
return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0;
|
||||
}
|
||||
|
||||
static startMailBroadcastReceipt(builder:flatbuffers.Builder) {
|
||||
builder.startObject(11);
|
||||
}
|
||||
|
||||
static addMessageId(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, messageIdOffset, 0);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addGameName(builder:flatbuffers.Builder, gameNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(2, gameNameOffset, 0);
|
||||
}
|
||||
|
||||
static addKind(builder:flatbuffers.Builder, kindOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(3, kindOffset, 0);
|
||||
}
|
||||
|
||||
static addSenderKind(builder:flatbuffers.Builder, senderKindOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(4, senderKindOffset, 0);
|
||||
}
|
||||
|
||||
static addSubject(builder:flatbuffers.Builder, subjectOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(5, subjectOffset, 0);
|
||||
}
|
||||
|
||||
static addBody(builder:flatbuffers.Builder, bodyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(6, bodyOffset, 0);
|
||||
}
|
||||
|
||||
static addBodyLang(builder:flatbuffers.Builder, bodyLangOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(7, bodyLangOffset, 0);
|
||||
}
|
||||
|
||||
static addBroadcastScope(builder:flatbuffers.Builder, broadcastScopeOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(8, broadcastScopeOffset, 0);
|
||||
}
|
||||
|
||||
static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) {
|
||||
builder.addFieldInt64(9, createdAtMs, BigInt('0'));
|
||||
}
|
||||
|
||||
static addRecipientCount(builder:flatbuffers.Builder, recipientCount:number) {
|
||||
builder.addFieldInt32(10, recipientCount, 0);
|
||||
}
|
||||
|
||||
static endMailBroadcastReceipt(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createMailBroadcastReceipt(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset, gameIdOffset:flatbuffers.Offset, gameNameOffset:flatbuffers.Offset, kindOffset:flatbuffers.Offset, senderKindOffset:flatbuffers.Offset, subjectOffset:flatbuffers.Offset, bodyOffset:flatbuffers.Offset, bodyLangOffset:flatbuffers.Offset, broadcastScopeOffset:flatbuffers.Offset, createdAtMs:bigint, recipientCount:number):flatbuffers.Offset {
|
||||
MailBroadcastReceipt.startMailBroadcastReceipt(builder);
|
||||
MailBroadcastReceipt.addMessageId(builder, messageIdOffset);
|
||||
MailBroadcastReceipt.addGameId(builder, gameIdOffset);
|
||||
MailBroadcastReceipt.addGameName(builder, gameNameOffset);
|
||||
MailBroadcastReceipt.addKind(builder, kindOffset);
|
||||
MailBroadcastReceipt.addSenderKind(builder, senderKindOffset);
|
||||
MailBroadcastReceipt.addSubject(builder, subjectOffset);
|
||||
MailBroadcastReceipt.addBody(builder, bodyOffset);
|
||||
MailBroadcastReceipt.addBodyLang(builder, bodyLangOffset);
|
||||
MailBroadcastReceipt.addBroadcastScope(builder, broadcastScopeOffset);
|
||||
MailBroadcastReceipt.addCreatedAtMs(builder, createdAtMs);
|
||||
MailBroadcastReceipt.addRecipientCount(builder, recipientCount);
|
||||
return MailBroadcastReceipt.endMailBroadcastReceipt(builder);
|
||||
}
|
||||
|
||||
unpack(): MailBroadcastReceiptT {
|
||||
return new MailBroadcastReceiptT(
|
||||
this.messageId(),
|
||||
this.gameId(),
|
||||
this.gameName(),
|
||||
this.kind(),
|
||||
this.senderKind(),
|
||||
this.subject(),
|
||||
this.body(),
|
||||
this.bodyLang(),
|
||||
this.broadcastScope(),
|
||||
this.createdAtMs(),
|
||||
this.recipientCount()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: MailBroadcastReceiptT): void {
|
||||
_o.messageId = this.messageId();
|
||||
_o.gameId = this.gameId();
|
||||
_o.gameName = this.gameName();
|
||||
_o.kind = this.kind();
|
||||
_o.senderKind = this.senderKind();
|
||||
_o.subject = this.subject();
|
||||
_o.body = this.body();
|
||||
_o.bodyLang = this.bodyLang();
|
||||
_o.broadcastScope = this.broadcastScope();
|
||||
_o.createdAtMs = this.createdAtMs();
|
||||
_o.recipientCount = this.recipientCount();
|
||||
}
|
||||
}
|
||||
|
||||
export class MailBroadcastReceiptT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public messageId: string|Uint8Array|null = null,
|
||||
public gameId: string|Uint8Array|null = null,
|
||||
public gameName: string|Uint8Array|null = null,
|
||||
public kind: string|Uint8Array|null = null,
|
||||
public senderKind: string|Uint8Array|null = null,
|
||||
public subject: string|Uint8Array|null = null,
|
||||
public body: string|Uint8Array|null = null,
|
||||
public bodyLang: string|Uint8Array|null = null,
|
||||
public broadcastScope: string|Uint8Array|null = null,
|
||||
public createdAtMs: bigint = BigInt('0'),
|
||||
public recipientCount: number = 0
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const messageId = (this.messageId !== null ? builder.createString(this.messageId!) : 0);
|
||||
const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0);
|
||||
const gameName = (this.gameName !== null ? builder.createString(this.gameName!) : 0);
|
||||
const kind = (this.kind !== null ? builder.createString(this.kind!) : 0);
|
||||
const senderKind = (this.senderKind !== null ? builder.createString(this.senderKind!) : 0);
|
||||
const subject = (this.subject !== null ? builder.createString(this.subject!) : 0);
|
||||
const body = (this.body !== null ? builder.createString(this.body!) : 0);
|
||||
const bodyLang = (this.bodyLang !== null ? builder.createString(this.bodyLang!) : 0);
|
||||
const broadcastScope = (this.broadcastScope !== null ? builder.createString(this.broadcastScope!) : 0);
|
||||
|
||||
return MailBroadcastReceipt.createMailBroadcastReceipt(builder,
|
||||
messageId,
|
||||
gameId,
|
||||
gameName,
|
||||
kind,
|
||||
senderKind,
|
||||
subject,
|
||||
body,
|
||||
bodyLang,
|
||||
broadcastScope,
|
||||
this.createdAtMs,
|
||||
this.recipientCount
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
// 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 MailMessage implements flatbuffers.IUnpackableObject<MailMessageT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):MailMessage {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsMailMessage(bb:flatbuffers.ByteBuffer, obj?:MailMessage):MailMessage {
|
||||
return (obj || new MailMessage()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsMailMessage(bb:flatbuffers.ByteBuffer, obj?:MailMessage):MailMessage {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new MailMessage()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
messageId():string|null
|
||||
messageId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
messageId(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;
|
||||
}
|
||||
|
||||
gameName():string|null
|
||||
gameName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
gameName(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;
|
||||
}
|
||||
|
||||
kind():string|null
|
||||
kind(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
kind(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;
|
||||
}
|
||||
|
||||
senderKind():string|null
|
||||
senderKind(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
senderKind(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;
|
||||
}
|
||||
|
||||
senderUserId():string|null
|
||||
senderUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
senderUserId(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;
|
||||
}
|
||||
|
||||
senderUsername():string|null
|
||||
senderUsername(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
senderUsername(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;
|
||||
}
|
||||
|
||||
senderRaceName():string|null
|
||||
senderRaceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
senderRaceName(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;
|
||||
}
|
||||
|
||||
subject():string|null
|
||||
subject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
subject(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;
|
||||
}
|
||||
|
||||
body():string|null
|
||||
body(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
body(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 22);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
bodyLang():string|null
|
||||
bodyLang(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
bodyLang(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 24);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
broadcastScope():string|null
|
||||
broadcastScope(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
broadcastScope(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 26);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
createdAtMs():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 28);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
recipientUserId():string|null
|
||||
recipientUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientUserId(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 30);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
recipientUserName():string|null
|
||||
recipientUserName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientUserName(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 32);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
recipientRaceName():string|null
|
||||
recipientRaceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientRaceName(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 34);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
readAtMs():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 36);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
deletedAtMs():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 38);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
translatedSubject():string|null
|
||||
translatedSubject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
translatedSubject(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 40);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
translatedBody():string|null
|
||||
translatedBody(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
translatedBody(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 42);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
translationLang():string|null
|
||||
translationLang(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
translationLang(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 44);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
translator():string|null
|
||||
translator(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
translator(optionalEncoding?:any):string|Uint8Array|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 46);
|
||||
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
|
||||
}
|
||||
|
||||
static startMailMessage(builder:flatbuffers.Builder) {
|
||||
builder.startObject(22);
|
||||
}
|
||||
|
||||
static addMessageId(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, messageIdOffset, 0);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addGameName(builder:flatbuffers.Builder, gameNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(2, gameNameOffset, 0);
|
||||
}
|
||||
|
||||
static addKind(builder:flatbuffers.Builder, kindOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(3, kindOffset, 0);
|
||||
}
|
||||
|
||||
static addSenderKind(builder:flatbuffers.Builder, senderKindOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(4, senderKindOffset, 0);
|
||||
}
|
||||
|
||||
static addSenderUserId(builder:flatbuffers.Builder, senderUserIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(5, senderUserIdOffset, 0);
|
||||
}
|
||||
|
||||
static addSenderUsername(builder:flatbuffers.Builder, senderUsernameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(6, senderUsernameOffset, 0);
|
||||
}
|
||||
|
||||
static addSenderRaceName(builder:flatbuffers.Builder, senderRaceNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(7, senderRaceNameOffset, 0);
|
||||
}
|
||||
|
||||
static addSubject(builder:flatbuffers.Builder, subjectOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(8, subjectOffset, 0);
|
||||
}
|
||||
|
||||
static addBody(builder:flatbuffers.Builder, bodyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(9, bodyOffset, 0);
|
||||
}
|
||||
|
||||
static addBodyLang(builder:flatbuffers.Builder, bodyLangOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(10, bodyLangOffset, 0);
|
||||
}
|
||||
|
||||
static addBroadcastScope(builder:flatbuffers.Builder, broadcastScopeOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(11, broadcastScopeOffset, 0);
|
||||
}
|
||||
|
||||
static addCreatedAtMs(builder:flatbuffers.Builder, createdAtMs:bigint) {
|
||||
builder.addFieldInt64(12, createdAtMs, BigInt('0'));
|
||||
}
|
||||
|
||||
static addRecipientUserId(builder:flatbuffers.Builder, recipientUserIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(13, recipientUserIdOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipientUserName(builder:flatbuffers.Builder, recipientUserNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(14, recipientUserNameOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipientRaceName(builder:flatbuffers.Builder, recipientRaceNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(15, recipientRaceNameOffset, 0);
|
||||
}
|
||||
|
||||
static addReadAtMs(builder:flatbuffers.Builder, readAtMs:bigint) {
|
||||
builder.addFieldInt64(16, readAtMs, BigInt('0'));
|
||||
}
|
||||
|
||||
static addDeletedAtMs(builder:flatbuffers.Builder, deletedAtMs:bigint) {
|
||||
builder.addFieldInt64(17, deletedAtMs, BigInt('0'));
|
||||
}
|
||||
|
||||
static addTranslatedSubject(builder:flatbuffers.Builder, translatedSubjectOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(18, translatedSubjectOffset, 0);
|
||||
}
|
||||
|
||||
static addTranslatedBody(builder:flatbuffers.Builder, translatedBodyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(19, translatedBodyOffset, 0);
|
||||
}
|
||||
|
||||
static addTranslationLang(builder:flatbuffers.Builder, translationLangOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(20, translationLangOffset, 0);
|
||||
}
|
||||
|
||||
static addTranslator(builder:flatbuffers.Builder, translatorOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(21, translatorOffset, 0);
|
||||
}
|
||||
|
||||
static endMailMessage(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createMailMessage(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset, gameIdOffset:flatbuffers.Offset, gameNameOffset:flatbuffers.Offset, kindOffset:flatbuffers.Offset, senderKindOffset:flatbuffers.Offset, senderUserIdOffset:flatbuffers.Offset, senderUsernameOffset:flatbuffers.Offset, senderRaceNameOffset:flatbuffers.Offset, subjectOffset:flatbuffers.Offset, bodyOffset:flatbuffers.Offset, bodyLangOffset:flatbuffers.Offset, broadcastScopeOffset:flatbuffers.Offset, createdAtMs:bigint, recipientUserIdOffset:flatbuffers.Offset, recipientUserNameOffset:flatbuffers.Offset, recipientRaceNameOffset:flatbuffers.Offset, readAtMs:bigint, deletedAtMs:bigint, translatedSubjectOffset:flatbuffers.Offset, translatedBodyOffset:flatbuffers.Offset, translationLangOffset:flatbuffers.Offset, translatorOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
MailMessage.startMailMessage(builder);
|
||||
MailMessage.addMessageId(builder, messageIdOffset);
|
||||
MailMessage.addGameId(builder, gameIdOffset);
|
||||
MailMessage.addGameName(builder, gameNameOffset);
|
||||
MailMessage.addKind(builder, kindOffset);
|
||||
MailMessage.addSenderKind(builder, senderKindOffset);
|
||||
MailMessage.addSenderUserId(builder, senderUserIdOffset);
|
||||
MailMessage.addSenderUsername(builder, senderUsernameOffset);
|
||||
MailMessage.addSenderRaceName(builder, senderRaceNameOffset);
|
||||
MailMessage.addSubject(builder, subjectOffset);
|
||||
MailMessage.addBody(builder, bodyOffset);
|
||||
MailMessage.addBodyLang(builder, bodyLangOffset);
|
||||
MailMessage.addBroadcastScope(builder, broadcastScopeOffset);
|
||||
MailMessage.addCreatedAtMs(builder, createdAtMs);
|
||||
MailMessage.addRecipientUserId(builder, recipientUserIdOffset);
|
||||
MailMessage.addRecipientUserName(builder, recipientUserNameOffset);
|
||||
MailMessage.addRecipientRaceName(builder, recipientRaceNameOffset);
|
||||
MailMessage.addReadAtMs(builder, readAtMs);
|
||||
MailMessage.addDeletedAtMs(builder, deletedAtMs);
|
||||
MailMessage.addTranslatedSubject(builder, translatedSubjectOffset);
|
||||
MailMessage.addTranslatedBody(builder, translatedBodyOffset);
|
||||
MailMessage.addTranslationLang(builder, translationLangOffset);
|
||||
MailMessage.addTranslator(builder, translatorOffset);
|
||||
return MailMessage.endMailMessage(builder);
|
||||
}
|
||||
|
||||
unpack(): MailMessageT {
|
||||
return new MailMessageT(
|
||||
this.messageId(),
|
||||
this.gameId(),
|
||||
this.gameName(),
|
||||
this.kind(),
|
||||
this.senderKind(),
|
||||
this.senderUserId(),
|
||||
this.senderUsername(),
|
||||
this.senderRaceName(),
|
||||
this.subject(),
|
||||
this.body(),
|
||||
this.bodyLang(),
|
||||
this.broadcastScope(),
|
||||
this.createdAtMs(),
|
||||
this.recipientUserId(),
|
||||
this.recipientUserName(),
|
||||
this.recipientRaceName(),
|
||||
this.readAtMs(),
|
||||
this.deletedAtMs(),
|
||||
this.translatedSubject(),
|
||||
this.translatedBody(),
|
||||
this.translationLang(),
|
||||
this.translator()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: MailMessageT): void {
|
||||
_o.messageId = this.messageId();
|
||||
_o.gameId = this.gameId();
|
||||
_o.gameName = this.gameName();
|
||||
_o.kind = this.kind();
|
||||
_o.senderKind = this.senderKind();
|
||||
_o.senderUserId = this.senderUserId();
|
||||
_o.senderUsername = this.senderUsername();
|
||||
_o.senderRaceName = this.senderRaceName();
|
||||
_o.subject = this.subject();
|
||||
_o.body = this.body();
|
||||
_o.bodyLang = this.bodyLang();
|
||||
_o.broadcastScope = this.broadcastScope();
|
||||
_o.createdAtMs = this.createdAtMs();
|
||||
_o.recipientUserId = this.recipientUserId();
|
||||
_o.recipientUserName = this.recipientUserName();
|
||||
_o.recipientRaceName = this.recipientRaceName();
|
||||
_o.readAtMs = this.readAtMs();
|
||||
_o.deletedAtMs = this.deletedAtMs();
|
||||
_o.translatedSubject = this.translatedSubject();
|
||||
_o.translatedBody = this.translatedBody();
|
||||
_o.translationLang = this.translationLang();
|
||||
_o.translator = this.translator();
|
||||
}
|
||||
}
|
||||
|
||||
export class MailMessageT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public messageId: string|Uint8Array|null = null,
|
||||
public gameId: string|Uint8Array|null = null,
|
||||
public gameName: string|Uint8Array|null = null,
|
||||
public kind: string|Uint8Array|null = null,
|
||||
public senderKind: string|Uint8Array|null = null,
|
||||
public senderUserId: string|Uint8Array|null = null,
|
||||
public senderUsername: string|Uint8Array|null = null,
|
||||
public senderRaceName: string|Uint8Array|null = null,
|
||||
public subject: string|Uint8Array|null = null,
|
||||
public body: string|Uint8Array|null = null,
|
||||
public bodyLang: string|Uint8Array|null = null,
|
||||
public broadcastScope: string|Uint8Array|null = null,
|
||||
public createdAtMs: bigint = BigInt('0'),
|
||||
public recipientUserId: string|Uint8Array|null = null,
|
||||
public recipientUserName: string|Uint8Array|null = null,
|
||||
public recipientRaceName: string|Uint8Array|null = null,
|
||||
public readAtMs: bigint = BigInt('0'),
|
||||
public deletedAtMs: bigint = BigInt('0'),
|
||||
public translatedSubject: string|Uint8Array|null = null,
|
||||
public translatedBody: string|Uint8Array|null = null,
|
||||
public translationLang: string|Uint8Array|null = null,
|
||||
public translator: string|Uint8Array|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const messageId = (this.messageId !== null ? builder.createString(this.messageId!) : 0);
|
||||
const gameId = (this.gameId !== null ? builder.createString(this.gameId!) : 0);
|
||||
const gameName = (this.gameName !== null ? builder.createString(this.gameName!) : 0);
|
||||
const kind = (this.kind !== null ? builder.createString(this.kind!) : 0);
|
||||
const senderKind = (this.senderKind !== null ? builder.createString(this.senderKind!) : 0);
|
||||
const senderUserId = (this.senderUserId !== null ? builder.createString(this.senderUserId!) : 0);
|
||||
const senderUsername = (this.senderUsername !== null ? builder.createString(this.senderUsername!) : 0);
|
||||
const senderRaceName = (this.senderRaceName !== null ? builder.createString(this.senderRaceName!) : 0);
|
||||
const subject = (this.subject !== null ? builder.createString(this.subject!) : 0);
|
||||
const body = (this.body !== null ? builder.createString(this.body!) : 0);
|
||||
const bodyLang = (this.bodyLang !== null ? builder.createString(this.bodyLang!) : 0);
|
||||
const broadcastScope = (this.broadcastScope !== null ? builder.createString(this.broadcastScope!) : 0);
|
||||
const recipientUserId = (this.recipientUserId !== null ? builder.createString(this.recipientUserId!) : 0);
|
||||
const recipientUserName = (this.recipientUserName !== null ? builder.createString(this.recipientUserName!) : 0);
|
||||
const recipientRaceName = (this.recipientRaceName !== null ? builder.createString(this.recipientRaceName!) : 0);
|
||||
const translatedSubject = (this.translatedSubject !== null ? builder.createString(this.translatedSubject!) : 0);
|
||||
const translatedBody = (this.translatedBody !== null ? builder.createString(this.translatedBody!) : 0);
|
||||
const translationLang = (this.translationLang !== null ? builder.createString(this.translationLang!) : 0);
|
||||
const translator = (this.translator !== null ? builder.createString(this.translator!) : 0);
|
||||
|
||||
return MailMessage.createMailMessage(builder,
|
||||
messageId,
|
||||
gameId,
|
||||
gameName,
|
||||
kind,
|
||||
senderKind,
|
||||
senderUserId,
|
||||
senderUsername,
|
||||
senderRaceName,
|
||||
subject,
|
||||
body,
|
||||
bodyLang,
|
||||
broadcastScope,
|
||||
this.createdAtMs,
|
||||
recipientUserId,
|
||||
recipientUserName,
|
||||
recipientRaceName,
|
||||
this.readAtMs,
|
||||
this.deletedAtMs,
|
||||
translatedSubject,
|
||||
translatedBody,
|
||||
translationLang,
|
||||
translator
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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 MailRecipientState implements flatbuffers.IUnpackableObject<MailRecipientStateT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):MailRecipientState {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsMailRecipientState(bb:flatbuffers.ByteBuffer, obj?:MailRecipientState):MailRecipientState {
|
||||
return (obj || new MailRecipientState()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsMailRecipientState(bb:flatbuffers.ByteBuffer, obj?:MailRecipientState):MailRecipientState {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new MailRecipientState()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
messageId():string|null
|
||||
messageId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
messageId(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;
|
||||
}
|
||||
|
||||
readAtMs():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
deletedAtMs():bigint {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 8);
|
||||
return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0');
|
||||
}
|
||||
|
||||
static startMailRecipientState(builder:flatbuffers.Builder) {
|
||||
builder.startObject(3);
|
||||
}
|
||||
|
||||
static addMessageId(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, messageIdOffset, 0);
|
||||
}
|
||||
|
||||
static addReadAtMs(builder:flatbuffers.Builder, readAtMs:bigint) {
|
||||
builder.addFieldInt64(1, readAtMs, BigInt('0'));
|
||||
}
|
||||
|
||||
static addDeletedAtMs(builder:flatbuffers.Builder, deletedAtMs:bigint) {
|
||||
builder.addFieldInt64(2, deletedAtMs, BigInt('0'));
|
||||
}
|
||||
|
||||
static endMailRecipientState(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createMailRecipientState(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset, readAtMs:bigint, deletedAtMs:bigint):flatbuffers.Offset {
|
||||
MailRecipientState.startMailRecipientState(builder);
|
||||
MailRecipientState.addMessageId(builder, messageIdOffset);
|
||||
MailRecipientState.addReadAtMs(builder, readAtMs);
|
||||
MailRecipientState.addDeletedAtMs(builder, deletedAtMs);
|
||||
return MailRecipientState.endMailRecipientState(builder);
|
||||
}
|
||||
|
||||
unpack(): MailRecipientStateT {
|
||||
return new MailRecipientStateT(
|
||||
this.messageId(),
|
||||
this.readAtMs(),
|
||||
this.deletedAtMs()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: MailRecipientStateT): void {
|
||||
_o.messageId = this.messageId();
|
||||
_o.readAtMs = this.readAtMs();
|
||||
_o.deletedAtMs = this.deletedAtMs();
|
||||
}
|
||||
}
|
||||
|
||||
export class MailRecipientStateT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public messageId: string|Uint8Array|null = null,
|
||||
public readAtMs: bigint = BigInt('0'),
|
||||
public deletedAtMs: bigint = BigInt('0')
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const messageId = (this.messageId !== null ? builder.createString(this.messageId!) : 0);
|
||||
|
||||
return MailRecipientState.createMailRecipientState(builder,
|
||||
messageId,
|
||||
this.readAtMs,
|
||||
this.deletedAtMs
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class MessageGetRequest implements flatbuffers.IUnpackableObject<MessageGetRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):MessageGetRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsMessageGetRequest(bb:flatbuffers.ByteBuffer, obj?:MessageGetRequest):MessageGetRequest {
|
||||
return (obj || new MessageGetRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsMessageGetRequest(bb:flatbuffers.ByteBuffer, obj?:MessageGetRequest):MessageGetRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new MessageGetRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
messageId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
static startMessageGetRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addMessageId(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(1, messageIdOffset, 0);
|
||||
}
|
||||
|
||||
static endMessageGetRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
builder.requiredField(offset, 6) // message_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): MessageGetRequestT {
|
||||
return new MessageGetRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
(this.messageId() !== null ? this.messageId()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: MessageGetRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.messageId = (this.messageId() !== null ? this.messageId()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageGetRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public messageId: UUIDT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
MessageGetRequest.startMessageGetRequest(builder);
|
||||
MessageGetRequest.addGameId(builder, (this.gameId !== null ? this.gameId!.pack(builder) : 0));
|
||||
MessageGetRequest.addMessageId(builder, (this.messageId !== null ? this.messageId!.pack(builder) : 0));
|
||||
|
||||
return MessageGetRequest.endMessageGetRequest(builder);
|
||||
}
|
||||
}
|
||||
@@ -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 { MailMessage, MailMessageT } from '../diplomail/mail-message.js';
|
||||
|
||||
|
||||
export class MessageGetResponse implements flatbuffers.IUnpackableObject<MessageGetResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):MessageGetResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsMessageGetResponse(bb:flatbuffers.ByteBuffer, obj?:MessageGetResponse):MessageGetResponse {
|
||||
return (obj || new MessageGetResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsMessageGetResponse(bb:flatbuffers.ByteBuffer, obj?:MessageGetResponse):MessageGetResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new MessageGetResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
message(obj?:MailMessage):MailMessage|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new MailMessage()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
static startMessageGetResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addMessage(builder:flatbuffers.Builder, messageOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, messageOffset, 0);
|
||||
}
|
||||
|
||||
static endMessageGetResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createMessageGetResponse(builder:flatbuffers.Builder, messageOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
MessageGetResponse.startMessageGetResponse(builder);
|
||||
MessageGetResponse.addMessage(builder, messageOffset);
|
||||
return MessageGetResponse.endMessageGetResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): MessageGetResponseT {
|
||||
return new MessageGetResponseT(
|
||||
(this.message() !== null ? this.message()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: MessageGetResponseT): void {
|
||||
_o.message = (this.message() !== null ? this.message()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageGetResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public message: MailMessageT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const message = (this.message !== null ? this.message!.pack(builder) : 0);
|
||||
|
||||
return MessageGetResponse.createMessageGetResponse(builder,
|
||||
message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class ReadRequest implements flatbuffers.IUnpackableObject<ReadRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ReadRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsReadRequest(bb:flatbuffers.ByteBuffer, obj?:ReadRequest):ReadRequest {
|
||||
return (obj || new ReadRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsReadRequest(bb:flatbuffers.ByteBuffer, obj?:ReadRequest):ReadRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ReadRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
messageId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 6);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
static startReadRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(2);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addMessageId(builder:flatbuffers.Builder, messageIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(1, messageIdOffset, 0);
|
||||
}
|
||||
|
||||
static endReadRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
builder.requiredField(offset, 6) // message_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
unpack(): ReadRequestT {
|
||||
return new ReadRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
(this.messageId() !== null ? this.messageId()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ReadRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.messageId = (this.messageId() !== null ? this.messageId()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public messageId: UUIDT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
ReadRequest.startReadRequest(builder);
|
||||
ReadRequest.addGameId(builder, (this.gameId !== null ? this.gameId!.pack(builder) : 0));
|
||||
ReadRequest.addMessageId(builder, (this.messageId !== null ? this.messageId!.pack(builder) : 0));
|
||||
|
||||
return ReadRequest.endReadRequest(builder);
|
||||
}
|
||||
}
|
||||
@@ -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 { MailRecipientState, MailRecipientStateT } from '../diplomail/mail-recipient-state.js';
|
||||
|
||||
|
||||
export class ReadResponse implements flatbuffers.IUnpackableObject<ReadResponseT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):ReadResponse {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsReadResponse(bb:flatbuffers.ByteBuffer, obj?:ReadResponse):ReadResponse {
|
||||
return (obj || new ReadResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsReadResponse(bb:flatbuffers.ByteBuffer, obj?:ReadResponse):ReadResponse {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new ReadResponse()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
state(obj?:MailRecipientState):MailRecipientState|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new MailRecipientState()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null;
|
||||
}
|
||||
|
||||
static startReadResponse(builder:flatbuffers.Builder) {
|
||||
builder.startObject(1);
|
||||
}
|
||||
|
||||
static addState(builder:flatbuffers.Builder, stateOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(0, stateOffset, 0);
|
||||
}
|
||||
|
||||
static endReadResponse(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createReadResponse(builder:flatbuffers.Builder, stateOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
ReadResponse.startReadResponse(builder);
|
||||
ReadResponse.addState(builder, stateOffset);
|
||||
return ReadResponse.endReadResponse(builder);
|
||||
}
|
||||
|
||||
unpack(): ReadResponseT {
|
||||
return new ReadResponseT(
|
||||
(this.state() !== null ? this.state()!.unpack() : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: ReadResponseT): void {
|
||||
_o.state = (this.state() !== null ? this.state()!.unpack() : null);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReadResponseT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public state: MailRecipientStateT|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const state = (this.state !== null ? this.state!.pack(builder) : 0);
|
||||
|
||||
return ReadResponse.createReadResponse(builder,
|
||||
state
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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 { UUID, UUIDT } from '../common/uuid.js';
|
||||
|
||||
|
||||
export class SendRequest implements flatbuffers.IUnpackableObject<SendRequestT> {
|
||||
bb: flatbuffers.ByteBuffer|null = null;
|
||||
bb_pos = 0;
|
||||
__init(i:number, bb:flatbuffers.ByteBuffer):SendRequest {
|
||||
this.bb_pos = i;
|
||||
this.bb = bb;
|
||||
return this;
|
||||
}
|
||||
|
||||
static getRootAsSendRequest(bb:flatbuffers.ByteBuffer, obj?:SendRequest):SendRequest {
|
||||
return (obj || new SendRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
static getSizePrefixedRootAsSendRequest(bb:flatbuffers.ByteBuffer, obj?:SendRequest):SendRequest {
|
||||
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
|
||||
return (obj || new SendRequest()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
|
||||
}
|
||||
|
||||
gameId(obj?:UUID):UUID|null {
|
||||
const offset = this.bb!.__offset(this.bb_pos, 4);
|
||||
return offset ? (obj || new UUID()).__init(this.bb_pos + offset, this.bb!) : null;
|
||||
}
|
||||
|
||||
recipientUserId():string|null
|
||||
recipientUserId(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientUserId(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;
|
||||
}
|
||||
|
||||
recipientRaceName():string|null
|
||||
recipientRaceName(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
recipientRaceName(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;
|
||||
}
|
||||
|
||||
subject():string|null
|
||||
subject(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
subject(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;
|
||||
}
|
||||
|
||||
body():string|null
|
||||
body(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
|
||||
body(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;
|
||||
}
|
||||
|
||||
static startSendRequest(builder:flatbuffers.Builder) {
|
||||
builder.startObject(5);
|
||||
}
|
||||
|
||||
static addGameId(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldStruct(0, gameIdOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipientUserId(builder:flatbuffers.Builder, recipientUserIdOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(1, recipientUserIdOffset, 0);
|
||||
}
|
||||
|
||||
static addRecipientRaceName(builder:flatbuffers.Builder, recipientRaceNameOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(2, recipientRaceNameOffset, 0);
|
||||
}
|
||||
|
||||
static addSubject(builder:flatbuffers.Builder, subjectOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(3, subjectOffset, 0);
|
||||
}
|
||||
|
||||
static addBody(builder:flatbuffers.Builder, bodyOffset:flatbuffers.Offset) {
|
||||
builder.addFieldOffset(4, bodyOffset, 0);
|
||||
}
|
||||
|
||||
static endSendRequest(builder:flatbuffers.Builder):flatbuffers.Offset {
|
||||
const offset = builder.endObject();
|
||||
builder.requiredField(offset, 4) // game_id
|
||||
return offset;
|
||||
}
|
||||
|
||||
static createSendRequest(builder:flatbuffers.Builder, gameIdOffset:flatbuffers.Offset, recipientUserIdOffset:flatbuffers.Offset, recipientRaceNameOffset:flatbuffers.Offset, subjectOffset:flatbuffers.Offset, bodyOffset:flatbuffers.Offset):flatbuffers.Offset {
|
||||
SendRequest.startSendRequest(builder);
|
||||
SendRequest.addGameId(builder, gameIdOffset);
|
||||
SendRequest.addRecipientUserId(builder, recipientUserIdOffset);
|
||||
SendRequest.addRecipientRaceName(builder, recipientRaceNameOffset);
|
||||
SendRequest.addSubject(builder, subjectOffset);
|
||||
SendRequest.addBody(builder, bodyOffset);
|
||||
return SendRequest.endSendRequest(builder);
|
||||
}
|
||||
|
||||
unpack(): SendRequestT {
|
||||
return new SendRequestT(
|
||||
(this.gameId() !== null ? this.gameId()!.unpack() : null),
|
||||
this.recipientUserId(),
|
||||
this.recipientRaceName(),
|
||||
this.subject(),
|
||||
this.body()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unpackTo(_o: SendRequestT): void {
|
||||
_o.gameId = (this.gameId() !== null ? this.gameId()!.unpack() : null);
|
||||
_o.recipientUserId = this.recipientUserId();
|
||||
_o.recipientRaceName = this.recipientRaceName();
|
||||
_o.subject = this.subject();
|
||||
_o.body = this.body();
|
||||
}
|
||||
}
|
||||
|
||||
export class SendRequestT implements flatbuffers.IGeneratedObject {
|
||||
constructor(
|
||||
public gameId: UUIDT|null = null,
|
||||
public recipientUserId: string|Uint8Array|null = null,
|
||||
public recipientRaceName: string|Uint8Array|null = null,
|
||||
public subject: string|Uint8Array|null = null,
|
||||
public body: string|Uint8Array|null = null
|
||||
){}
|
||||
|
||||
|
||||
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
|
||||
const recipientUserId = (this.recipientUserId !== null ? builder.createString(this.recipientUserId!) : 0);
|
||||
const recipientRaceName = (this.recipientRaceName !== null ? builder.createString(this.recipientRaceName!) : 0);
|
||||
const subject = (this.subject !== null ? builder.createString(this.subject!) : 0);
|
||||
const body = (this.body !== null ? builder.createString(this.body!) : 0);
|
||||
|
||||
return SendRequest.createSendRequest(builder,
|
||||
(this.gameId !== null ? this.gameId!.pack(builder) : 0),
|
||||
recipientUserId,
|
||||
recipientRaceName,
|
||||
subject,
|
||||
body
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user