6b6baf5710
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 31s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
Lobby: group the my-games list into your-turn / opponent-turn / finished (empty sections hidden), ordered by last activity (your-turn oldest-first, the other two newest-first), as a compact line-separated list. gameDTO and FB GameView gain last_activity_unix (turn start while active, finish time once finished); a pure lib/lobbysort.ts holds the grouping/ordering. Friends: the in-game 'add to friends' item is now server-derived via a new GET /user/friends/outgoing (+ friends.outgoing op), returning addressees with a pending OR declined request (both read as 'request sent'), so it is correct across reloads; it shows a disabled '✓ in friends' once accepted. It live-updates when the opponent answers: RespondFriendRequest now publishes friend_added (accept) / friend_declined (new notify sub-kind, decline) to the original requester, whose open game re-derives its friend state. Tests: lobbysort unit test; gateway outgoing + last_activity transcode tests; backend integration ListOutgoingRequests + respond-publishes-to-requester; e2e updated for the new lobby section labels + a non-friend active opponent. Docs: ARCHITECTURE notify catalog, FUNCTIONAL(+ru) lobby/friends, PLAN.
190 lines
6.4 KiB
Go
190 lines
6.4 KiB
Go
package transcode
|
|
|
|
import (
|
|
flatbuffers "github.com/google/flatbuffers/go"
|
|
|
|
"scrabble/gateway/internal/backendclient"
|
|
fb "scrabble/pkg/fbs/scrabblefb"
|
|
)
|
|
|
|
// Stage 8 encoders: friends, blocks, invitations, statistics and GCG. They follow
|
|
// encode.go's bottom-up rule (build every string/child vector before the table).
|
|
|
|
// buildAccountRef builds an AccountRef table and returns its offset.
|
|
func buildAccountRef(b *flatbuffers.Builder, r backendclient.AccountRefResp) flatbuffers.UOffsetT {
|
|
id := b.CreateString(r.AccountID)
|
|
name := b.CreateString(r.DisplayName)
|
|
fb.AccountRefStart(b)
|
|
fb.AccountRefAddAccountId(b, id)
|
|
fb.AccountRefAddDisplayName(b, name)
|
|
return fb.AccountRefEnd(b)
|
|
}
|
|
|
|
// buildAccountRefVector builds a [AccountRef] vector using the table-specific
|
|
// StartXVector function and returns the vector offset.
|
|
func buildAccountRefVector(b *flatbuffers.Builder, refs []backendclient.AccountRefResp, start func(*flatbuffers.Builder, int) flatbuffers.UOffsetT) flatbuffers.UOffsetT {
|
|
offs := make([]flatbuffers.UOffsetT, len(refs))
|
|
for i, r := range refs {
|
|
offs[i] = buildAccountRef(b, r)
|
|
}
|
|
start(b, len(offs))
|
|
for i := len(offs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(offs[i])
|
|
}
|
|
return b.EndVector(len(offs))
|
|
}
|
|
|
|
// encodeFriendList builds a FriendList payload.
|
|
func encodeFriendList(r backendclient.FriendListResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
v := buildAccountRefVector(b, r.Friends, fb.FriendListStartFriendsVector)
|
|
fb.FriendListStart(b)
|
|
fb.FriendListAddFriends(b, v)
|
|
b.Finish(fb.FriendListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeIncomingList builds an IncomingRequestList payload.
|
|
func encodeIncomingList(r backendclient.IncomingListResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
v := buildAccountRefVector(b, r.Requests, fb.IncomingRequestListStartRequestsVector)
|
|
fb.IncomingRequestListStart(b)
|
|
fb.IncomingRequestListAddRequests(b, v)
|
|
b.Finish(fb.IncomingRequestListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeOutgoingList builds an OutgoingRequestList payload.
|
|
func encodeOutgoingList(r backendclient.OutgoingListResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
v := buildAccountRefVector(b, r.Requests, fb.OutgoingRequestListStartRequestsVector)
|
|
fb.OutgoingRequestListStart(b)
|
|
fb.OutgoingRequestListAddRequests(b, v)
|
|
b.Finish(fb.OutgoingRequestListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeBlockList builds a BlockList payload.
|
|
func encodeBlockList(r backendclient.BlockListResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
v := buildAccountRefVector(b, r.Blocked, fb.BlockListStartBlockedVector)
|
|
fb.BlockListStart(b)
|
|
fb.BlockListAddBlocked(b, v)
|
|
b.Finish(fb.BlockListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeFriendCode builds a FriendCode payload.
|
|
func encodeFriendCode(r backendclient.FriendCodeResp) []byte {
|
|
b := flatbuffers.NewBuilder(64)
|
|
code := b.CreateString(r.Code)
|
|
fb.FriendCodeStart(b)
|
|
fb.FriendCodeAddCode(b, code)
|
|
fb.FriendCodeAddExpiresAtUnix(b, r.ExpiresAtUnix)
|
|
b.Finish(fb.FriendCodeEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeRedeemResult builds a RedeemResult payload.
|
|
func encodeRedeemResult(r backendclient.RedeemResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
friend := buildAccountRef(b, r.Friend)
|
|
fb.RedeemResultStart(b)
|
|
fb.RedeemResultAddFriend(b, friend)
|
|
b.Finish(fb.RedeemResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeStats builds a StatsView payload.
|
|
func encodeStats(r backendclient.StatsResp) []byte {
|
|
b := flatbuffers.NewBuilder(64)
|
|
fb.StatsViewStart(b)
|
|
fb.StatsViewAddWins(b, int32(r.Wins))
|
|
fb.StatsViewAddLosses(b, int32(r.Losses))
|
|
fb.StatsViewAddDraws(b, int32(r.Draws))
|
|
fb.StatsViewAddMaxGamePoints(b, int32(r.MaxGamePoints))
|
|
fb.StatsViewAddMaxWordPoints(b, int32(r.MaxWordPoints))
|
|
b.Finish(fb.StatsViewEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// buildInvitation builds an Invitation table and returns its offset.
|
|
func buildInvitation(b *flatbuffers.Builder, inv backendclient.InvitationResp) flatbuffers.UOffsetT {
|
|
inviteeOffs := make([]flatbuffers.UOffsetT, len(inv.Invitees))
|
|
for i, iv := range inv.Invitees {
|
|
aid := b.CreateString(iv.AccountID)
|
|
name := b.CreateString(iv.DisplayName)
|
|
resp := b.CreateString(iv.Response)
|
|
fb.InvitationInviteeStart(b)
|
|
fb.InvitationInviteeAddAccountId(b, aid)
|
|
fb.InvitationInviteeAddDisplayName(b, name)
|
|
fb.InvitationInviteeAddSeat(b, int32(iv.Seat))
|
|
fb.InvitationInviteeAddResponse(b, resp)
|
|
inviteeOffs[i] = fb.InvitationInviteeEnd(b)
|
|
}
|
|
fb.InvitationStartInviteesVector(b, len(inviteeOffs))
|
|
for i := len(inviteeOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(inviteeOffs[i])
|
|
}
|
|
invitees := b.EndVector(len(inviteeOffs))
|
|
|
|
inviter := buildAccountRef(b, inv.Inviter)
|
|
id := b.CreateString(inv.ID)
|
|
variant := b.CreateString(inv.Variant)
|
|
dropout := b.CreateString(inv.DropoutTiles)
|
|
status := b.CreateString(inv.Status)
|
|
gameID := b.CreateString(inv.GameID)
|
|
fb.InvitationStart(b)
|
|
fb.InvitationAddId(b, id)
|
|
fb.InvitationAddInviter(b, inviter)
|
|
fb.InvitationAddInvitees(b, invitees)
|
|
fb.InvitationAddVariant(b, variant)
|
|
fb.InvitationAddTurnTimeoutSecs(b, int32(inv.TurnTimeoutSecs))
|
|
fb.InvitationAddHintsAllowed(b, inv.HintsAllowed)
|
|
fb.InvitationAddHintsPerPlayer(b, int32(inv.HintsPerPlayer))
|
|
fb.InvitationAddDropoutTiles(b, dropout)
|
|
fb.InvitationAddStatus(b, status)
|
|
fb.InvitationAddGameId(b, gameID)
|
|
fb.InvitationAddExpiresAtUnix(b, inv.ExpiresAtUnix)
|
|
return fb.InvitationEnd(b)
|
|
}
|
|
|
|
// encodeInvitation builds an Invitation payload.
|
|
func encodeInvitation(inv backendclient.InvitationResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
b.Finish(buildInvitation(b, inv))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeInvitationList builds an InvitationList payload.
|
|
func encodeInvitationList(r backendclient.InvitationListResp) []byte {
|
|
b := flatbuffers.NewBuilder(1024)
|
|
offs := make([]flatbuffers.UOffsetT, len(r.Invitations))
|
|
for i, inv := range r.Invitations {
|
|
offs[i] = buildInvitation(b, inv)
|
|
}
|
|
fb.InvitationListStartInvitationsVector(b, len(offs))
|
|
for i := len(offs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(offs[i])
|
|
}
|
|
v := b.EndVector(len(offs))
|
|
fb.InvitationListStart(b)
|
|
fb.InvitationListAddInvitations(b, v)
|
|
b.Finish(fb.InvitationListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeGcg builds a GcgExport payload.
|
|
func encodeGcg(r backendclient.GcgResp) []byte {
|
|
b := flatbuffers.NewBuilder(1024)
|
|
gid := b.CreateString(r.GameID)
|
|
fn := b.CreateString(r.Filename)
|
|
content := b.CreateString(r.Content)
|
|
fb.GcgExportStart(b)
|
|
fb.GcgExportAddGameId(b, gid)
|
|
fb.GcgExportAddFilename(b, fn)
|
|
fb.GcgExportAddContent(b, content)
|
|
b.Finish(fb.GcgExportEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|