0202-финиш перед десктопом

пересчет поправил
редактирование с перепроведением
галка автопроведения работает
рекомендации починил
This commit is contained in:
2026-02-02 13:53:38 +03:00
parent 10882f55c8
commit 88620f3fb6
37 changed files with 1905 additions and 11162 deletions

View File

@@ -202,17 +202,17 @@ func (bot *Bot) registrationMiddleware(next tele.HandlerFunc) tele.HandlerFunc {
func (bot *Bot) handleStartCommand(c tele.Context) error {
payload := c.Message().Payload
// Обработка desktop авторизации
if payload != "" && strings.HasPrefix(payload, "auth_") {
sessionID := strings.TrimPrefix(payload, "auth_")
telegramID := c.Sender().ID
logger.Log.Info("Обработка desktop авторизации",
zap.String("session_id", sessionID),
zap.Int64("telegram_id", telegramID),
)
if err := bot.authService.ConfirmDesktopAuth(sessionID, telegramID); err != nil {
logger.Log.Error("Ошибка подтверждения desktop авторизации",
zap.String("session_id", sessionID),
@@ -221,10 +221,10 @@ func (bot *Bot) handleStartCommand(c tele.Context) error {
)
return c.Send("❌ Ошибка авторизации. Попробуйте снова.", tele.ModeHTML)
}
return c.Send("✅ Авторизация успешна! Вы можете вернуться в приложение.", tele.ModeHTML)
}
if payload != "" && strings.HasPrefix(payload, "invite_") {
return bot.handleInviteLink(c, strings.TrimPrefix(payload, "invite_"))
}
@@ -417,19 +417,70 @@ func (bot *Bot) renderServersMenu(c tele.Context) error {
}
role, _ := bot.accountRepo.GetUserRole(userDB.ID, s.ID)
label := fmt.Sprintf("%s %s (%s)", icon, s.Name, role)
btn := menu.Data(label, "set_server_"+s.ID.String())
btn := menu.Data(label, "srv_menu_"+s.ID.String())
rows = append(rows, menu.Row(btn))
}
btnAdd := menu.Data(" Добавить сервер", "act_add_server")
btnDel := menu.Data("⚙️ Управление / Удаление", "act_del_server_menu")
btnBack := menu.Data("🔙 Назад", "nav_main")
rows = append(rows, menu.Row(btnAdd, btnDel))
rows = append(rows, menu.Row(btnAdd))
rows = append(rows, menu.Row(btnBack))
menu.Inline(rows...)
txt := fmt.Sprintf("<b>🖥 Ваши серверы (%d):</b>\n\nНажмите на сервер, чтобы сделать его активным.", len(servers))
txt := fmt.Sprintf("<b>🖥 Ваши серверы (%d):</b>\n\nНажмите на сервер для управления.", len(servers))
return c.EditOrSend(txt, menu, tele.ModeHTML)
}
// renderServerMenu показывает подменю управления конкретным сервером
func (bot *Bot) renderServerMenu(c tele.Context, serverID uuid.UUID) error {
userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID)
server, err := bot.accountRepo.GetServerByID(serverID)
if err != nil {
return c.Send("Ошибка: сервер не найден")
}
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
activeServer, _ := bot.accountRepo.GetActiveServer(userDB.ID)
isActive := activeServer != nil && activeServer.ID == server.ID
menu := &tele.ReplyMarkup{}
var rows []tele.Row
// Кнопка "Выбрать активным" (доступна всем)
if !isActive {
btnSetActive := menu.Data("✅ Выбрать активным", "srv_set_active_"+server.ID.String())
rows = append(rows, menu.Row(btnSetActive))
} else {
btnActive := menu.Data("🟢 Активный сервер", "noop")
rows = append(rows, menu.Row(btnActive))
}
// Кнопка "Показать URL/Логин" (доступна Admin и Owner)
if role == account.RoleOwner || role == account.RoleAdmin {
btnShowCreds := menu.Data("👁 Показать URL/Логин", "srv_show_creds_"+server.ID.String())
btnInvite := menu.Data("📩 Пригласить сотрудника", fmt.Sprintf("gen_invite_%s", server.ID.String()))
rows = append(rows, menu.Row(btnShowCreds, btnInvite))
}
// Кнопка "Обновить логин-пароль" (только Owner)
if role == account.RoleOwner {
btnUpdateCreds := menu.Data("✏️ Обновить логин-пароль", "srv_update_creds_"+server.ID.String())
rows = append(rows, menu.Row(btnUpdateCreds))
}
// Кнопка "Удалить сервер" (только Owner)
if role == account.RoleOwner {
btnDelete := menu.Data("❌ Удалить сервер", "srv_delete_"+server.ID.String())
rows = append(rows, menu.Row(btnDelete))
}
btnBack := menu.Data("🔙 Назад к списку", "nav_servers")
rows = append(rows, menu.Row(btnBack))
menu.Inline(rows...)
txt := fmt.Sprintf("<b>⚙️ Управление сервером</b>\n\n🏢 <b>Название:</b> %s\n🔗 <b>URL:</b> %s\n👤 <b>Ваша роль:</b> %s",
server.Name, server.BaseURL, role)
return c.EditOrSend(txt, menu, tele.ModeHTML)
}
@@ -536,6 +587,131 @@ func (bot *Bot) handleCallback(c tele.Context) error {
return bot.handleBillingCallbacks(c, data, userDB)
}
// Обработка кнопок подменю сервера
if strings.HasPrefix(data, "srv_menu_") {
serverIDStr := strings.TrimPrefix(data, "srv_menu_")
serverIDStr = strings.TrimSpace(serverIDStr)
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
serverIDStr = serverIDStr[:idx]
}
targetID := parseUUID(serverIDStr)
return bot.renderServerMenu(c, targetID)
}
if strings.HasPrefix(data, "srv_set_active_") {
serverIDStr := strings.TrimPrefix(data, "srv_set_active_")
serverIDStr = strings.TrimSpace(serverIDStr)
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
serverIDStr = serverIDStr[:idx]
}
targetID := parseUUID(serverIDStr)
if err := bot.accountRepo.SetActiveServer(userDB.ID, targetID); err != nil {
logger.Log.Error("Failed to set active server", zap.Error(err))
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: доступ запрещен"})
}
bot.rmsFactory.ClearCacheForUser(userDB.ID)
c.Respond(&tele.CallbackResponse{Text: "✅ Сервер выбран активным"})
return bot.renderServerMenu(c, targetID)
}
if strings.HasPrefix(data, "srv_show_creds_") {
serverIDStr := strings.TrimPrefix(data, "srv_show_creds_")
serverIDStr = strings.TrimSpace(serverIDStr)
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
serverIDStr = serverIDStr[:idx]
}
targetID := parseUUID(serverIDStr)
server, err := bot.accountRepo.GetServerByID(targetID)
if err != nil {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: сервер не найден"})
}
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
if role != account.RoleOwner && role != account.RoleAdmin {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: недостаточно прав"})
}
// Получаем личные креды пользователя через GetServerUsers
serverUsers, err := bot.accountRepo.GetServerUsers(server.ID)
if err != nil {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка получения данных"})
}
var login string
for _, su := range serverUsers {
if su.UserID == userDB.ID {
login = su.Login
break
}
}
if login == "" {
return c.Respond(&tele.CallbackResponse{Text: "У вас нет сохраненных учетных данных"})
}
c.Respond()
return c.Send(fmt.Sprintf("🔑 <b>Учетные данные сервера</b>\n\n🏢 <b>Название:</b> %s\n🔗 <b>URL:</b> %s\n👤 <b>Логин:</b> %s\n🔒 <b>Пароль:</b> ***скрыт***",
server.Name, server.BaseURL, login), tele.ModeHTML)
}
if strings.HasPrefix(data, "srv_update_creds_") {
serverIDStr := strings.TrimPrefix(data, "srv_update_creds_")
serverIDStr = strings.TrimSpace(serverIDStr)
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
serverIDStr = serverIDStr[:idx]
}
targetID := parseUUID(serverIDStr)
server, err := bot.accountRepo.GetServerByID(targetID)
if err != nil {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: сервер не найден"})
}
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
if role != account.RoleOwner {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: только владелец может обновлять учетные данные"})
}
// Сохраняем ID сервера в контексте FSM
bot.fsm.UpdateContext(c.Sender().ID, func(ctx *UserContext) {
ctx.EditingServerID = server.ID
ctx.TempURL = server.BaseURL
})
bot.fsm.SetState(c.Sender().ID, StateUpdateServerLogin)
c.Respond()
return c.EditOrSend("✏️ <b>Обновление учетных данных</b>\n\nВведите новый <b>логин</b> для сервера <b>"+server.Name+"</b>.\n\n(Напишите 'отмена' для выхода)", tele.ModeHTML)
}
if strings.HasPrefix(data, "srv_delete_") {
serverIDStr := strings.TrimPrefix(data, "srv_delete_")
serverIDStr = strings.TrimSpace(serverIDStr)
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
serverIDStr = serverIDStr[:idx]
}
targetID := parseUUID(serverIDStr)
role, err := bot.accountRepo.GetUserRole(userDB.ID, targetID)
if err != nil {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка прав доступа"})
}
if role != account.RoleOwner {
return c.Respond(&tele.CallbackResponse{Text: "Только владелец может удалить сервер"})
}
// Подтверждение удаления
menu := &tele.ReplyMarkup{}
btnYes := menu.Data("✅ Да, удалить", "srv_delete_confirm_"+targetID.String())
btnNo := menu.Data("❌ Отмена", "srv_menu_"+targetID.String())
menu.Inline(menu.Row(btnYes), menu.Row(btnNo))
server, _ := bot.accountRepo.GetServerByID(targetID)
return c.EditOrSend("⚠️ <b>Подтверждение удаления</b>\n\nВы уверены, что хотите удалить сервер <b>"+server.Name+"</b>?\n\nЭто действие необратимо!", menu, tele.ModeHTML)
}
if strings.HasPrefix(data, "srv_delete_confirm_") {
serverIDStr := strings.TrimPrefix(data, "srv_delete_confirm_")
serverIDStr = strings.TrimSpace(serverIDStr)
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
serverIDStr = serverIDStr[:idx]
}
targetID := parseUUID(serverIDStr)
if err := bot.accountRepo.DeleteServer(targetID); err != nil {
return c.Respond(&tele.CallbackResponse{Text: "Ошибка удаления"})
}
bot.rmsFactory.ClearCacheForUser(userDB.ID)
c.Respond(&tele.CallbackResponse{Text: "Сервер удален"})
return bot.renderServersMenu(c)
}
if strings.HasPrefix(data, "set_server_") {
serverIDStr := strings.TrimPrefix(data, "set_server_")
serverIDStr = strings.TrimSpace(serverIDStr)
@@ -799,6 +975,7 @@ func (bot *Bot) handleText(c tele.Context) error {
userID := c.Sender().ID
state := bot.fsm.GetState(userID)
text := strings.TrimSpace(c.Text())
userDB, _ := bot.accountRepo.GetUserByTelegramID(userID)
if bot.maintenanceMode && !bot.isDev(userID) {
return c.Send("Сервис на обслуживании", tele.ModeHTML)
@@ -888,6 +1065,52 @@ func (bot *Bot) handleText(c tele.Context) error {
ctx.BillingTargetURL = text
})
return bot.renderTariffShowcase(c, text)
case StateUpdateServerLogin:
ctx := bot.fsm.GetContext(userID)
if ctx.EditingServerID == uuid.Nil {
bot.fsm.Reset(userID)
return bot.renderMainMenu(c)
}
bot.fsm.UpdateContext(userID, func(uCtx *UserContext) {
uCtx.TempLogin = text
uCtx.State = StateUpdateServerPassword
})
return c.Send("🔑 Введите новый <b>пароль</b>:")
case StateUpdateServerPassword:
password := text
ctx := bot.fsm.GetContext(userID)
if ctx.EditingServerID == uuid.Nil {
bot.fsm.Reset(userID)
return bot.renderMainMenu(c)
}
server, err := bot.accountRepo.GetServerByID(ctx.EditingServerID)
if err != nil {
bot.fsm.Reset(userID)
return c.Send("❌ Ошибка: сервер не найден")
}
// Проверяем новые креды
msg, _ := bot.b.Send(c.Sender(), "⏳ Проверяю подключение...")
tempClient := bot.rmsFactory.CreateClientFromRawCredentials(ctx.TempURL, ctx.TempLogin, password)
if err := tempClient.Auth(); err != nil {
bot.b.Delete(msg)
bot.fsm.Reset(userID)
return c.Send(fmt.Sprintf("❌ Ошибка авторизации: %v\nПроверьте логин/пароль.", err))
}
// Шифруем пароль и сохраняем
encPass, _ := bot.cryptoManager.Encrypt(password)
// Обновляем креды через ConnectServer (он обновит существующую связь)
_, err = bot.accountRepo.ConnectServer(userDB.ID, ctx.TempURL, ctx.TempLogin, encPass, server.Name)
bot.b.Delete(msg)
if err != nil {
bot.fsm.Reset(userID)
return c.Send("❌ Ошибка обновления данных")
}
bot.fsm.Reset(userID)
bot.rmsFactory.ClearCacheForUser(userDB.ID)
c.Send("✅ <b>Учетные данные обновлены!</b>\n\nТеперь вы можете использовать новые логин и пароль для подключения к серверу.", tele.ModeHTML)
return bot.renderServerMenu(c, server.ID)
}
return nil