From f0b6faa6934f6e4b2bbb3a553c7f40af36a4daba Mon Sep 17 00:00:00 2001 From: Vadym Date: Mon, 10 Jun 2024 21:21:21 +0300 Subject: [PATCH] feat: improved user acceptance flow (#59) * feat: improved user acceptance flow * fix: linter errors --------- Co-authored-by: hrvadl --- .golangci.yaml | 12 ------- .../callback/callbackdata/decisions.go | 8 ++--- .../handler/callback/callbackdata/parser.go | 32 +++++++++++++---- internal/handler/callback/handler.go | 8 ++--- internal/handler/join/handler.go | 34 ++++++++++++------- internal/messages/terms.go | 3 -- internal/module/gcp/secrets/secrets.go | 5 ++- 7 files changed, 59 insertions(+), 43 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index e813d0e..19ad8f9 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -42,18 +42,6 @@ linters: - wrapcheck # Checks that errors returned from external packages are wrapped - wsl # Whitespace Linter - Forces you to use empty lines! - # Disabled because deprecated - - deadcode # Finds unused code - - exhaustivestruct # Checks if all struct's fields are initialized - - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes - - ifshort # Checks that your code uses short syntax for if-statements whenever possible - - interfacer # Linter that suggests narrower interface types - - maligned # Tool to detect Go structs that would take less memory if their fields were sorted - - nosnakecase # nosnakecase is a linter that detects snake case of variable naming and function name. - - scopelint # Scopelint checks for unpinned variables in go programs - - structcheck # Finds unused struct fields - - varcheck # Finds unused global variables and constants - # To see a list of enabled/disabled by current configuration linters: # golangci-lint linters diff --git a/internal/handler/callback/callbackdata/decisions.go b/internal/handler/callback/callbackdata/decisions.go index 06a63c9..5b9bf3c 100644 --- a/internal/handler/callback/callbackdata/decisions.go +++ b/internal/handler/callback/callbackdata/decisions.go @@ -7,10 +7,10 @@ const ( DeclineDecision = "decline" ) -func NewAgreeWithGroupID(groupdID int64) string { - return fmt.Sprintf("%s_%v", AgreeDecision, groupdID) +func NewAgreeWithGroupID(groupdID int64, msgID int) string { + return fmt.Sprintf("%s_%d_%d", AgreeDecision, groupdID, msgID) } -func NewDeclineWithGroupID(groupdID int64) string { - return fmt.Sprintf("%s_%v", DeclineDecision, groupdID) +func NewDeclineWithGroupID(groupdID int64, msgID int) string { + return fmt.Sprintf("%s_%d_%d", DeclineDecision, groupdID, msgID) } diff --git a/internal/handler/callback/callbackdata/parser.go b/internal/handler/callback/callbackdata/parser.go index 680ac38..13f3619 100644 --- a/internal/handler/callback/callbackdata/parser.go +++ b/internal/handler/callback/callbackdata/parser.go @@ -1,31 +1,49 @@ package callbackdata import ( + "errors" "fmt" "strconv" "strings" ) type Payload struct { - Decision string - GroupID int64 + Decision string + GroupID int64 + MessageID int } +var ( + ErrInvalidCallbackData = errors.New("invalid callback query data token") + ErrInvalidGroupID = errors.New("invalid groupID in callback query data") + ErrInvalidMessageID = errors.New("invalid messageID in callback query data") + ErrInvalidDecision = errors.New("invalid terms of use decision") +) + func Parse(data string) (*Payload, error) { splits := strings.Split(data, "_") - if len(splits) != 2 { //nolint:gomnd,mnd - return nil, fmt.Errorf("invalid callback query data token: %v", splits) + if len(splits) != 3 { //nolint:gomnd,mnd + return nil, fmt.Errorf("%w: %v", ErrInvalidCallbackData, splits) } groupID, err := strconv.ParseInt(splits[1], 10, 64) if err != nil { - return nil, fmt.Errorf("invalid groupID in callback query data: %s", splits[1]) + return nil, fmt.Errorf("%w: %s", ErrInvalidGroupID, splits[1]) + } + + messageID, err := strconv.Atoi(splits[2]) + if err != nil { + return nil, fmt.Errorf("%w: %s", ErrInvalidMessageID, splits[1]) } decision := splits[0] if decision != AgreeDecision && decision != DeclineDecision { - return nil, fmt.Errorf("invalid callback data for terms of use decision: %v", decision) + return nil, fmt.Errorf("%w: %v", ErrInvalidDecision, decision) } - return &Payload{decision, groupID}, nil + return &Payload{ + Decision: decision, + GroupID: groupID, + MessageID: messageID, + }, nil } diff --git a/internal/handler/callback/handler.go b/internal/handler/callback/handler.go index ad20a06..0778225 100644 --- a/internal/handler/callback/handler.go +++ b/internal/handler/callback/handler.go @@ -68,10 +68,10 @@ func (h *handler) callbackQuery(ctx context.Context, bot *telego.Bot, query tele msg = fmt.Sprintf(messages.Decline, viper.GetString("admin-username")) } - _, err = bot.SendMessage(&telego.SendMessageParams{ - ChatID: tu.ID(query.From.ID), - Text: msg, - ReplyMarkup: tu.ReplyKeyboardRemove(), + _, err = bot.EditMessageText(&telego.EditMessageTextParams{ + MessageID: data.MessageID, + ChatID: tu.ID(query.From.ID), + Text: msg, }) if err != nil { log.Error("Sending decision message failed", slog.Any("error", err)) diff --git a/internal/handler/join/handler.go b/internal/handler/join/handler.go index def5117..fbff10a 100644 --- a/internal/handler/join/handler.go +++ b/internal/handler/join/handler.go @@ -70,30 +70,40 @@ func (h *handler) chatJoinRequest(ctx context.Context, bot *telego.Bot, request return } + _, err := bot.SendMessage(&telego.SendMessageParams{ + ChatID: tu.ID(request.From.ID), + ParseMode: telego.ModeHTML, + Text: messages.JoinHeader + messages.Rules, + }) + if err != nil { + log.Error("Sending TermsOfUse and Rules message failed", slog.Any("error", err)) + } + + msg := tu.Message(tu.ID(request.From.ID), messages.JoinFooter) + toEdit, err := bot.SendMessage(msg) + if err != nil { + log.Error("Sending terms of use failed", slog.Any("error", err)) + } + k := tu.InlineKeyboard( tu.InlineKeyboardRow( telego.InlineKeyboardButton{ Text: AgreeText, - CallbackData: callbackdata.NewAgreeWithGroupID(request.Chat.ID), + CallbackData: callbackdata.NewAgreeWithGroupID(request.Chat.ID, toEdit.MessageID), }, telego.InlineKeyboardButton{ Text: DontAgreeText, - CallbackData: callbackdata.NewDeclineWithGroupID(request.Chat.ID), + CallbackData: callbackdata.NewDeclineWithGroupID(request.Chat.ID, toEdit.MessageID), }, ), ) - _, err := bot.SendMessage(&telego.SendMessageParams{ - ChatID: tu.ID(request.From.ID), - ParseMode: telego.ModeHTML, - Text: messages.JoinHeader + messages.Rules, + _, err = bot.EditMessageReplyMarkup(&telego.EditMessageReplyMarkupParams{ + ReplyMarkup: k, + ChatID: tu.ID(request.From.ID), + MessageID: toEdit.MessageID, }) if err != nil { - log.Error("Sending TermsOfUse and Rules message failed", slog.Any("error", err)) - } - - msg := tu.Message(tu.ID(request.From.ID), messages.JoinFooter).WithReplyMarkup(k).WithProtectContent() - if _, err := bot.SendMessage(msg); err != nil { - log.Error("Sending terms of use failed", slog.Any("error", err)) + log.Error("Editing callback query failed", slog.Any("error", err)) } } diff --git a/internal/messages/terms.go b/internal/messages/terms.go index 2cd9ced..ff97128 100644 --- a/internal/messages/terms.go +++ b/internal/messages/terms.go @@ -4,10 +4,7 @@ const JoinHeader = ` Вітаємо! Ви подали запит на вступ до групи GolangUA. ` -//nolint:lll const JoinFooter = ` -Перед тим як прийняти рішення, будь ласка, привітайтесь з ботом, написавши довільне повідомлення. Після цього ви зможете натиснути на потрібну кнопну та отримати результат вашого запиту. - Приймаючи запрошення в цю групу ви автоматично: 1. Засуджуєте війну рф проти України. 2. Не визнаєте тимчасовано окупованії українські території субʼєктом рф. diff --git a/internal/module/gcp/secrets/secrets.go b/internal/module/gcp/secrets/secrets.go index 35abcf4..b641920 100644 --- a/internal/module/gcp/secrets/secrets.go +++ b/internal/module/gcp/secrets/secrets.go @@ -3,6 +3,7 @@ package secrets import ( "context" "encoding/base64" + "errors" "fmt" "google.golang.org/api/secretmanager/v1" @@ -14,6 +15,8 @@ type Client struct { *secretmanager.Service } +var ErrSecretAccessRequest = errors.New("secret access request failed") + func NewClient(ctx context.Context) (*Client, error) { client, err := secretmanager.NewService(ctx) if err != nil { @@ -31,7 +34,7 @@ func (c Client) GetSecretValue(ctx context.Context, name string) (string, error) return "", fmt.Errorf("secret access request: %w", err) } if resp.HTTPStatusCode != 200 { //nolint:gomnd,mnd - return "", fmt.Errorf("secret access request: code=%d, data=%v", resp.HTTPStatusCode, resp.Payload.Data) + return "", fmt.Errorf("%w: code=%d, data=%v", ErrSecretAccessRequest, resp.HTTPStatusCode, resp.Payload.Data) } decoded, err := base64.StdEncoding.DecodeString(resp.Payload.Data)