package transcode import ( "context" "scrabble/gateway/internal/backendclient" fb "scrabble/pkg/fbs/scrabblefb" ) // Stage 8 message types: friends (incl. the one-time code path), per-user blocks, // friend-game invitations, profile editing + email binding, statistics and GCG // export. All are authenticated. Registered by registerStage8 from NewRegistry. const ( MsgFriendsList = "friends.list" MsgFriendsIncoming = "friends.incoming" MsgFriendRequest = "friends.request" MsgFriendRespond = "friends.respond" MsgFriendCancel = "friends.cancel" MsgFriendUnfriend = "friends.unfriend" MsgFriendCodeIssue = "friends.code.issue" MsgFriendCodeRedeem = "friends.code.redeem" MsgBlocksList = "blocks.list" MsgBlockAdd = "blocks.add" MsgBlockRemove = "blocks.remove" MsgInvitationsList = "invitation.list" MsgInvitationCreate = "invitation.create" MsgInvitationAccept = "invitation.accept" MsgInvitationDecline = "invitation.decline" MsgInvitationCancel = "invitation.cancel" MsgProfileUpdate = "profile.update" MsgEmailBindReq = "email.bind.request" MsgEmailBindConfirm = "email.bind.confirm" MsgStatsGet = "stats.get" MsgGameGCG = "game.gcg" ) // registerStage8 adds the Stage 8 social, account and history operations to the // registry (all authenticated; the email-bind ops carry the costly-email flag). func registerStage8(r *Registry, backend *backendclient.Client) { r.ops[MsgFriendsList] = Op{Handler: friendsListHandler(backend), Auth: true} r.ops[MsgFriendsIncoming] = Op{Handler: friendsIncomingHandler(backend), Auth: true} r.ops[MsgFriendRequest] = Op{Handler: friendRequestHandler(backend), Auth: true} r.ops[MsgFriendRespond] = Op{Handler: friendRespondHandler(backend), Auth: true} r.ops[MsgFriendCancel] = Op{Handler: friendCancelHandler(backend), Auth: true} r.ops[MsgFriendUnfriend] = Op{Handler: friendUnfriendHandler(backend), Auth: true} r.ops[MsgFriendCodeIssue] = Op{Handler: friendCodeIssueHandler(backend), Auth: true} r.ops[MsgFriendCodeRedeem] = Op{Handler: friendCodeRedeemHandler(backend), Auth: true} r.ops[MsgBlocksList] = Op{Handler: blocksListHandler(backend), Auth: true} r.ops[MsgBlockAdd] = Op{Handler: blockAddHandler(backend), Auth: true} r.ops[MsgBlockRemove] = Op{Handler: blockRemoveHandler(backend), Auth: true} r.ops[MsgInvitationsList] = Op{Handler: invitationsListHandler(backend), Auth: true} r.ops[MsgInvitationCreate] = Op{Handler: invitationCreateHandler(backend), Auth: true} r.ops[MsgInvitationAccept] = Op{Handler: invitationRespondHandler(backend, true), Auth: true} r.ops[MsgInvitationDecline] = Op{Handler: invitationRespondHandler(backend, false), Auth: true} r.ops[MsgInvitationCancel] = Op{Handler: invitationCancelHandler(backend), Auth: true} r.ops[MsgProfileUpdate] = Op{Handler: profileUpdateHandler(backend), Auth: true} r.ops[MsgEmailBindReq] = Op{Handler: emailBindRequestHandler(backend), Auth: true, Email: true} r.ops[MsgEmailBindConfirm] = Op{Handler: emailBindConfirmHandler(backend), Auth: true, Email: true} r.ops[MsgStatsGet] = Op{Handler: statsHandler(backend), Auth: true} r.ops[MsgGameGCG] = Op{Handler: gcgHandler(backend), Auth: true} } // --- friends --- func friendsListHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { res, err := backend.ListFriends(ctx, req.UserID) if err != nil { return nil, err } return encodeFriendList(res), nil } } func friendsIncomingHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { res, err := backend.ListIncoming(ctx, req.UserID) if err != nil { return nil, err } return encodeIncomingList(res), nil } } func friendRequestHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsTargetRequest(req.Payload, 0) if err := backend.SendFriendRequest(ctx, req.UserID, string(in.AccountId())); err != nil { return nil, err } return encodeAck(true), nil } } func friendRespondHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsFriendRespondRequest(req.Payload, 0) if err := backend.RespondFriendRequest(ctx, req.UserID, string(in.RequesterId()), in.Accept()); err != nil { return nil, err } return encodeAck(true), nil } } func friendCancelHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsTargetRequest(req.Payload, 0) if err := backend.CancelFriendRequest(ctx, req.UserID, string(in.AccountId())); err != nil { return nil, err } return encodeAck(true), nil } } func friendUnfriendHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsTargetRequest(req.Payload, 0) if err := backend.Unfriend(ctx, req.UserID, string(in.AccountId())); err != nil { return nil, err } return encodeAck(true), nil } } func friendCodeIssueHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { res, err := backend.IssueFriendCode(ctx, req.UserID) if err != nil { return nil, err } return encodeFriendCode(res), nil } } func friendCodeRedeemHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsRedeemCodeRequest(req.Payload, 0) res, err := backend.RedeemFriendCode(ctx, req.UserID, string(in.Code())) if err != nil { return nil, err } return encodeRedeemResult(res), nil } } // --- blocks --- func blocksListHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { res, err := backend.ListBlocks(ctx, req.UserID) if err != nil { return nil, err } return encodeBlockList(res), nil } } func blockAddHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsTargetRequest(req.Payload, 0) if err := backend.Block(ctx, req.UserID, string(in.AccountId())); err != nil { return nil, err } return encodeAck(true), nil } } func blockRemoveHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsTargetRequest(req.Payload, 0) if err := backend.Unblock(ctx, req.UserID, string(in.AccountId())); err != nil { return nil, err } return encodeAck(true), nil } } // --- invitations --- func invitationsListHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { res, err := backend.ListInvitations(ctx, req.UserID) if err != nil { return nil, err } return encodeInvitationList(res), nil } } func invitationCreateHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsCreateInvitationRequest(req.Payload, 0) params := backendclient.InvitationParams{ InviteeIDs: decodeInviteeIDs(in), Variant: string(in.Variant()), TurnTimeoutSecs: int(in.TurnTimeoutSecs()), HintsAllowed: in.HintsAllowed(), HintsPerPlayer: int(in.HintsPerPlayer()), DropoutTiles: string(in.DropoutTiles()), } res, err := backend.CreateInvitation(ctx, req.UserID, params) if err != nil { return nil, err } return encodeInvitation(res), nil } } func invitationRespondHandler(backend *backendclient.Client, accept bool) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsInvitationActionRequest(req.Payload, 0) res, err := backend.RespondInvitation(ctx, req.UserID, string(in.InvitationId()), accept) if err != nil { return nil, err } return encodeInvitation(res), nil } } func invitationCancelHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsInvitationActionRequest(req.Payload, 0) if err := backend.CancelInvitation(ctx, req.UserID, string(in.InvitationId())); err != nil { return nil, err } return encodeAck(true), nil } } // --- profile, email, stats, gcg --- func profileUpdateHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsUpdateProfileRequest(req.Payload, 0) p := backendclient.ProfileResp{ DisplayName: string(in.DisplayName()), PreferredLanguage: string(in.PreferredLanguage()), TimeZone: string(in.TimeZone()), AwayStart: string(in.AwayStart()), AwayEnd: string(in.AwayEnd()), BlockChat: in.BlockChat(), BlockFriendRequests: in.BlockFriendRequests(), } out, err := backend.UpdateProfile(ctx, req.UserID, p) if err != nil { return nil, err } return encodeProfile(out), nil } } func emailBindRequestHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsEmailBindRequest(req.Payload, 0) if err := backend.EmailBindRequest(ctx, req.UserID, string(in.Email())); err != nil { return nil, err } return encodeAck(true), nil } } func emailBindConfirmHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsEmailConfirmRequest(req.Payload, 0) out, err := backend.EmailBindConfirm(ctx, req.UserID, string(in.Email()), string(in.Code())) if err != nil { return nil, err } return encodeProfile(out), nil } } func statsHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { res, err := backend.Stats(ctx, req.UserID) if err != nil { return nil, err } return encodeStats(res), nil } } func gcgHandler(backend *backendclient.Client) Handler { return func(ctx context.Context, req Request) ([]byte, error) { in := fb.GetRootAsGameActionRequest(req.Payload, 0) res, err := backend.ExportGCG(ctx, req.UserID, string(in.GameId())) if err != nil { return nil, err } return encodeGcg(res), nil } } // decodeInviteeIDs reads the invitee id vector from a CreateInvitationRequest. func decodeInviteeIDs(in *fb.CreateInvitationRequest) []string { n := in.InviteeIdsLength() out := make([]string, 0, n) for i := range n { out = append(out, string(in.InviteeIds(i))) } return out }