feat(gateway): confirmEmailLink RPC + language on the email request + profile event

Add the confirm-link edge method (auth.email.confirm_link): a new
EmailConfirmLinkRequest/Result fbs table, the transcode const + handler + encoder,
and the backend-client call to the existing /sessions/email/confirm-link endpoint —
it rides Execute under the existing service prefix, so no proto/Caddy change. Add a
language field to EmailRequestRequest and forward it (the backend already seeds it).
Add the NotifyProfile sub-kind + notify.ProfileChanged, published by the confirm-link
handler on a successful link so an in-app session re-fetches its profile when the
email was confirmed in another browser. Regenerated fbs bindings (Go + TS).
This commit is contained in:
Ilia Denisov
2026-07-03 04:22:09 +02:00
parent 25d80bc31d
commit 762155a55e
14 changed files with 418 additions and 41 deletions
+45 -32
View File
@@ -19,37 +19,38 @@ import (
// Message types in the vertical slice.
const (
MsgAuthTelegram = "auth.telegram"
MsgAuthVK = "auth.vk"
MsgAuthGuest = "auth.guest"
MsgAuthEmailReq = "auth.email.request"
MsgAuthEmailLogin = "auth.email.login"
MsgProfileGet = "profile.get"
MsgBlockStatus = "account.block_status"
MsgGameSubmitPlay = "game.submit_play"
MsgGameState = "game.state"
MsgLobbyEnqueue = "lobby.enqueue"
MsgLobbyCancel = "lobby.cancel"
MsgLobbyPoll = "lobby.poll"
MsgChatPost = "chat.post"
MsgGamesList = "games.list"
MsgGamePass = "game.pass"
MsgGameExchange = "game.exchange"
MsgGameResign = "game.resign"
MsgGameHint = "game.hint"
MsgGameEvaluate = "game.evaluate"
MsgGameCheckWord = "game.check_word"
MsgGameComplaint = "game.complaint"
MsgGameHistory = "game.history"
MsgChatList = "chat.list"
MsgChatNudge = "chat.nudge"
MsgChatRead = "chat.read"
MsgDraftGet = "draft.get"
MsgDraftSave = "draft.save"
MsgGameHide = "game.hide"
MsgFeedbackSubmit = "feedback.submit"
MsgFeedbackGet = "feedback.get"
MsgFeedbackUnread = "feedback.unread"
MsgAuthTelegram = "auth.telegram"
MsgAuthVK = "auth.vk"
MsgAuthGuest = "auth.guest"
MsgAuthEmailReq = "auth.email.request"
MsgAuthEmailLogin = "auth.email.login"
MsgAuthEmailConfirmLink = "auth.email.confirm_link"
MsgProfileGet = "profile.get"
MsgBlockStatus = "account.block_status"
MsgGameSubmitPlay = "game.submit_play"
MsgGameState = "game.state"
MsgLobbyEnqueue = "lobby.enqueue"
MsgLobbyCancel = "lobby.cancel"
MsgLobbyPoll = "lobby.poll"
MsgChatPost = "chat.post"
MsgGamesList = "games.list"
MsgGamePass = "game.pass"
MsgGameExchange = "game.exchange"
MsgGameResign = "game.resign"
MsgGameHint = "game.hint"
MsgGameEvaluate = "game.evaluate"
MsgGameCheckWord = "game.check_word"
MsgGameComplaint = "game.complaint"
MsgGameHistory = "game.history"
MsgChatList = "chat.list"
MsgChatNudge = "chat.nudge"
MsgChatRead = "chat.read"
MsgDraftGet = "draft.get"
MsgDraftSave = "draft.save"
MsgGameHide = "game.hide"
MsgFeedbackSubmit = "feedback.submit"
MsgFeedbackGet = "feedback.get"
MsgFeedbackUnread = "feedback.unread"
)
// Request is one decoded Execute call.
@@ -101,6 +102,7 @@ func NewRegistry(backend *backendclient.Client, tg TelegramValidator, opts ...Op
r.ops[MsgAuthGuest] = Op{Handler: authGuestHandler(backend)}
r.ops[MsgAuthEmailReq] = Op{Handler: authEmailRequestHandler(backend), Email: true}
r.ops[MsgAuthEmailLogin] = Op{Handler: authEmailLoginHandler(backend), Email: true}
r.ops[MsgAuthEmailConfirmLink] = Op{Handler: authEmailConfirmLinkHandler(backend)}
r.ops[MsgProfileGet] = Op{Handler: profileHandler(backend), Auth: true}
r.ops[MsgBlockStatus] = Op{Handler: blockStatusHandler(backend), Auth: true}
r.ops[MsgGameSubmitPlay] = Op{Handler: submitPlayHandler(backend), Auth: true}
@@ -238,7 +240,7 @@ func authGuestHandler(backend *backendclient.Client) Handler {
func authEmailRequestHandler(backend *backendclient.Client) Handler {
return func(ctx context.Context, req Request) ([]byte, error) {
in := fb.GetRootAsEmailRequestRequest(req.Payload, 0)
if err := backend.EmailRequest(ctx, string(in.Email()), string(in.BrowserTz())); err != nil {
if err := backend.EmailRequest(ctx, string(in.Email()), string(in.BrowserTz()), string(in.Language())); err != nil {
return nil, err
}
return encodeAck(true), nil
@@ -256,6 +258,17 @@ func authEmailLoginHandler(backend *backendclient.Client) Handler {
}
}
func authEmailConfirmLinkHandler(backend *backendclient.Client) Handler {
return func(ctx context.Context, req Request) ([]byte, error) {
in := fb.GetRootAsEmailConfirmLinkRequest(req.Payload, 0)
res, err := backend.EmailConfirmLink(ctx, string(in.Token()))
if err != nil {
return nil, err
}
return encodeConfirmLinkResult(res), nil
}
}
func profileHandler(backend *backendclient.Client) Handler {
return func(ctx context.Context, req Request) ([]byte, error) {
p, err := backend.Profile(ctx, req.UserID)