- @Tags 由功能维度改为 public/user/admin 权限维度 - main.go 增加全局 tag 声明与权限说明(需放在 @securitydefinitions 之前,否则会被解析器吞掉) - 重新生成 docs/,公开 4 个、需登录 7 个、管理员 11 个接口
251 lines
8.5 KiB
Go
251 lines
8.5 KiB
Go
// Package usergroup 提供用户组接口。
|
|
package usergroup
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"rill/internal/httpx"
|
|
"rill/internal/model"
|
|
)
|
|
|
|
// Request 创建/更新用户组请求。
|
|
type Request struct {
|
|
Name string `json:"name" binding:"required,max=50" example:"Operations"`
|
|
Description string `json:"description" binding:"max=255" example:"Handles daily operations"`
|
|
}
|
|
|
|
// ListResponse 用户组分页列表响应。
|
|
type ListResponse 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 List user groups ordered by id ASC. page starts at 1; page_size is 1-100, default 20.
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Param page query int false "Page number, default 1" example(1)
|
|
// @Param page_size query int false "Page size, default 20, max 100" example(20)
|
|
// @Success 200 {object} usergroup.ListResponse
|
|
// @Failure 500 {object} httpx.ErrorResponse
|
|
// @Security BearerAuth
|
|
// @Failure 401 {object} httpx.ErrorResponse "unauthorized or session expired"
|
|
// @Failure 403 {object} httpx.ErrorResponse "admin permission required or account disabled"
|
|
// @Router /user-groups [get]
|
|
func List(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
page, pageSize := httpx.ParsePagination(c)
|
|
ctx := c.Request.Context()
|
|
|
|
var total int64
|
|
if err := db.WithContext(ctx).Model(&model.UserGroup{}).Count(&total).Error; err != nil {
|
|
httpx.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 {
|
|
httpx.RespondDBError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, ListResponse{
|
|
Items: groups,
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
})
|
|
}
|
|
}
|
|
|
|
// @Summary Create a user group
|
|
// @Description Create a user group. name is required and unique (max 50 chars); description is optional (max 255 chars); id is assigned by the server.
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param group body usergroup.Request true "User group payload"
|
|
// @Success 201 {object} model.UserGroup
|
|
// @Failure 400 {object} httpx.ErrorResponse "invalid request"
|
|
// @Failure 409 {object} httpx.ErrorResponse "user group name already exists"
|
|
// @Failure 500 {object} httpx.ErrorResponse
|
|
// @Security BearerAuth
|
|
// @Failure 401 {object} httpx.ErrorResponse "unauthorized or session expired"
|
|
// @Failure 403 {object} httpx.ErrorResponse "admin permission required or account disabled"
|
|
// @Router /user-groups [post]
|
|
func Create(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var req Request
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, httpx.ErrorResponse{Error: "invalid request: " + 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 {
|
|
httpx.RespondDuplicateOrDBError(c, err, "user group name already exists")
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, group)
|
|
}
|
|
}
|
|
|
|
// @Summary Get a user group
|
|
// @Description Get a user group by id; id 0 is the built-in admin group.
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Param id path int true "User group ID" example(1)
|
|
// @Success 200 {object} model.UserGroup
|
|
// @Failure 400 {object} httpx.ErrorResponse "invalid id"
|
|
// @Failure 404 {object} httpx.ErrorResponse "record not found"
|
|
// @Failure 500 {object} httpx.ErrorResponse
|
|
// @Security BearerAuth
|
|
// @Failure 401 {object} httpx.ErrorResponse "unauthorized or session expired"
|
|
// @Failure 403 {object} httpx.ErrorResponse "admin permission required or account disabled"
|
|
// @Router /user-groups/{id} [get]
|
|
func Get(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 {
|
|
httpx.RespondGetError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, group)
|
|
}
|
|
}
|
|
|
|
// @Summary Update a user group
|
|
// @Description Update a user group's name and description; name is required and unique.
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "User group ID" example(1)
|
|
// @Param group body usergroup.Request true "User group payload"
|
|
// @Success 200 {object} model.UserGroup
|
|
// @Failure 400 {object} httpx.ErrorResponse "invalid request or id"
|
|
// @Failure 404 {object} httpx.ErrorResponse "record not found"
|
|
// @Failure 409 {object} httpx.ErrorResponse "user group name already exists"
|
|
// @Failure 500 {object} httpx.ErrorResponse
|
|
// @Security BearerAuth
|
|
// @Failure 401 {object} httpx.ErrorResponse "unauthorized or session expired"
|
|
// @Failure 403 {object} httpx.ErrorResponse "admin permission required or account disabled"
|
|
// @Router /user-groups/{id} [put]
|
|
func Update(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id, ok := parseGroupID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req Request
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, httpx.ErrorResponse{Error: "invalid request: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
var group model.UserGroup
|
|
if err := db.WithContext(ctx).First(&group, id).Error; err != nil {
|
|
httpx.RespondGetError(c, err)
|
|
return
|
|
}
|
|
|
|
group.Name = req.Name
|
|
group.Description = req.Description
|
|
if err := db.WithContext(ctx).Save(&group).Error; err != nil {
|
|
httpx.RespondDuplicateOrDBError(c, err, "user group name already exists")
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, group)
|
|
}
|
|
}
|
|
|
|
// @Summary Delete a user group
|
|
// @Description Delete a user group by id; returns 204 with no body on success. Returns 409 for system groups or when the group still has members.
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Param id path int true "User group ID" example(2)
|
|
// @Success 204 "Deleted"
|
|
// @Failure 400 {object} httpx.ErrorResponse "invalid id"
|
|
// @Failure 404 {object} httpx.ErrorResponse "record not found"
|
|
// @Failure 409 {object} httpx.ErrorResponse "system group cannot be deleted or group still has members"
|
|
// @Failure 500 {object} httpx.ErrorResponse
|
|
// @Security BearerAuth
|
|
// @Failure 401 {object} httpx.ErrorResponse "unauthorized or session expired"
|
|
// @Failure 403 {object} httpx.ErrorResponse "admin permission required or account disabled"
|
|
// @Router /user-groups/{id} [delete]
|
|
func Delete(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 {
|
|
httpx.RespondGetError(c, err)
|
|
return
|
|
}
|
|
if group.IsSystem {
|
|
c.JSON(http.StatusConflict, httpx.ErrorResponse{Error: "system group cannot be deleted"})
|
|
return
|
|
}
|
|
|
|
var members int64
|
|
if err := db.WithContext(ctx).Model(&model.UserGroupMember{}).
|
|
Where("user_group_id = ?", id).
|
|
Count(&members).Error; err != nil {
|
|
httpx.RespondDBError(c, err)
|
|
return
|
|
}
|
|
if members > 0 {
|
|
c.JSON(http.StatusConflict, httpx.ErrorResponse{Error: "user group still has members"})
|
|
return
|
|
}
|
|
|
|
if err := db.WithContext(ctx).Delete(&group).Error; err != nil {
|
|
httpx.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, httpx.ErrorResponse{Error: "invalid id"})
|
|
return 0, false
|
|
}
|
|
return uint(id), true
|
|
}
|