package handlers import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" "rmser/internal/domain/account" "rmser/internal/domain/catalog" "rmser/pkg/logger" ) // Notifier - интерфейс для отправки уведомлений (реализуется ботом) type Notifier interface { SendRoleChangeNotification(telegramID int64, serverName string, newRole string) SendRemovalNotification(telegramID int64, serverName string) } type SettingsHandler struct { accountRepo account.Repository catalogRepo catalog.Repository notifier Notifier // Поле для отправки уведомлений } func NewSettingsHandler(accRepo account.Repository, catRepo catalog.Repository) *SettingsHandler { return &SettingsHandler{ accountRepo: accRepo, catalogRepo: catRepo, } } // SetNotifier используется для внедрения зависимости после инициализации func (h *SettingsHandler) SetNotifier(n Notifier) { h.notifier = n } // SettingsResponse - DTO для отдачи настроек type SettingsResponse struct { ID string `json:"id"` Name string `json:"name"` BaseURL string `json:"base_url"` DefaultStoreID *string `json:"default_store_id"` // Nullable RootGroupID *string `json:"root_group_id"` // Nullable AutoConduct bool `json:"auto_conduct"` Role string `json:"role"` // OWNER, ADMIN, OPERATOR } // GetSettings возвращает настройки активного сервера + роль пользователя func (h *SettingsHandler) GetSettings(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) server, err := h.accountRepo.GetActiveServer(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) return } role, err := h.accountRepo.GetUserRole(userID, server.ID) if err != nil { role = account.RoleOperator } resp := SettingsResponse{ ID: server.ID.String(), Name: server.Name, BaseURL: server.BaseURL, AutoConduct: server.AutoProcess, Role: string(role), } if server.DefaultStoreID != nil { s := server.DefaultStoreID.String() resp.DefaultStoreID = &s } if server.RootGroupGUID != nil { s := server.RootGroupGUID.String() resp.RootGroupID = &s } c.JSON(http.StatusOK, resp) } // UpdateSettingsDTO type UpdateSettingsDTO struct { Name string `json:"name"` DefaultStoreID string `json:"default_store_id"` RootGroupID string `json:"root_group_id"` AutoProcess bool `json:"auto_process"` AutoConduct bool `json:"auto_conduct"` } // UpdateSettings сохраняет настройки func (h *SettingsHandler) UpdateSettings(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) var req UpdateSettingsDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } server, err := h.accountRepo.GetActiveServer(userID) if err != nil || server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) return } // ПРОВЕРКА ПРАВ role, err := h.accountRepo.GetUserRole(userID, server.ID) if err != nil { c.JSON(http.StatusForbidden, gin.H{"error": "Access check failed"}) return } if role != account.RoleOwner && role != account.RoleAdmin { c.JSON(http.StatusForbidden, gin.H{"error": "У вас нет прав на изменение настроек сервера (требуется ADMIN или OWNER)"}) return } if req.Name != "" { server.Name = req.Name } if req.AutoConduct { server.AutoProcess = true } else { server.AutoProcess = req.AutoProcess || req.AutoConduct } if req.DefaultStoreID != "" { if uid, err := uuid.Parse(req.DefaultStoreID); err == nil { server.DefaultStoreID = &uid } } else { server.DefaultStoreID = nil } if req.RootGroupID != "" { if uid, err := uuid.Parse(req.RootGroupID); err == nil { server.RootGroupGUID = &uid } } else { server.RootGroupGUID = nil } if err := h.accountRepo.SaveServerSettings(server); err != nil { logger.Log.Error("Failed to save settings", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.GetSettings(c) } // --- Group Tree Logic --- type GroupNode struct { Key string `json:"key"` Value string `json:"value"` Title string `json:"title"` Children []*GroupNode `json:"children"` } func (h *SettingsHandler) GetGroupsTree(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) server, err := h.accountRepo.GetActiveServer(userID) if err != nil || server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) return } groups, err := h.catalogRepo.GetGroups(server.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } tree := buildTree(groups) c.JSON(http.StatusOK, tree) } func buildTree(flat []catalog.Product) []*GroupNode { nodeMap := make(map[uuid.UUID]*GroupNode) for _, g := range flat { nodeMap[g.ID] = &GroupNode{ Key: g.ID.String(), Value: g.ID.String(), Title: g.Name, Children: make([]*GroupNode, 0), } } var roots []*GroupNode for _, g := range flat { node := nodeMap[g.ID] if g.ParentID != nil { if parent, exists := nodeMap[*g.ParentID]; exists { parent.Children = append(parent.Children, node) } else { roots = append(roots, node) } } else { roots = append(roots, node) } } return roots } // --- User Management --- func (h *SettingsHandler) GetServerUsers(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) server, err := h.accountRepo.GetActiveServer(userID) if err != nil || server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) return } myRole, err := h.accountRepo.GetUserRole(userID, server.ID) if err != nil || (myRole != account.RoleOwner && myRole != account.RoleAdmin) { c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) return } users, err := h.accountRepo.GetServerUsers(server.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } type UserDTO struct { UserID uuid.UUID `json:"user_id"` Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` PhotoURL string `json:"photo_url"` Role account.Role `json:"role"` IsMe bool `json:"is_me"` } response := make([]UserDTO, 0, len(users)) for _, u := range users { response = append(response, UserDTO{ UserID: u.UserID, Username: u.User.Username, FirstName: u.User.FirstName, LastName: u.User.LastName, PhotoURL: u.User.PhotoURL, Role: u.Role, IsMe: u.UserID == userID, }) } c.JSON(http.StatusOK, response) } type UpdateUserRoleDTO struct { NewRole string `json:"new_role" binding:"required"` // ADMIN, OPERATOR } func (h *SettingsHandler) UpdateUserRole(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) targetUserID, err := uuid.Parse(c.Param("userId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid target user id"}) return } var req UpdateUserRoleDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } newRole := account.Role(req.NewRole) if newRole != account.RoleAdmin && newRole != account.RoleOperator { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role (allowed: ADMIN, OPERATOR)"}) return } server, err := h.accountRepo.GetActiveServer(userID) if err != nil || server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) return } myRole, _ := h.accountRepo.GetUserRole(userID, server.ID) if myRole != account.RoleOwner { c.JSON(http.StatusForbidden, gin.H{"error": "Only OWNER can change roles"}) return } if userID == targetUserID { c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot change own role"}) return } if err := h.accountRepo.SetUserRole(server.ID, targetUserID, newRole); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // --- УВЕДОМЛЕНИЕ О СМЕНЕ РОЛИ --- if h.notifier != nil { go func() { users, err := h.accountRepo.GetServerUsers(server.ID) if err == nil { for _, u := range users { if u.UserID == targetUserID { h.notifier.SendRoleChangeNotification(u.User.TelegramID, server.Name, string(newRole)) break } } } }() } c.JSON(http.StatusOK, gin.H{"status": "updated"}) } func (h *SettingsHandler) RemoveUser(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) targetUserID, err := uuid.Parse(c.Param("userId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid target user id"}) return } server, err := h.accountRepo.GetActiveServer(userID) if err != nil || server == nil { c.JSON(http.StatusNotFound, gin.H{"error": "No active server"}) return } myRole, _ := h.accountRepo.GetUserRole(userID, server.ID) if myRole != account.RoleOwner && myRole != account.RoleAdmin { c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) return } if userID == targetUserID { c.JSON(http.StatusBadRequest, gin.H{"error": "Use 'leave' function instead"}) return } // Ищем цель в списке, чтобы проверить права и получить TelegramID для уведомления users, _ := h.accountRepo.GetServerUsers(server.ID) var targetTgID int64 var found bool for _, u := range users { if u.UserID == targetUserID { found = true targetTgID = u.User.TelegramID if u.Role == account.RoleOwner { c.JSON(http.StatusForbidden, gin.H{"error": "Cannot remove Owner"}) return } if myRole == account.RoleAdmin && u.Role == account.RoleAdmin { c.JSON(http.StatusForbidden, gin.H{"error": "Admins cannot remove other Admins"}) return } break } } if !found { c.JSON(http.StatusNotFound, gin.H{"error": "Target user not found on server"}) return } if err := h.accountRepo.RemoveUserFromServer(server.ID, targetUserID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // --- УВЕДОМЛЕНИЕ ОБ УДАЛЕНИИ --- if h.notifier != nil && targetTgID != 0 { go h.notifier.SendRemovalNotification(targetTgID, server.Name) } c.JSON(http.StatusOK, gin.H{"status": "removed"}) }