feat: expand admin user management
This commit is contained in:
+77
-3
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -28,10 +30,26 @@ func (a *App) adminListUsers(c *gin.Context) {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
for i := range users {
|
||||
users[i].PasswordHash = ""
|
||||
type userView struct {
|
||||
User
|
||||
Balance int64 `json:"balance"`
|
||||
Reserved int64 `json:"reserved"`
|
||||
Available int64 `json:"available"`
|
||||
TaskCount int64 `json:"taskCount"`
|
||||
CompletedCount int64 `json:"completedCount"`
|
||||
}
|
||||
c.JSON(200, gin.H{"data": users})
|
||||
out := make([]userView, 0, len(users))
|
||||
for i := range users {
|
||||
u := users[i]
|
||||
u.PasswordHash = ""
|
||||
var account CreditAccount
|
||||
_ = a.db.Where("user_id = ?", u.ID).First(&account).Error
|
||||
var taskCount, completedCount int64
|
||||
_ = a.db.Model(&Task{}).Where("user_id = ?", u.ID).Count(&taskCount).Error
|
||||
_ = a.db.Model(&Task{}).Where("user_id = ? AND status = ?", u.ID, "done").Count(&completedCount).Error
|
||||
out = append(out, userView{User: u, Balance: account.Balance, Reserved: account.Reserved, Available: account.Balance - account.Reserved, TaskCount: taskCount, CompletedCount: completedCount})
|
||||
}
|
||||
c.JSON(200, gin.H{"data": out})
|
||||
}
|
||||
|
||||
func (a *App) adminCreateUser(c *gin.Context) {
|
||||
@@ -88,6 +106,62 @@ func (a *App) adminCreateUser(c *gin.Context) {
|
||||
c.JSON(201, u)
|
||||
}
|
||||
|
||||
func (a *App) adminAdjustUserCredits(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
var in struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if c.BindJSON(&in) != nil || in.Amount == 0 {
|
||||
c.JSON(400, gin.H{"error": "amount must not be zero"})
|
||||
return
|
||||
}
|
||||
operator := c.MustGet("user").(*User)
|
||||
var account CreditAccount
|
||||
err = a.db.Transaction(func(tx *gorm.DB) error {
|
||||
var u User
|
||||
if err := tx.First(&u, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("user_id = ?", u.ID).First(&account).Error; err != nil {
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
account = CreditAccount{UserID: u.ID}
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newBalance := account.Balance + in.Amount
|
||||
if newBalance < account.Reserved {
|
||||
return errors.New("balance cannot be lower than reserved credits")
|
||||
}
|
||||
account.Balance = newBalance
|
||||
if err := tx.Save(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
reason := strings.TrimSpace(in.Reason)
|
||||
if reason == "" {
|
||||
reason = "admin credit adjustment"
|
||||
}
|
||||
return tx.Create(&CreditTransaction{UserID: u.ID, Type: "adjust", Amount: in.Amount, BalanceAfter: account.Balance, ReferenceType: "admin", ReferenceID: strconv.FormatUint(uint64(operator.ID), 10), Reason: reason}).Error
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(404, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
a.audit(operator.ID, "user.credit.adjust", "user", strconv.FormatUint(id, 10), fmt.Sprintf("amount=%d;reason=%s", in.Amount, strings.TrimSpace(in.Reason)))
|
||||
c.JSON(200, gin.H{"balance": account.Balance, "reserved": account.Reserved, "available": account.Balance - account.Reserved})
|
||||
}
|
||||
|
||||
func (a *App) adminAudit(c *gin.Context) {
|
||||
var logs []AuditLog
|
||||
q := a.db.Order("id desc").Limit(200)
|
||||
|
||||
Reference in New Issue
Block a user