Some checks are pending
Release Binaries / Build and Release (.exe, amd64, windows, windows-amd64) (push) Waiting to run
Release Binaries / Build and Release (amd64, darwin, darwin-amd64) (push) Waiting to run
Release Binaries / Build and Release (amd64, linux, linux-amd64) (push) Waiting to run
Release Binaries / Build and Release (arm64, darwin, darwin-arm64) (push) Waiting to run
Release Binaries / Build and Release (arm64, linux, linux-arm64) (push) Waiting to run
- Remove Role association field from User model - Remove User association field from Token model - controller/user.go: query Role separately after loading User - controller/token.go: query User and Role with separate DB calls - handler/admin.go: introduce userResp type, build role info manually; batch-load roles in AdminListUsers to avoid N+1 🤖 Generated with [Qoder][https://qoder.com]
272 lines
8.9 KiB
Go
272 lines
8.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/loveuer/nf"
|
|
"github.com/loveuer/nf/nft/log"
|
|
"github.com/loveuer/ushare/internal/model"
|
|
"github.com/loveuer/ushare/internal/pkg/db"
|
|
"github.com/loveuer/ushare/internal/pkg/tool"
|
|
"github.com/spf13/cast"
|
|
)
|
|
|
|
// userResp is the JSON response shape for a user including role info,
|
|
// built manually at the business layer instead of relying on GORM associations.
|
|
type userResp struct {
|
|
ID uint `json:"id"`
|
|
Username string `json:"username"`
|
|
RoleID uint `json:"role_id"`
|
|
Role model.Role `json:"role"`
|
|
Active bool `json:"active"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
func toUserResp(u model.User, r model.Role) userResp {
|
|
return userResp{
|
|
ID: u.ID,
|
|
Username: u.Username,
|
|
RoleID: u.RoleID,
|
|
Role: r,
|
|
Active: u.Active,
|
|
CreatedAt: u.CreatedAt,
|
|
UpdatedAt: u.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func AdminListUsers() nf.HandlerFunc {
|
|
return func(c *nf.Ctx) error {
|
|
var users []model.User
|
|
if err := db.Default.Session().Find(&users).Error; err != nil {
|
|
log.Error("handler.AdminListUsers: %s", err.Error())
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询失败"})
|
|
}
|
|
|
|
// Collect unique role IDs and query them in one shot
|
|
roleIDSet := make(map[uint]struct{})
|
|
for _, u := range users {
|
|
roleIDSet[u.RoleID] = struct{}{}
|
|
}
|
|
roleIDs := make([]uint, 0, len(roleIDSet))
|
|
for id := range roleIDSet {
|
|
roleIDs = append(roleIDs, id)
|
|
}
|
|
|
|
var roles []model.Role
|
|
if err := db.Default.Session().Where("id IN ?", roleIDs).Find(&roles).Error; err != nil {
|
|
log.Error("handler.AdminListUsers: query roles: %s", err.Error())
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询失败"})
|
|
}
|
|
|
|
roleMap := make(map[uint]model.Role, len(roles))
|
|
for _, r := range roles {
|
|
roleMap[r.ID] = r
|
|
}
|
|
|
|
resp := make([]userResp, 0, len(users))
|
|
for _, u := range users {
|
|
resp = append(resp, toUserResp(u, roleMap[u.RoleID]))
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": resp})
|
|
}
|
|
}
|
|
|
|
func AdminCreateUser() nf.HandlerFunc {
|
|
return func(c *nf.Ctx) error {
|
|
type Req struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
RoleID uint `json:"role_id"`
|
|
}
|
|
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "参数错误"})
|
|
}
|
|
|
|
req.Username = strings.TrimSpace(req.Username)
|
|
if req.Username == "" {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "用户名不能为空"})
|
|
}
|
|
if req.Password == "" {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "密码不能为空"})
|
|
}
|
|
if req.RoleID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "角色不能为空"})
|
|
}
|
|
|
|
if err := tool.CheckPassword(req.Password); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": err.Error()})
|
|
}
|
|
|
|
var count int64
|
|
db.Default.Session().Model(&model.User{}).Where("username = ?", req.Username).Count(&count)
|
|
if count > 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "用户名已存在"})
|
|
}
|
|
|
|
var role model.Role
|
|
if err := db.Default.Session().First(&role, req.RoleID).Error; err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的角色"})
|
|
}
|
|
|
|
user := &model.User{
|
|
Username: req.Username,
|
|
Password: tool.NewPassword(req.Password),
|
|
RoleID: req.RoleID,
|
|
Active: true,
|
|
}
|
|
|
|
if err := db.Default.Session().Create(user).Error; err != nil {
|
|
log.Error("handler.AdminCreateUser: %s", err.Error())
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "创建用户失败"})
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": toUserResp(*user, role)})
|
|
}
|
|
}
|
|
|
|
func AdminUpdateUser() nf.HandlerFunc {
|
|
return func(c *nf.Ctx) error {
|
|
type Req struct {
|
|
RoleID *uint `json:"role_id"`
|
|
Active *bool `json:"active"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
id, err := cast.ToUintE(c.Param("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的用户ID"})
|
|
}
|
|
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "参数错误"})
|
|
}
|
|
|
|
session := c.Locals("user").(*model.Session)
|
|
|
|
var user model.User
|
|
if err := db.Default.Session().First(&user, id).Error; err != nil {
|
|
return c.Status(http.StatusNotFound).JSON(map[string]string{"msg": "用户不存在"})
|
|
}
|
|
|
|
var currentRole model.Role
|
|
if err := db.Default.Session().First(¤tRole, user.RoleID).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询角色失败"})
|
|
}
|
|
|
|
updates := map[string]any{}
|
|
|
|
if req.RoleID != nil && *req.RoleID != user.RoleID {
|
|
var newRole model.Role
|
|
if err := db.Default.Session().First(&newRole, *req.RoleID).Error; err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的角色"})
|
|
}
|
|
// If demoting from admin, ensure at least one other active admin remains
|
|
if currentRole.Name == model.RoleAdmin && newRole.Name != model.RoleAdmin {
|
|
var adminCount int64
|
|
db.Default.Session().Model(&model.User{}).
|
|
Where("role_id = ? AND active = ? AND id != ?", user.RoleID, true, id).
|
|
Count(&adminCount)
|
|
if adminCount == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无法更改角色: 系统中至少需要一个管理员"})
|
|
}
|
|
}
|
|
updates["role_id"] = *req.RoleID
|
|
currentRole = newRole
|
|
}
|
|
|
|
if req.Active != nil && *req.Active != user.Active {
|
|
if user.ID == session.UserID && !*req.Active {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "不能禁用自己的账号"})
|
|
}
|
|
if currentRole.Name == model.RoleAdmin && !*req.Active {
|
|
var adminCount int64
|
|
db.Default.Session().Model(&model.User{}).
|
|
Where("role_id = ? AND active = ? AND id != ?", user.RoleID, true, id).
|
|
Count(&adminCount)
|
|
if adminCount == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无法禁用: 系统中至少需要一个启用的管理员"})
|
|
}
|
|
}
|
|
updates["active"] = *req.Active
|
|
}
|
|
|
|
if req.Password != "" {
|
|
if err := tool.CheckPassword(req.Password); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": err.Error()})
|
|
}
|
|
updates["password"] = tool.NewPassword(req.Password)
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "没有需要更新的字段"})
|
|
}
|
|
|
|
if err := db.Default.Session().Model(&user).Updates(updates).Error; err != nil {
|
|
log.Error("handler.AdminUpdateUser: %s", err.Error())
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "更新失败"})
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": toUserResp(user, currentRole)})
|
|
}
|
|
}
|
|
|
|
func AdminDeleteUser() nf.HandlerFunc {
|
|
return func(c *nf.Ctx) error {
|
|
id, err := cast.ToUintE(c.Param("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的用户ID"})
|
|
}
|
|
|
|
session := c.Locals("user").(*model.Session)
|
|
if session.UserID == id {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "不能删除自己的账号"})
|
|
}
|
|
|
|
var user model.User
|
|
if err := db.Default.Session().First(&user, id).Error; err != nil {
|
|
return c.Status(http.StatusNotFound).JSON(map[string]string{"msg": "用户不存在"})
|
|
}
|
|
|
|
// Prevent deleting the last admin: check via role name
|
|
var userRole model.Role
|
|
if err := db.Default.Session().First(&userRole, user.RoleID).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询角色失败"})
|
|
}
|
|
|
|
if userRole.Name == model.RoleAdmin {
|
|
var adminCount int64
|
|
db.Default.Session().Model(&model.User{}).
|
|
Where("role_id = ? AND id != ?", user.RoleID, id).
|
|
Count(&adminCount)
|
|
if adminCount == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无法删除最后一个管理员"})
|
|
}
|
|
}
|
|
|
|
if err := db.Default.Session().Delete(&user).Error; err != nil {
|
|
log.Error("handler.AdminDeleteUser: %s", err.Error())
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "删除失败"})
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": "ok"})
|
|
}
|
|
}
|
|
|
|
func AdminListRoles() nf.HandlerFunc {
|
|
return func(c *nf.Ctx) error {
|
|
var roles []model.Role
|
|
if err := db.Default.Session().Find(&roles).Error; err != nil {
|
|
log.Error("handler.AdminListRoles: %s", err.Error())
|
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询失败"})
|
|
}
|
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": roles})
|
|
}
|
|
}
|