package handlers import ( "strings" "time" "github.com/gofiber/fiber/v2" "github.com/buhapp/backend/internal/audit" "github.com/buhapp/backend/internal/auth" "github.com/buhapp/backend/internal/consent" "github.com/buhapp/backend/internal/telegram" "github.com/buhapp/backend/internal/users" ) // TGHandler — обработчики для Telegram WebApp type TGHandler struct { Bot *telegram.Bot Users *users.Repo Auth *auth.Service Consent *consent.Repo Audit *audit.Repo WebhookSecret string } func NewTGHandler(bot *telegram.Bot, u *users.Repo, a *auth.Service, c *consent.Repo, au *audit.Repo, webhookSecret string) *TGHandler { return &TGHandler{Bot: bot, Users: u, Auth: a, Consent: c, Audit: au, WebhookSecret: webhookSecret} } type tgAuthRequest struct { InitData string `json:"init_data"` } // TGAuth — логин/регистрация через Telegram WebApp initData func (h *TGHandler) TGAuth(c *fiber.Ctx) error { var req tgAuthRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid JSON"}) } if h.Bot == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "telegram not configured"}) } // Проверяем подпись tgData, err := telegram.ValidateInitData(h.Bot.Token, req.InitData, 5*time.Minute) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid init data: " + err.Error()}) } if tgData.User == nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no user in init data"}) } tgID := tgData.User.ID uid := telegram.UserIDFromTelegram(tgID) // Получаем или создаём пользователя u, err := h.Users.GetByTelegramID(c.UserContext(), tgID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "db error"}) } if u == nil { // Регистрируем нового name := strings.TrimSpace(tgData.User.FirstName) if tgData.User.LastName != "" { name += " " + tgData.User.LastName } if name == "" { name = "User" } u = &users.User{ ID: uid, Name: name, TgUsername: tgData.User.Username, IsVerified: true, // подтверждён через Telegram CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.Users.CreateWithTelegramID(c.UserContext(), u, tgID); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "create user failed: " + err.Error()}) } // аудит _ = h.Audit.Log(c.UserContext(), &audit.Event{ UserID: &uid, Action: "user.register_telegram", IP: c.IP(), UserAgent: c.Get("User-Agent"), }) } else { // обновить last_seen_at _ = h.Users.TouchLastSeen(c.UserContext(), u.ID) } // Выпускаем JWT tokens, err := h.Auth.Generate(u.ID) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "token generation failed"}) } return c.JSON(fiber.Map{ "user": u, "tokens": tokens, "tg": fiber.Map{ "id": tgData.User.ID, "username": tgData.User.Username, "first_name": tgData.User.FirstName, "photo_url": tgData.User.PhotoURL, }, }) } // BotWebhook — webhook от Telegram. Проверяем Secret Token. func (h *TGHandler) BotWebhook(c *fiber.Ctx) error { // Проверка секрета (если задан) if h.WebhookSecret != "" { if c.Get("X-Telegram-Bot-Api-Secret-Token") != h.WebhookSecret { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid secret"}) } } var u telegram.Update if err := c.BodyParser(&u); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid"}) } h.handleUpdate(c.UserContext(), u) return c.JSON(fiber.Map{"ok": true}) } // ProcessUpdate — обработчик апдейтов (вызывается из bot loop) func (h *TGHandler) ProcessUpdate(u telegram.Update) { h.handleUpdate(nil, u) } func (h *TGHandler) handleUpdate(ctx interface{}, u telegram.Update) { if u.Message != nil { h.handleMessage(u.Message) } if u.Callback != nil { h.handleCallback(u.Callback) } } func (h *TGHandler) handleMessage(m *telegram.Message) { if m.Text == "" || m.Chat == nil { return } switch m.Text { case "/start": name := "" if m.From != nil { name = m.From.FirstName } h.Bot.SendMessage(m.Chat.ID, "Привет"+ifNonEmpty(name, ", "+name)+"!\n\nBuhApp — приложение для поиска компании.\n\nНажми кнопку ниже, чтобы открыть:", func(opts *telegram.MessageOptions) { opts.ParseMode = "HTML" opts.ReplyMarkup = telegram.InlineKeyboard([]telegram.InlineButton{ {Text: "🚀 Открыть BuhApp", WebApp: &telegram.WebAppInfo{URL: "https://buhapp.mygoodservice.ru"}}, }) }, ) } } func (h *TGHandler) handleCallback(cb *telegram.Callback) { _ = h.Bot.AnswerCallback(cb.ID, "Открывай Mini App!") } func ifNonEmpty(s, prefix string) string { if s == "" { return "" } return prefix }