Files
rill/internal/api/user_groups.go
T
2026-09-19 17:26:07 +08:00

237 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"errors"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"rill/internal/model"
)
// UserGroupRequest 创建/更新用户组请求。
type UserGroupRequest struct {
Name string `json:"name" binding:"required,max=50" example:"运营组"`
Description string `json:"description" binding:"max=255" example:"负责日常运营"`
}
// UserGroupListResponse 用户组分页列表响应。
type UserGroupListResponse struct {
Items []model.UserGroup `json:"items"`
Total int64 `json:"total" example:"42"`
Page int `json:"page" example:"1"`
PageSize int `json:"page_size" example:"20"`
}
// @Summary List user groups
// @Description 分页查询用户组列表,按 id 升序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
// @Tags user-groups
// @Produce json
// @Param page query int false "页码,默认 1" example(1)
// @Param page_size query int false "每页数量,默认 20,最大 100" example(20)
// @Success 200 {object} api.UserGroupListResponse
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups [get]
func listUserGroups(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
page, pageSize := parsePagination(c)
ctx := c.Request.Context()
var total int64
if err := db.WithContext(ctx).Model(&model.UserGroup{}).Count(&total).Error; err != nil {
respondDBError(c, err)
return
}
var groups []model.UserGroup
if err := db.WithContext(ctx).
Order("id ASC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&groups).Error; err != nil {
respondDBError(c, err)
return
}
c.JSON(http.StatusOK, UserGroupListResponse{
Items: groups,
Total: total,
Page: page,
PageSize: pageSize,
})
}
}
// @Summary Create a user group
// @Description 创建用户组。name 必填且唯一(最长 50 字符),description 可选(最长 255 字符);id 由服务端分配。
// @Tags user-groups
// @Accept json
// @Produce json
// @Param group body api.UserGroupRequest true "用户组信息"
// @Success 201 {object} model.UserGroup
// @Failure 400 {object} api.ErrorResponse "参数无效"
// @Failure 409 {object} api.ErrorResponse "用户组名称已存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups [post]
func createUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req UserGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
ctx := c.Request.Context()
var group model.UserGroup
err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var maxID uint
if err := tx.Model(&model.UserGroup{}).Select("COALESCE(MAX(id), 0)").Scan(&maxID).Error; err != nil {
return err
}
group = model.UserGroup{
ID: maxID + 1,
Name: req.Name,
Description: req.Description,
}
return tx.Create(&group).Error
})
if err != nil {
respondDuplicateOrDBError(c, err, "用户组名称已存在")
return
}
c.JSON(http.StatusCreated, group)
}
}
// @Summary Get a user group
// @Description 按 id 查询单个用户组,id 0 为内置 admin 组。
// @Tags user-groups
// @Produce json
// @Param id path int true "用户组 ID" example(1)
// @Success 200 {object} model.UserGroup
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups/{id} [get]
func getUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseGroupID(c)
if !ok {
return
}
var group model.UserGroup
if err := db.WithContext(c.Request.Context()).First(&group, id).Error; err != nil {
respondGetError(c, err)
return
}
c.JSON(http.StatusOK, group)
}
}
// @Summary Update a user group
// @Description 更新用户组的 name 与 descriptionname 必填且唯一。
// @Tags user-groups
// @Accept json
// @Produce json
// @Param id path int true "用户组 ID" example(1)
// @Param group body api.UserGroupRequest true "用户组信息"
// @Success 200 {object} model.UserGroup
// @Failure 400 {object} api.ErrorResponse "参数无效或 id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 409 {object} api.ErrorResponse "用户组名称已存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups/{id} [put]
func updateUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseGroupID(c)
if !ok {
return
}
var req UserGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
ctx := c.Request.Context()
var group model.UserGroup
if err := db.WithContext(ctx).First(&group, id).Error; err != nil {
respondGetError(c, err)
return
}
group.Name = req.Name
group.Description = req.Description
if err := db.WithContext(ctx).Save(&group).Error; err != nil {
respondDuplicateOrDBError(c, err, "用户组名称已存在")
return
}
c.JSON(http.StatusOK, group)
}
}
// @Summary Delete a user group
// @Description 按 id 删除用户组,成功时返回 204 且无响应体。系统内置组或组内仍有用户时返回 409。
// @Tags user-groups
// @Produce json
// @Param id path int true "用户组 ID" example(2)
// @Success 204 "删除成功"
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 409 {object} api.ErrorResponse "系统内置组不可删除或用户组内仍有用户"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups/{id} [delete]
func deleteUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseGroupID(c)
if !ok {
return
}
ctx := c.Request.Context()
var group model.UserGroup
if err := db.WithContext(ctx).First(&group, id).Error; err != nil {
respondGetError(c, err)
return
}
if group.IsSystem {
c.JSON(http.StatusConflict, ErrorResponse{Error: "系统内置组不可删除"})
return
}
var members int64
if err := db.WithContext(ctx).Table(userGroupMembersTable).
Where("user_group_id = ?", id).
Count(&members).Error; err != nil {
respondDBError(c, err)
return
}
if members > 0 {
c.JSON(http.StatusConflict, ErrorResponse{Error: "用户组内仍有用户,无法删除"})
return
}
if err := db.WithContext(ctx).Delete(&group).Error; err != nil {
respondDBError(c, err)
return
}
c.Status(http.StatusNoContent)
}
}
// parseGroupID 解析用户组 id,允许内置组 id 0。
func parseGroupID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "id 无效"})
return 0, false
}
return uint(id), true
}
var errGroupsNotFound = errors.New("用户组不存在")