package api import ( "fmt" "net/http" "time" "github.com/pulse-lets-go/internal/models" "github.com/pulse-lets-go/internal/util" "golang.org/x/crypto/bcrypt" ) // handleGetUsers — GET /api/users — список пользователей. func (a *API) handleGetUsers(w http.ResponseWriter, r *http.Request) { users, err := a.configManager.ReadUsers() if err != nil { writeError(w, http.StatusInternalServerError, "ошибка чтения пользователей") return } // Не возвращаем хэши паролей type safeUser struct { ID string `json:"id"` Username string `json:"username"` Role models.UserRole `json:"role"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } result := make([]safeUser, len(users)) for i, u := range users { result[i] = safeUser{ ID: u.ID, Username: u.Username, Role: u.Role, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, } } writeJSON(w, http.StatusOK, result) } // handleCreateUser — POST /api/users — создание пользователя. func (a *API) handleCreateUser(w http.ResponseWriter, r *http.Request) { var req models.CreateUserRequest if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "некорректный JSON") return } if req.Username == "" || req.Password == "" { writeError(w, http.StatusBadRequest, "имя и пароль обязательны") return } if req.Role != models.RoleAdmin && req.Role != models.RoleViewer { writeError(w, http.StatusBadRequest, "роль должна быть admin или viewer") return } users, err := a.configManager.ReadUsers() if err != nil { writeError(w, http.StatusInternalServerError, "ошибка чтения пользователей") return } // Проверка уникальности имени for _, u := range users { if u.Username == req.Username { writeError(w, http.StatusConflict, "пользователь с таким именем уже существует") return } } hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { writeError(w, http.StatusInternalServerError, "ошибка хеширования пароля") return } now := time.Now().UTC() user := models.User{ ID: generateID("user"), Username: req.Username, PasswordHash: string(hash), Role: req.Role, CreatedAt: now, UpdatedAt: now, } users = append(users, user) if err := a.configManager.SaveUsers(users); err != nil { writeError(w, http.StatusInternalServerError, "ошибка сохранения пользователя") return } writeJSON(w, http.StatusCreated, map[string]string{ "id": user.ID, "username": user.Username, "role": string(user.Role), }) } // handleUpdateUser — PUT /api/users/{id} — обновление пользователя. func (a *API) handleUpdateUser(w http.ResponseWriter, r *http.Request) { userID := r.PathValue("id") if userID == "" { writeError(w, http.StatusBadRequest, "не указан id пользователя") return } var req models.UpdateUserRequest if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "некорректный JSON") return } users, err := a.configManager.ReadUsers() if err != nil { writeError(w, http.StatusInternalServerError, "ошибка чтения пользователей") return } idx := findUserIndex(users, userID) if idx < 0 { writeError(w, http.StatusNotFound, "пользователь не найден") return } u := &users[idx] if req.Username != nil { // Проверка уникальности for i, other := range users { if other.Username == *req.Username && i != idx { writeError(w, http.StatusConflict, "пользователь с таким именем уже существует") return } } u.Username = *req.Username } if req.Password != nil { hash, _ := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost) u.PasswordHash = string(hash) } if req.Role != nil { if *req.Role != models.RoleAdmin && *req.Role != models.RoleViewer { writeError(w, http.StatusBadRequest, "роль должна быть admin или viewer") return } // Защита первичного администратора от понижения роли if userID == "user-01" && *req.Role != models.RoleAdmin { writeError(w, http.StatusConflict, "роль первичного администратора нельзя изменить") return } u.Role = *req.Role } u.UpdatedAt = time.Now().UTC() if err := a.configManager.SaveUsers(users); err != nil { writeError(w, http.StatusInternalServerError, "ошибка сохранения пользователя") return } writeJSON(w, http.StatusOK, map[string]string{ "id": u.ID, "username": u.Username, "role": string(u.Role), }) } // handleDeleteUser — DELETE /api/users/{id} — удаление пользователя. func (a *API) handleDeleteUser(w http.ResponseWriter, r *http.Request) { userID := r.PathValue("id") if userID == "" { writeError(w, http.StatusBadRequest, "не указан id пользователя") return } users, err := a.configManager.ReadUsers() if err != nil { writeError(w, http.StatusInternalServerError, "ошибка чтения пользователей") return } idx := findUserIndex(users, userID) if idx < 0 { writeError(w, http.StatusNotFound, "пользователь не найден") return } // Защита первичного администратора от удаления if userID == "user-01" { writeError(w, http.StatusConflict, "первичного администратора нельзя удалить") return } // Не даём удалить последнего админа adminCount := 0 for _, u := range users { if u.Role == models.RoleAdmin { adminCount++ } } if users[idx].Role == models.RoleAdmin && adminCount <= 1 { writeError(w, http.StatusConflict, "нельзя удалить последнего администратора") return } users = append(users[:idx], users[idx+1:]...) if err := a.configManager.SaveUsers(users); err != nil { writeError(w, http.StatusInternalServerError, "ошибка сохранения пользователей") return } writeJSON(w, http.StatusNoContent, nil) } // findUserIndex находит индекс пользователя по ID. func findUserIndex(users []models.User, id string) int { for i := range users { if users[i].ID == id { return i } } return -1 } // generateID генерирует ID вида "prefix-XXXXX". func generateID(prefix string) string { return fmt.Sprintf("%s-%s", prefix, util.RandomHex(6)) }