Files
OpenFlare/internal/apps/admin/user/routers.go
T
2026-06-08 20:37:40 +08:00

270 lines
7.8 KiB
Go

/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/linux-do/credit/internal/db"
"github.com/linux-do/credit/internal/db/idgen"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/util"
"gorm.io/gorm"
)
// listUsersRequest 用户列表查询请求
type listUsersRequest struct {
Page int `form:"page" binding:"min=1"`
PageSize int `form:"page_size" binding:"min=1,max=100"`
UserID *uint64 `form:"user_id" binding:"omitempty,gt=0"`
Username string `form:"username"`
}
type user struct {
ID uint64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
AvatarUrl string `json:"avatar_url"`
IsActive bool `json:"is_active"`
IsAdmin bool `json:"is_admin"`
LastLoginAt time.Time `json:"last_login_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// listUsersResponse 用户列表响应
type listUsersResponse struct {
Users []user `json:"users"`
Total int64 `json:"total"`
}
// ListUsers 获取用户列表
// @Summary 获取用户列表
// @Description 分页返回用户列表,支持按用户 ID 和用户名筛选,需要管理员权限
// @Tags admin
// @Produce json
// @Security SessionCookie
// @Param request query listUsersRequest true "查询参数"
// @Success 200 {object} util.ResponseAny{data=user.listUsersResponse} "用户列表"
// @Failure 400 {object} util.ResponseAny "参数错误"
// @Failure 401 {object} util.ResponseAny "未登录"
// @Failure 403 {object} util.ResponseAny "无管理员权限"
// @Failure 500 {object} util.ResponseAny "内部错误"
// @Router /api/v1/admin/users [get]
// ListUsers 获取用户列表
func ListUsers(c *gin.Context) {
var req listUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
var users []user
var total int64
query := db.DB(c.Request.Context()).Table("users")
username := strings.TrimSpace(req.Username)
if req.UserID != nil {
query = query.Where("id = ?", *req.UserID)
}
if username != "" {
query = query.Where("username LIKE ?", username+"%")
}
if err := query.Count(&total).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
offset := (req.Page - 1) * req.PageSize
if err := query.
Select("id, username, nickname, avatar_url, is_active, is_admin, " +
"last_login_at, created_at, updated_at").
Order("id DESC").
Offset(offset).
Limit(req.PageSize).
Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OK(listUsersResponse{
Users: users,
Total: total,
}))
}
// updateUserStatusRequest 更新用户状态请求
type updateUserStatusRequest struct {
IsActive bool `json:"is_active"`
}
// UpdateUserStatus 更新用户状态(启用/禁用)
// @Summary 更新用户状态
// @Description 启用或禁用指定用户,管理员账号无法被禁用,需要管理员权限
// @Tags admin
// @Accept json
// @Produce json
// @Security SessionCookie
// @Param id path int true "用户 ID"
// @Param request body updateUserStatusRequest true "状态参数"
// @Success 200 {object} util.ResponseAny{data=string} "更新成功"
// @Failure 400 {object} util.ResponseAny "参数错误"
// @Failure 401 {object} util.ResponseAny "未登录"
// @Failure 403 {object} util.ResponseAny "无管理员权限或尝试禁用管理员"
// @Failure 404 {object} util.ResponseAny "用户不存在"
// @Failure 500 {object} util.ResponseAny "内部错误"
// @Router /api/v1/admin/users/{id}/status [put]
func UpdateUserStatus(c *gin.Context) {
var req updateUserStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
id := c.Param("id")
var targetUser struct {
ID uint64 `gorm:"column:id"`
IsAdmin bool `gorm:"column:is_admin"`
}
if err := db.DB(c.Request.Context()).
Table("users").
Select("id, is_admin").
Where("id = ?", id).
First(&targetUser).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, util.Err(userNotFound))
return
}
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
if !req.IsActive && targetUser.IsAdmin {
c.JSON(http.StatusForbidden, util.Err(cannotDisable))
return
}
if err := db.DB(c.Request.Context()).
Table("users").
Where("id = ?", id).
Update("is_active", req.IsActive).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(updateUserFailed))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
// createUserRequest 创建用户请求
type createUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=64"`
Password string `json:"password" binding:"required,min=8,max=64"`
Nickname string `json:"nickname" binding:"omitempty,max=64"`
IsActive bool `json:"is_active"`
IsAdmin bool `json:"is_admin"`
}
// CreateUser 创建用户
// @Summary 创建用户
// @Description 创建一个本地密码登录的新用户,需要管理员权限
// @Tags admin
// @Accept json
// @Produce json
// @Security SessionCookie
// @Param request body user.createUserRequest true "创建用户参数"
// @Success 200 {object} util.ResponseAny{data=user.user} "创建成功"
// @Failure 400 {object} util.ResponseAny "参数错误或用户名已存在"
// @Failure 401 {object} util.ResponseAny "未登录"
// @Failure 403 {object} util.ResponseAny "无管理员权限"
// @Failure 500 {object} util.ResponseAny "内部错误"
// @Router /api/v1/admin/users [post]
func CreateUser(c *gin.Context) {
var req createUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
req.Username = strings.TrimSpace(req.Username)
req.Nickname = strings.TrimSpace(req.Nickname)
req.Password = strings.TrimSpace(req.Password)
if req.Username == "" {
c.JSON(http.StatusBadRequest, util.Err(usernameRequired))
return
}
if len(req.Password) < 8 {
c.JSON(http.StatusBadRequest, util.Err(passwordTooShort))
return
}
ctx := c.Request.Context()
var count int64
if err := db.DB(ctx).Table("users").Where("username = ?", req.Username).Count(&count).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
if count > 0 {
c.JSON(http.StatusBadRequest, util.Err(usernameExists))
return
}
newUser := model.User{
ID: idgen.NextUint64ID(),
Username: req.Username,
Nickname: req.Nickname,
IsActive: req.IsActive,
IsAdmin: req.IsAdmin,
LastLoginAt: time.Time{},
}
if newUser.Nickname == "" {
newUser.Nickname = req.Username
}
if err := newUser.SetEncryptedPassword(req.Password); err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
if err := db.DB(ctx).Create(&newUser).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
res := user{
ID: newUser.ID,
Username: newUser.Username,
Nickname: newUser.Nickname,
AvatarUrl: newUser.AvatarUrl,
IsActive: newUser.IsActive,
IsAdmin: newUser.IsAdmin,
LastLoginAt: newUser.LastLoginAt,
CreatedAt: newUser.CreatedAt,
UpdatedAt: newUser.UpdatedAt,
}
c.JSON(http.StatusOK, util.OK(res))
}