aa2290b7b4
Step-up: email accounts confirm with a mailed code (purpose=delete, no deeplink —
ConfirmByToken refuses a delete token so a stray click can't delete); platform-only
accounts type a fixed phrase (anti-impulse). Endpoints /user/delete/{request,confirm};
the confirm orchestration resigns active games, drops all-robot games, tombstones +
anonymizes the account (freeing its creds), and revokes its sessions — the tombstone is
the point of no return, the rest best-effort. Gateway account.delete.{request,confirm}
ops + fbs AccountDeleteConfirm/AccountDeleteRequestResult + branded ru/en delete email.
Integration tests cover the step-up (code + no-email) and the orchestration pieces.
502 lines
17 KiB
Go
502 lines
17 KiB
Go
package transcode
|
|
|
|
import (
|
|
flatbuffers "github.com/google/flatbuffers/go"
|
|
|
|
"scrabble/gateway/internal/backendclient"
|
|
fb "scrabble/pkg/fbs/scrabblefb"
|
|
"scrabble/pkg/wire"
|
|
)
|
|
|
|
// The encoders build the FlatBuffers response payloads from the backend's typed
|
|
// responses. FlatBuffers is built bottom-up: every string and child vector is
|
|
// created before the table that references it, and no two tables/vectors are
|
|
// under construction at once.
|
|
|
|
// encodeSession builds a Session payload.
|
|
func encodeSession(s backendclient.SessionResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
token := b.CreateString(s.Token)
|
|
uid := b.CreateString(s.UserID)
|
|
name := b.CreateString(s.DisplayName)
|
|
fb.SessionStart(b)
|
|
fb.SessionAddToken(b, token)
|
|
fb.SessionAddUserId(b, uid)
|
|
fb.SessionAddIsGuest(b, s.IsGuest)
|
|
fb.SessionAddDisplayName(b, name)
|
|
b.Finish(fb.SessionEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeAck builds an Ack payload.
|
|
func encodeAck(ok bool) []byte {
|
|
b := flatbuffers.NewBuilder(16)
|
|
fb.AckStart(b)
|
|
fb.AckAddOk(b, ok)
|
|
b.Finish(fb.AckEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeDeleteRequestResult builds an AccountDeleteRequestResult payload reporting which
|
|
// deletion step-up the account uses ("email" | "phrase").
|
|
func encodeDeleteRequestResult(method string) []byte {
|
|
b := flatbuffers.NewBuilder(32)
|
|
m := b.CreateString(method)
|
|
fb.AccountDeleteRequestResultStart(b)
|
|
fb.AccountDeleteRequestResultAddMethod(b, m)
|
|
b.Finish(fb.AccountDeleteRequestResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeConfirmLinkResult builds an EmailConfirmLinkResult payload, embedding the
|
|
// minted Session for a login. All strings and the nested Session table are built
|
|
// before the result table is opened.
|
|
func encodeConfirmLinkResult(r backendclient.ConfirmLinkResp) []byte {
|
|
b := flatbuffers.NewBuilder(160)
|
|
purpose := b.CreateString(r.Purpose)
|
|
status := b.CreateString(r.Status)
|
|
var session flatbuffers.UOffsetT
|
|
if r.Session != nil {
|
|
token := b.CreateString(r.Session.Token)
|
|
uid := b.CreateString(r.Session.UserID)
|
|
name := b.CreateString(r.Session.DisplayName)
|
|
fb.SessionStart(b)
|
|
fb.SessionAddToken(b, token)
|
|
fb.SessionAddUserId(b, uid)
|
|
fb.SessionAddIsGuest(b, r.Session.IsGuest)
|
|
fb.SessionAddDisplayName(b, name)
|
|
session = fb.SessionEnd(b)
|
|
}
|
|
fb.EmailConfirmLinkResultStart(b)
|
|
fb.EmailConfirmLinkResultAddPurpose(b, purpose)
|
|
fb.EmailConfirmLinkResultAddStatus(b, status)
|
|
if r.Session != nil {
|
|
fb.EmailConfirmLinkResultAddSession(b, session)
|
|
}
|
|
b.Finish(fb.EmailConfirmLinkResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeProfile builds a Profile payload, including the advertising-banner block
|
|
// when the backend marked the viewer eligible.
|
|
func encodeProfile(p backendclient.ProfileResp) []byte {
|
|
b := flatbuffers.NewBuilder(192)
|
|
uid := b.CreateString(p.UserID)
|
|
name := b.CreateString(p.DisplayName)
|
|
lang := b.CreateString(p.PreferredLanguage)
|
|
tz := b.CreateString(p.TimeZone)
|
|
awayStart := b.CreateString(p.AwayStart)
|
|
awayEnd := b.CreateString(p.AwayEnd)
|
|
email := b.CreateString(p.Email)
|
|
// Build the banner table (and its children) before opening Profile: FlatBuffers
|
|
// forbids a nested table while another is under construction.
|
|
var banner flatbuffers.UOffsetT
|
|
if p.Banner != nil {
|
|
banner = encodeBanner(b, *p.Banner)
|
|
}
|
|
prefs := buildStringVector(b, p.VariantPreferences, fb.ProfileStartVariantPreferencesVector)
|
|
fb.ProfileStart(b)
|
|
fb.ProfileAddUserId(b, uid)
|
|
fb.ProfileAddDisplayName(b, name)
|
|
fb.ProfileAddPreferredLanguage(b, lang)
|
|
fb.ProfileAddTimeZone(b, tz)
|
|
fb.ProfileAddHintBalance(b, int32(p.HintBalance))
|
|
fb.ProfileAddBlockChat(b, p.BlockChat)
|
|
fb.ProfileAddBlockFriendRequests(b, p.BlockFriendRequests)
|
|
fb.ProfileAddIsGuest(b, p.IsGuest)
|
|
fb.ProfileAddAwayStart(b, awayStart)
|
|
fb.ProfileAddAwayEnd(b, awayEnd)
|
|
fb.ProfileAddNotificationsInAppOnly(b, p.NotificationsInAppOnly)
|
|
fb.ProfileAddVariantPreferences(b, prefs)
|
|
fb.ProfileAddEmail(b, email)
|
|
fb.ProfileAddTelegramLinked(b, p.TelegramLinked)
|
|
fb.ProfileAddVkLinked(b, p.VkLinked)
|
|
if p.Banner != nil {
|
|
fb.ProfileAddBanner(b, banner)
|
|
}
|
|
b.Finish(fb.ProfileEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeBanner builds a BannerInfo table from the resolved banner block and
|
|
// returns its offset. It is bottom-up: each campaign's messages vector and table
|
|
// are built first, then the campaigns vector, then the BannerInfo table. The
|
|
// caller must invoke it with no table under construction.
|
|
func encodeBanner(b *flatbuffers.Builder, banner backendclient.BannerResp) flatbuffers.UOffsetT {
|
|
campOffsets := make([]flatbuffers.UOffsetT, len(banner.Campaigns))
|
|
for i, c := range banner.Campaigns {
|
|
msgOffsets := make([]flatbuffers.UOffsetT, len(c.Messages))
|
|
for j, m := range c.Messages {
|
|
msgOffsets[j] = b.CreateString(m)
|
|
}
|
|
fb.BannerCampaignStartMessagesVector(b, len(msgOffsets))
|
|
for j := len(msgOffsets) - 1; j >= 0; j-- {
|
|
b.PrependUOffsetT(msgOffsets[j])
|
|
}
|
|
msgs := b.EndVector(len(msgOffsets))
|
|
fb.BannerCampaignStart(b)
|
|
fb.BannerCampaignAddWeight(b, int32(c.Weight))
|
|
fb.BannerCampaignAddMessages(b, msgs)
|
|
campOffsets[i] = fb.BannerCampaignEnd(b)
|
|
}
|
|
fb.BannerInfoStartCampaignsVector(b, len(campOffsets))
|
|
for i := len(campOffsets) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(campOffsets[i])
|
|
}
|
|
camps := b.EndVector(len(campOffsets))
|
|
t := banner.Timings
|
|
fb.BannerInfoStart(b)
|
|
fb.BannerInfoAddCampaigns(b, camps)
|
|
fb.BannerInfoAddHoldMs(b, int32(t.HoldMs))
|
|
fb.BannerInfoAddEdgePauseMs(b, int32(t.EdgePauseMs))
|
|
fb.BannerInfoAddScrollPxPerSec(b, int32(t.ScrollPxPerSec))
|
|
fb.BannerInfoAddFadeOutMs(b, int32(t.FadeOutMs))
|
|
fb.BannerInfoAddGapMs(b, int32(t.GapMs))
|
|
fb.BannerInfoAddFadeInMs(b, int32(t.FadeInMs))
|
|
return fb.BannerInfoEnd(b)
|
|
}
|
|
|
|
// encodeBlockStatus builds a BlockStatus payload.
|
|
func encodeBlockStatus(s backendclient.BlockStatusResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
until := b.CreateString(s.Until)
|
|
reason := b.CreateString(s.Reason)
|
|
fb.BlockStatusStart(b)
|
|
fb.BlockStatusAddBlocked(b, s.Blocked)
|
|
fb.BlockStatusAddPermanent(b, s.Permanent)
|
|
fb.BlockStatusAddUntil(b, until)
|
|
fb.BlockStatusAddReason(b, reason)
|
|
b.Finish(fb.BlockStatusEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeLinkResult builds a LinkResult payload. A switched-session token
|
|
// (a guest initiator whose durable counterpart won) is carried as a nested Session
|
|
// for the client to adopt; it is omitted otherwise.
|
|
func encodeLinkResult(r backendclient.LinkResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
status := b.CreateString(r.Status)
|
|
secID := b.CreateString(r.SecondaryUserID)
|
|
secName := b.CreateString(r.SecondaryName)
|
|
hasSession := r.Token != "" && r.Profile != nil
|
|
var sess flatbuffers.UOffsetT
|
|
if hasSession {
|
|
token := b.CreateString(r.Token)
|
|
uid := b.CreateString(r.Profile.UserID)
|
|
name := b.CreateString(r.Profile.DisplayName)
|
|
fb.SessionStart(b)
|
|
fb.SessionAddToken(b, token)
|
|
fb.SessionAddUserId(b, uid)
|
|
fb.SessionAddIsGuest(b, r.Profile.IsGuest)
|
|
fb.SessionAddDisplayName(b, name)
|
|
sess = fb.SessionEnd(b)
|
|
}
|
|
fb.LinkResultStart(b)
|
|
fb.LinkResultAddStatus(b, status)
|
|
fb.LinkResultAddSecondaryUserId(b, secID)
|
|
fb.LinkResultAddSecondaryDisplayName(b, secName)
|
|
fb.LinkResultAddSecondaryGames(b, int32(r.SecondaryGames))
|
|
fb.LinkResultAddSecondaryFriends(b, int32(r.SecondaryFriends))
|
|
if hasSession {
|
|
fb.LinkResultAddSession(b, sess)
|
|
}
|
|
b.Finish(fb.LinkResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeMoveResult builds a MoveResult payload.
|
|
func encodeMoveResult(r backendclient.MoveResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
move := buildMoveRecord(b, r.Move)
|
|
game := buildGameView(b, r.Game)
|
|
rackBytes := make([]byte, len(r.Rack))
|
|
for i, v := range r.Rack {
|
|
rackBytes[i] = byte(v)
|
|
}
|
|
rack := b.CreateByteVector(rackBytes)
|
|
fb.MoveResultStart(b)
|
|
fb.MoveResultAddMove(b, move)
|
|
fb.MoveResultAddGame(b, game)
|
|
fb.MoveResultAddRack(b, rack)
|
|
fb.MoveResultAddBagLen(b, int32(r.BagLen))
|
|
b.Finish(fb.MoveResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeState builds a StateView payload. The rack is a vector of alphabet indices and the
|
|
// alphabet display table is included only when the backend returned it (the
|
|
// client requests it on a per-variant cache miss).
|
|
func encodeState(s backendclient.StateResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
b.Finish(wire.BuildStateView(b, toWireState(s)))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// toWireState maps a StateResp to the shared wire.StateView.
|
|
func toWireState(s backendclient.StateResp) wire.StateView {
|
|
alphabet := make([]wire.AlphabetEntry, len(s.Alphabet))
|
|
for i, e := range s.Alphabet {
|
|
alphabet[i] = wire.AlphabetEntry{Index: e.Index, Letter: e.Letter, Value: e.Value}
|
|
}
|
|
return wire.StateView{
|
|
Game: toWireGame(s.Game),
|
|
Seat: s.Seat,
|
|
Rack: s.Rack,
|
|
BagLen: s.BagLen,
|
|
HintsRemaining: s.HintsRemaining,
|
|
WalletBalance: s.WalletBalance,
|
|
Alphabet: alphabet,
|
|
}
|
|
}
|
|
|
|
// encodeMatch builds a MatchResult payload.
|
|
func encodeMatch(m backendclient.MatchResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
// Enqueue always lands the caller in a game; an open game awaiting an opponent reports
|
|
// matched=false but still carries it, so encode the game whenever it is present (else the
|
|
// client never receives it and cannot navigate in).
|
|
hasGame := m.Game != nil
|
|
var game flatbuffers.UOffsetT
|
|
if hasGame {
|
|
game = buildGameView(b, *m.Game)
|
|
}
|
|
fb.MatchResultStart(b)
|
|
fb.MatchResultAddMatched(b, m.Matched)
|
|
if hasGame {
|
|
fb.MatchResultAddGame(b, game)
|
|
}
|
|
b.Finish(fb.MatchResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// buildChatMessage builds a ChatMessage table and returns its offset.
|
|
func buildChatMessage(b *flatbuffers.Builder, c backendclient.ChatResp) flatbuffers.UOffsetT {
|
|
id := b.CreateString(c.ID)
|
|
gid := b.CreateString(c.GameID)
|
|
sid := b.CreateString(c.SenderID)
|
|
kind := b.CreateString(c.Kind)
|
|
body := b.CreateString(c.Body)
|
|
fb.ChatMessageStart(b)
|
|
fb.ChatMessageAddId(b, id)
|
|
fb.ChatMessageAddGameId(b, gid)
|
|
fb.ChatMessageAddSenderId(b, sid)
|
|
fb.ChatMessageAddKind(b, kind)
|
|
fb.ChatMessageAddBody(b, body)
|
|
fb.ChatMessageAddCreatedAtUnix(b, c.CreatedAtUnix)
|
|
return fb.ChatMessageEnd(b)
|
|
}
|
|
|
|
// encodeChat builds a ChatMessage payload.
|
|
func encodeChat(c backendclient.ChatResp) []byte {
|
|
b := flatbuffers.NewBuilder(192)
|
|
b.Finish(buildChatMessage(b, c))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeHintResult builds a HintResult payload.
|
|
func encodeHintResult(r backendclient.HintResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
move := buildMoveRecord(b, r.Move)
|
|
fb.HintResultStart(b)
|
|
fb.HintResultAddMove(b, move)
|
|
fb.HintResultAddHintsRemaining(b, int32(r.HintsRemaining))
|
|
fb.HintResultAddWalletBalance(b, int32(r.WalletBalance))
|
|
b.Finish(fb.HintResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeEvalResult builds an EvalResult payload.
|
|
func encodeEvalResult(r backendclient.EvalResultResp) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
words := buildStringVector(b, r.Words, fb.EvalResultStartWordsVector)
|
|
dir := b.CreateString(r.Dir)
|
|
fb.EvalResultStart(b)
|
|
fb.EvalResultAddLegal(b, r.Legal)
|
|
fb.EvalResultAddScore(b, int32(r.Score))
|
|
fb.EvalResultAddWords(b, words)
|
|
fb.EvalResultAddDir(b, dir)
|
|
b.Finish(fb.EvalResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeWordCheck builds a WordCheckResult payload.
|
|
func encodeWordCheck(r backendclient.WordCheckResp) []byte {
|
|
b := flatbuffers.NewBuilder(64)
|
|
word := b.CreateString(r.Word)
|
|
fb.WordCheckResultStart(b)
|
|
fb.WordCheckResultAddWord(b, word)
|
|
fb.WordCheckResultAddLegal(b, r.Legal)
|
|
b.Finish(fb.WordCheckResultEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeDraftView builds a DraftView payload wrapping the player's composition JSON. The
|
|
// string is empty for the save acknowledgement (the client ignores that payload).
|
|
func encodeDraftView(jsonStr string) []byte {
|
|
b := flatbuffers.NewBuilder(256)
|
|
j := b.CreateString(jsonStr)
|
|
fb.DraftViewStart(b)
|
|
fb.DraftViewAddJson(b, j)
|
|
b.Finish(fb.DraftViewEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeHistory builds a History payload (the decoded move journal).
|
|
func encodeHistory(r backendclient.HistoryResp) []byte {
|
|
b := flatbuffers.NewBuilder(1024)
|
|
moveOffs := make([]flatbuffers.UOffsetT, len(r.Moves))
|
|
for i, m := range r.Moves {
|
|
moveOffs[i] = buildMoveRecord(b, m)
|
|
}
|
|
fb.HistoryStartMovesVector(b, len(moveOffs))
|
|
for i := len(moveOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(moveOffs[i])
|
|
}
|
|
moves := b.EndVector(len(moveOffs))
|
|
gid := b.CreateString(r.GameID)
|
|
fb.HistoryStart(b)
|
|
fb.HistoryAddGameId(b, gid)
|
|
fb.HistoryAddMoves(b, moves)
|
|
b.Finish(fb.HistoryEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeGameList builds a GameList payload (the caller's games).
|
|
func encodeGameList(r backendclient.GameListResp) []byte {
|
|
b := flatbuffers.NewBuilder(1024)
|
|
gameOffs := make([]flatbuffers.UOffsetT, len(r.Games))
|
|
for i, g := range r.Games {
|
|
gameOffs[i] = buildGameView(b, g)
|
|
}
|
|
fb.GameListStartGamesVector(b, len(gameOffs))
|
|
for i := len(gameOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(gameOffs[i])
|
|
}
|
|
games := b.EndVector(len(gameOffs))
|
|
fb.GameListStart(b)
|
|
fb.GameListAddGames(b, games)
|
|
fb.GameListAddAtGameLimit(b, r.AtGameLimit)
|
|
b.Finish(fb.GameListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeChatList builds a ChatList payload (a game's chat history).
|
|
func encodeChatList(r backendclient.ChatListResp) []byte {
|
|
b := flatbuffers.NewBuilder(512)
|
|
msgOffs := make([]flatbuffers.UOffsetT, len(r.Messages))
|
|
for i, m := range r.Messages {
|
|
msgOffs[i] = buildChatMessage(b, m)
|
|
}
|
|
fb.ChatListStartMessagesVector(b, len(msgOffs))
|
|
for i := len(msgOffs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(msgOffs[i])
|
|
}
|
|
msgs := b.EndVector(len(msgOffs))
|
|
fb.ChatListStart(b)
|
|
fb.ChatListAddMessages(b, msgs)
|
|
b.Finish(fb.ChatListEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeFeedbackState builds a FeedbackState payload, nesting the reply when present.
|
|
func encodeFeedbackState(st backendclient.FeedbackStateResp) []byte {
|
|
b := flatbuffers.NewBuilder(128)
|
|
var reply flatbuffers.UOffsetT
|
|
if st.Reply != nil {
|
|
body := b.CreateString(st.Reply.Body)
|
|
fb.FeedbackReplyStart(b)
|
|
fb.FeedbackReplyAddBody(b, body)
|
|
fb.FeedbackReplyAddRepliedAtUnix(b, st.Reply.RepliedAtUnix)
|
|
reply = fb.FeedbackReplyEnd(b)
|
|
}
|
|
reason := b.CreateString(st.BlockedReason)
|
|
fb.FeedbackStateStart(b)
|
|
fb.FeedbackStateAddCanSend(b, st.CanSend)
|
|
fb.FeedbackStateAddBlockedReason(b, reason)
|
|
if st.Reply != nil {
|
|
fb.FeedbackStateAddReply(b, reply)
|
|
}
|
|
b.Finish(fb.FeedbackStateEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// encodeFeedbackUnread builds a FeedbackUnread payload.
|
|
func encodeFeedbackUnread(u backendclient.FeedbackUnreadResp) []byte {
|
|
b := flatbuffers.NewBuilder(16)
|
|
fb.FeedbackUnreadStart(b)
|
|
fb.FeedbackUnreadAddReplyUnread(b, u.ReplyUnread)
|
|
b.Finish(fb.FeedbackUnreadEnd(b))
|
|
return b.FinishedBytes()
|
|
}
|
|
|
|
// buildGameView builds a GameView table and returns its offset.
|
|
func buildGameView(b *flatbuffers.Builder, g backendclient.GameResp) flatbuffers.UOffsetT {
|
|
return wire.BuildGameView(b, toWireGame(g))
|
|
}
|
|
|
|
// toWireGame maps a GameResp to the shared wire.GameView.
|
|
func toWireGame(g backendclient.GameResp) wire.GameView {
|
|
seats := make([]wire.SeatView, len(g.Seats))
|
|
for i, s := range g.Seats {
|
|
seats[i] = wire.SeatView{
|
|
Seat: s.Seat,
|
|
AccountID: s.AccountID,
|
|
Score: s.Score,
|
|
HintsUsed: s.HintsUsed,
|
|
IsWinner: s.IsWinner,
|
|
DisplayName: s.DisplayName,
|
|
}
|
|
}
|
|
return wire.GameView{
|
|
ID: g.ID,
|
|
Variant: g.Variant,
|
|
DictVersion: g.DictVersion,
|
|
Status: g.Status,
|
|
Players: g.Players,
|
|
ToMove: g.ToMove,
|
|
TurnTimeoutSecs: g.TurnTimeoutSecs,
|
|
MultipleWordsPerTurn: g.MultipleWordsPerTurn,
|
|
VsAI: g.VsAI,
|
|
MoveCount: g.MoveCount,
|
|
EndReason: g.EndReason,
|
|
Seats: seats,
|
|
LastActivityUnix: g.LastActivityUnix,
|
|
UnreadChat: g.UnreadChat,
|
|
UnreadMessages: g.UnreadMessages,
|
|
}
|
|
}
|
|
|
|
// buildMoveRecord builds a MoveRecord table and returns its offset.
|
|
func buildMoveRecord(b *flatbuffers.Builder, m backendclient.MoveRecordResp) flatbuffers.UOffsetT {
|
|
tiles := make([]wire.TileRecord, len(m.Tiles))
|
|
for i, t := range m.Tiles {
|
|
tiles[i] = wire.TileRecord{Row: t.Row, Col: t.Col, Letter: t.Letter, Blank: t.Blank}
|
|
}
|
|
return wire.BuildMoveRecord(b, wire.MoveRecord{
|
|
Player: m.Player,
|
|
Action: m.Action,
|
|
Dir: m.Dir,
|
|
MainRow: m.MainRow,
|
|
MainCol: m.MainCol,
|
|
Tiles: tiles,
|
|
Words: m.Words,
|
|
Count: m.Count,
|
|
Score: m.Score,
|
|
Total: m.Total,
|
|
})
|
|
}
|
|
|
|
// buildStringVector builds a vector of strings using the table-specific
|
|
// StartXVector function and returns the vector offset.
|
|
func buildStringVector(b *flatbuffers.Builder, items []string, start func(*flatbuffers.Builder, int) flatbuffers.UOffsetT) flatbuffers.UOffsetT {
|
|
offs := make([]flatbuffers.UOffsetT, len(items))
|
|
for i, s := range items {
|
|
offs[i] = b.CreateString(s)
|
|
}
|
|
start(b, len(offs))
|
|
for i := len(offs) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(offs[i])
|
|
}
|
|
return b.EndVector(len(offs))
|
|
}
|