* backend
- model: add `Remark` field (varchar 255, `json:"remark,omitempty"`); AutoMigrate handles schema change automatically
- controller:
* accept `remark` on user create/update endpoints
* hide remark from regular users (`GetSelf`) by zero-ing the field before JSON marshalling
* clarify inline comment explaining the omitempty behaviour
* frontend (React / Semi UI)
- AddUser.js & EditUser.js: add “Remark” input for admins
- UsersTable.js:
* remove standalone “Remark” column
* show remark as a truncated Tag next to username with Tooltip for full text
* import Tooltip component
- i18n: reuse existing translations where applicable
This commit enables administrators to label users with private notes while ensuring those notes are never exposed to the users themselves.
1057 lines
23 KiB
Go
1057 lines
23 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"one-api/common"
|
|
"one-api/model"
|
|
"one-api/setting"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"one-api/constant"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type LoginRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
func Login(c *gin.Context) {
|
|
if !common.PasswordLoginEnabled {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "管理员关闭了密码登录",
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
var loginRequest LoginRequest
|
|
err := json.NewDecoder(c.Request.Body).Decode(&loginRequest)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "无效的参数",
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
username := loginRequest.Username
|
|
password := loginRequest.Password
|
|
if username == "" || password == "" {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "无效的参数",
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
user := model.User{
|
|
Username: username,
|
|
Password: password,
|
|
}
|
|
err = user.ValidateAndFill()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": err.Error(),
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
setupLogin(&user, c)
|
|
}
|
|
|
|
// setup session & cookies and then return user info
|
|
func setupLogin(user *model.User, c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
session.Set("id", user.Id)
|
|
session.Set("username", user.Username)
|
|
session.Set("role", user.Role)
|
|
session.Set("status", user.Status)
|
|
session.Set("group", user.Group)
|
|
err := session.Save()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "无法保存会话信息,请重试",
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
cleanUser := model.User{
|
|
Id: user.Id,
|
|
Username: user.Username,
|
|
DisplayName: user.DisplayName,
|
|
Role: user.Role,
|
|
Status: user.Status,
|
|
Group: user.Group,
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "",
|
|
"success": true,
|
|
"data": cleanUser,
|
|
})
|
|
}
|
|
|
|
func Logout(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
session.Clear()
|
|
err := session.Save()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": err.Error(),
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "",
|
|
"success": true,
|
|
})
|
|
}
|
|
|
|
func Register(c *gin.Context) {
|
|
if !common.RegisterEnabled {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "管理员关闭了新用户注册",
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
if !common.PasswordRegisterEnabled {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册",
|
|
"success": false,
|
|
})
|
|
return
|
|
}
|
|
var user model.User
|
|
err := json.NewDecoder(c.Request.Body).Decode(&user)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的参数",
|
|
})
|
|
return
|
|
}
|
|
if err := common.Validate.Struct(&user); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "输入不合法 " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if common.EmailVerificationEnabled {
|
|
if user.Email == "" || user.VerificationCode == "" {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "管理员开启了邮箱验证,请输入邮箱地址和验证码",
|
|
})
|
|
return
|
|
}
|
|
if !common.VerifyCodeWithKey(user.Email, user.VerificationCode, common.EmailVerificationPurpose) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "验证码错误或已过期",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
exist, err := model.CheckUserExistOrDeleted(user.Username, user.Email)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "数据库错误,请稍后重试",
|
|
})
|
|
common.SysError(fmt.Sprintf("CheckUserExistOrDeleted error: %v", err))
|
|
return
|
|
}
|
|
if exist {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "用户名已存在,或已注销",
|
|
})
|
|
return
|
|
}
|
|
affCode := user.AffCode // this code is the inviter's code, not the user's own code
|
|
inviterId, _ := model.GetUserIdByAffCode(affCode)
|
|
cleanUser := model.User{
|
|
Username: user.Username,
|
|
Password: user.Password,
|
|
DisplayName: user.Username,
|
|
InviterId: inviterId,
|
|
}
|
|
if common.EmailVerificationEnabled {
|
|
cleanUser.Email = user.Email
|
|
}
|
|
if err := cleanUser.Insert(inviterId); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// 获取插入后的用户ID
|
|
var insertedUser model.User
|
|
if err := model.DB.Where("username = ?", cleanUser.Username).First(&insertedUser).Error; err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "用户注册失败或用户ID获取失败",
|
|
})
|
|
return
|
|
}
|
|
// 生成默认令牌
|
|
if constant.GenerateDefaultToken {
|
|
key, err := common.GenerateKey()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "生成默认令牌失败",
|
|
})
|
|
common.SysError("failed to generate token key: " + err.Error())
|
|
return
|
|
}
|
|
// 生成默认令牌
|
|
token := model.Token{
|
|
UserId: insertedUser.Id, // 使用插入后的用户ID
|
|
Name: cleanUser.Username + "的初始令牌",
|
|
Key: key,
|
|
CreatedTime: common.GetTimestamp(),
|
|
AccessedTime: common.GetTimestamp(),
|
|
ExpiredTime: -1, // 永不过期
|
|
RemainQuota: 500000, // 示例额度
|
|
UnlimitedQuota: true,
|
|
ModelLimitsEnabled: false,
|
|
}
|
|
if err := token.Insert(); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "创建默认令牌失败",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
func GetAllUsers(c *gin.Context) {
|
|
p, _ := strconv.Atoi(c.Query("p"))
|
|
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
|
if p < 1 {
|
|
p = 1
|
|
}
|
|
if pageSize < 0 {
|
|
pageSize = common.ItemsPerPage
|
|
}
|
|
users, total, err := model.GetAllUsers((p-1)*pageSize, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": gin.H{
|
|
"items": users,
|
|
"total": total,
|
|
"page": p,
|
|
"page_size": pageSize,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
func SearchUsers(c *gin.Context) {
|
|
keyword := c.Query("keyword")
|
|
group := c.Query("group")
|
|
p, _ := strconv.Atoi(c.Query("p"))
|
|
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
|
if p < 1 {
|
|
p = 1
|
|
}
|
|
if pageSize < 0 {
|
|
pageSize = common.ItemsPerPage
|
|
}
|
|
startIdx := (p - 1) * pageSize
|
|
users, total, err := model.SearchUsers(keyword, group, startIdx, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": gin.H{
|
|
"items": users,
|
|
"total": total,
|
|
"page": p,
|
|
"page_size": pageSize,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
func GetUser(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
user, err := model.GetUserById(id, false)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
myRole := c.GetInt("role")
|
|
if myRole <= user.Role && myRole != common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无权获取同级或更高等级用户的信息",
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": user,
|
|
})
|
|
return
|
|
}
|
|
|
|
func GenerateAccessToken(c *gin.Context) {
|
|
id := c.GetInt("id")
|
|
user, err := model.GetUserById(id, true)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
// get rand int 28-32
|
|
randI := common.GetRandomInt(4)
|
|
key, err := common.GenerateRandomKey(29 + randI)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "生成失败",
|
|
})
|
|
common.SysError("failed to generate key: " + err.Error())
|
|
return
|
|
}
|
|
user.SetAccessToken(key)
|
|
|
|
if model.DB.Where("access_token = ?", user.AccessToken).First(user).RowsAffected != 0 {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "请重试,系统生成的 UUID 竟然重复了!",
|
|
})
|
|
return
|
|
}
|
|
|
|
if err := user.Update(false); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": user.AccessToken,
|
|
})
|
|
return
|
|
}
|
|
|
|
type TransferAffQuotaRequest struct {
|
|
Quota int `json:"quota" binding:"required"`
|
|
}
|
|
|
|
func TransferAffQuota(c *gin.Context) {
|
|
id := c.GetInt("id")
|
|
user, err := model.GetUserById(id, true)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
tran := TransferAffQuotaRequest{}
|
|
if err := c.ShouldBindJSON(&tran); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
err = user.TransferAffQuotaToQuota(tran.Quota)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "划转失败 " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "划转成功",
|
|
})
|
|
}
|
|
|
|
func GetAffCode(c *gin.Context) {
|
|
id := c.GetInt("id")
|
|
user, err := model.GetUserById(id, true)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if user.AffCode == "" {
|
|
user.AffCode = common.GetRandomString(4)
|
|
if err := user.Update(false); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": user.AffCode,
|
|
})
|
|
return
|
|
}
|
|
|
|
func GetSelf(c *gin.Context) {
|
|
id := c.GetInt("id")
|
|
user, err := model.GetUserById(id, false)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
// Hide admin remarks: set to empty to trigger omitempty tag, ensuring the remark field is not included in JSON returned to regular users
|
|
user.Remark = ""
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": user,
|
|
})
|
|
return
|
|
}
|
|
|
|
func GetUserModels(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
id = c.GetInt("id")
|
|
}
|
|
user, err := model.GetUserCache(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
groups := setting.GetUserUsableGroups(user.Group)
|
|
var models []string
|
|
for group := range groups {
|
|
for _, g := range model.GetGroupModels(group) {
|
|
if !common.StringsContains(models, g) {
|
|
models = append(models, g)
|
|
}
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": models,
|
|
})
|
|
return
|
|
}
|
|
|
|
func UpdateUser(c *gin.Context) {
|
|
var updatedUser model.User
|
|
err := json.NewDecoder(c.Request.Body).Decode(&updatedUser)
|
|
if err != nil || updatedUser.Id == 0 {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的参数",
|
|
})
|
|
return
|
|
}
|
|
if updatedUser.Password == "" {
|
|
updatedUser.Password = "$I_LOVE_U" // make Validator happy :)
|
|
}
|
|
if err := common.Validate.Struct(&updatedUser); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "输入不合法 " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
originUser, err := model.GetUserById(updatedUser.Id, false)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
myRole := c.GetInt("role")
|
|
if myRole <= originUser.Role && myRole != common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无权更新同权限等级或更高权限等级的用户信息",
|
|
})
|
|
return
|
|
}
|
|
if myRole <= updatedUser.Role && myRole != common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无权将其他用户权限等级提升到大于等于自己的权限等级",
|
|
})
|
|
return
|
|
}
|
|
if updatedUser.Password == "$I_LOVE_U" {
|
|
updatedUser.Password = "" // rollback to what it should be
|
|
}
|
|
updatePassword := updatedUser.Password != ""
|
|
if err := updatedUser.Edit(updatePassword); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if originUser.Quota != updatedUser.Quota {
|
|
model.RecordLog(originUser.Id, model.LogTypeManage, fmt.Sprintf("管理员将用户额度从 %s修改为 %s", common.LogQuota(originUser.Quota), common.LogQuota(updatedUser.Quota)))
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
func UpdateSelf(c *gin.Context) {
|
|
var user model.User
|
|
err := json.NewDecoder(c.Request.Body).Decode(&user)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的参数",
|
|
})
|
|
return
|
|
}
|
|
if user.Password == "" {
|
|
user.Password = "$I_LOVE_U" // make Validator happy :)
|
|
}
|
|
if err := common.Validate.Struct(&user); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "输入不合法 " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
cleanUser := model.User{
|
|
Id: c.GetInt("id"),
|
|
Username: user.Username,
|
|
Password: user.Password,
|
|
DisplayName: user.DisplayName,
|
|
}
|
|
if user.Password == "$I_LOVE_U" {
|
|
user.Password = "" // rollback to what it should be
|
|
cleanUser.Password = ""
|
|
}
|
|
updatePassword, err := checkUpdatePassword(user.OriginalPassword, user.Password, cleanUser.Id)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if err := cleanUser.Update(updatePassword); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
func checkUpdatePassword(originalPassword string, newPassword string, userId int) (updatePassword bool, err error) {
|
|
var currentUser *model.User
|
|
currentUser, err = model.GetUserById(userId, true)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !common.ValidatePasswordAndHash(originalPassword, currentUser.Password) {
|
|
err = fmt.Errorf("原密码错误")
|
|
return
|
|
}
|
|
if newPassword == "" {
|
|
return
|
|
}
|
|
updatePassword = true
|
|
return
|
|
}
|
|
|
|
func DeleteUser(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
originUser, err := model.GetUserById(id, false)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
myRole := c.GetInt("role")
|
|
if myRole <= originUser.Role {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无权删除同权限等级或更高权限等级的用户",
|
|
})
|
|
return
|
|
}
|
|
err = model.HardDeleteUserById(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
func DeleteSelf(c *gin.Context) {
|
|
id := c.GetInt("id")
|
|
user, _ := model.GetUserById(id, false)
|
|
|
|
if user.Role == common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "不能删除超级管理员账户",
|
|
})
|
|
return
|
|
}
|
|
|
|
err := model.DeleteUserById(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
func CreateUser(c *gin.Context) {
|
|
var user model.User
|
|
err := json.NewDecoder(c.Request.Body).Decode(&user)
|
|
user.Username = strings.TrimSpace(user.Username)
|
|
if err != nil || user.Username == "" || user.Password == "" {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的参数",
|
|
})
|
|
return
|
|
}
|
|
if err := common.Validate.Struct(&user); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "输入不合法 " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if user.DisplayName == "" {
|
|
user.DisplayName = user.Username
|
|
}
|
|
myRole := c.GetInt("role")
|
|
if user.Role >= myRole {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无法创建权限大于等于自己的用户",
|
|
})
|
|
return
|
|
}
|
|
// Even for admin users, we cannot fully trust them!
|
|
cleanUser := model.User{
|
|
Username: user.Username,
|
|
Password: user.Password,
|
|
DisplayName: user.DisplayName,
|
|
}
|
|
if err := cleanUser.Insert(0); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
type ManageRequest struct {
|
|
Id int `json:"id"`
|
|
Action string `json:"action"`
|
|
}
|
|
|
|
// ManageUser Only admin user can do this
|
|
func ManageUser(c *gin.Context) {
|
|
var req ManageRequest
|
|
err := json.NewDecoder(c.Request.Body).Decode(&req)
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的参数",
|
|
})
|
|
return
|
|
}
|
|
user := model.User{
|
|
Id: req.Id,
|
|
}
|
|
// Fill attributes
|
|
model.DB.Unscoped().Where(&user).First(&user)
|
|
if user.Id == 0 {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "用户不存在",
|
|
})
|
|
return
|
|
}
|
|
myRole := c.GetInt("role")
|
|
if myRole <= user.Role && myRole != common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无权更新同权限等级或更高权限等级的用户信息",
|
|
})
|
|
return
|
|
}
|
|
switch req.Action {
|
|
case "disable":
|
|
user.Status = common.UserStatusDisabled
|
|
if user.Role == common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无法禁用超级管理员用户",
|
|
})
|
|
return
|
|
}
|
|
case "enable":
|
|
user.Status = common.UserStatusEnabled
|
|
case "delete":
|
|
if user.Role == common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无法删除超级管理员用户",
|
|
})
|
|
return
|
|
}
|
|
if err := user.Delete(); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
case "promote":
|
|
if myRole != common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "普通管理员用户无法提升其他用户为管理员",
|
|
})
|
|
return
|
|
}
|
|
if user.Role >= common.RoleAdminUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "该用户已经是管理员",
|
|
})
|
|
return
|
|
}
|
|
user.Role = common.RoleAdminUser
|
|
case "demote":
|
|
if user.Role == common.RoleRootUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无法降级超级管理员用户",
|
|
})
|
|
return
|
|
}
|
|
if user.Role == common.RoleCommonUser {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "该用户已经是普通用户",
|
|
})
|
|
return
|
|
}
|
|
user.Role = common.RoleCommonUser
|
|
}
|
|
|
|
if err := user.Update(false); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
clearUser := model.User{
|
|
Role: user.Role,
|
|
Status: user.Status,
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": clearUser,
|
|
})
|
|
return
|
|
}
|
|
|
|
func EmailBind(c *gin.Context) {
|
|
email := c.Query("email")
|
|
code := c.Query("code")
|
|
if !common.VerifyCodeWithKey(email, code, common.EmailVerificationPurpose) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "验证码错误或已过期",
|
|
})
|
|
return
|
|
}
|
|
session := sessions.Default(c)
|
|
id := session.Get("id")
|
|
user := model.User{
|
|
Id: id.(int),
|
|
}
|
|
err := user.FillUserById()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
user.Email = email
|
|
// no need to check if this email already taken, because we have used verification code to check it
|
|
err = user.Update(false)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
return
|
|
}
|
|
|
|
type topUpRequest struct {
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
var topUpLock = sync.Mutex{}
|
|
|
|
func TopUp(c *gin.Context) {
|
|
topUpLock.Lock()
|
|
defer topUpLock.Unlock()
|
|
req := topUpRequest{}
|
|
err := c.ShouldBindJSON(&req)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
id := c.GetInt("id")
|
|
quota, err := model.Redeem(req.Key, id)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": quota,
|
|
})
|
|
return
|
|
}
|
|
|
|
type UpdateUserSettingRequest struct {
|
|
QuotaWarningType string `json:"notify_type"`
|
|
QuotaWarningThreshold float64 `json:"quota_warning_threshold"`
|
|
WebhookUrl string `json:"webhook_url,omitempty"`
|
|
WebhookSecret string `json:"webhook_secret,omitempty"`
|
|
NotificationEmail string `json:"notification_email,omitempty"`
|
|
AcceptUnsetModelRatioModel bool `json:"accept_unset_model_ratio_model"`
|
|
RecordIpLog bool `json:"record_ip_log"`
|
|
}
|
|
|
|
func UpdateUserSetting(c *gin.Context) {
|
|
var req UpdateUserSettingRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的参数",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证预警类型
|
|
if req.QuotaWarningType != constant.NotifyTypeEmail && req.QuotaWarningType != constant.NotifyTypeWebhook {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的预警类型",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证预警阈值
|
|
if req.QuotaWarningThreshold <= 0 {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "预警阈值必须大于0",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 如果是webhook类型,验证webhook地址
|
|
if req.QuotaWarningType == constant.NotifyTypeWebhook {
|
|
if req.WebhookUrl == "" {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "Webhook地址不能为空",
|
|
})
|
|
return
|
|
}
|
|
// 验证URL格式
|
|
if _, err := url.ParseRequestURI(req.WebhookUrl); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的Webhook地址",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// 如果是邮件类型,验证邮箱地址
|
|
if req.QuotaWarningType == constant.NotifyTypeEmail && req.NotificationEmail != "" {
|
|
// 验证邮箱格式
|
|
if !strings.Contains(req.NotificationEmail, "@") {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "无效的邮箱地址",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
userId := c.GetInt("id")
|
|
user, err := model.GetUserById(userId, true)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// 构建设置
|
|
settings := map[string]interface{}{
|
|
constant.UserSettingNotifyType: req.QuotaWarningType,
|
|
constant.UserSettingQuotaWarningThreshold: req.QuotaWarningThreshold,
|
|
"accept_unset_model_ratio_model": req.AcceptUnsetModelRatioModel,
|
|
constant.UserSettingRecordIpLog: req.RecordIpLog,
|
|
}
|
|
|
|
// 如果是webhook类型,添加webhook相关设置
|
|
if req.QuotaWarningType == constant.NotifyTypeWebhook {
|
|
settings[constant.UserSettingWebhookUrl] = req.WebhookUrl
|
|
if req.WebhookSecret != "" {
|
|
settings[constant.UserSettingWebhookSecret] = req.WebhookSecret
|
|
}
|
|
}
|
|
|
|
// 如果提供了通知邮箱,添加到设置中
|
|
if req.QuotaWarningType == constant.NotifyTypeEmail && req.NotificationEmail != "" {
|
|
settings[constant.UserSettingNotificationEmail] = req.NotificationEmail
|
|
}
|
|
|
|
// 更新用户设置
|
|
user.SetSetting(settings)
|
|
if err := user.Update(false); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "更新设置失败: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "设置已更新",
|
|
})
|
|
}
|