Files
SingBox-Gopanel/internal/handler/user_api.go
CN-JS-HuiBai 6e75b7d7d5
Some checks failed
build / build (api, amd64, linux) (push) Failing after -50s
build / build (api, arm64, linux) (push) Failing after -52s
build / build (api.exe, amd64, windows) (push) Failing after -52s
修复前端节点在线情况
2026-04-18 02:32:03 +08:00

288 lines
7.1 KiB
Go

package handler
import (
"crypto/md5"
"fmt"
"net/http"
"strings"
"time"
"xboard-go/internal/database"
"xboard-go/internal/model"
"xboard-go/internal/service"
"xboard-go/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func UserInfo(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
Success(c, gin.H{
"email": user.Email,
"transfer_enable": user.TransferEnable,
"last_login_at": user.LastLoginAt,
"created_at": user.CreatedAt,
"banned": user.Banned,
"remind_expire": user.RemindExpire,
"remind_traffic": user.RemindTraffic,
"expired_at": user.ExpiredAt,
"balance": user.Balance,
"commission_balance": user.CommissionBalance,
"plan_id": user.PlanID,
"discount": user.Discount,
"commission_rate": user.CommissionRate,
"telegram_id": user.TelegramID,
"uuid": user.UUID,
"avatar_url": "https://cdn.v2ex.com/gravatar/" + fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(user.Email)))) + "?s=64&d=identicon",
})
}
func UserGetStat(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
var pendingOrders int64
var openTickets int64
var invitedUsers int64
database.DB.Model(&model.Order{}).Where("status = ? AND user_id = ?", 0, user.ID).Count(&pendingOrders)
database.DB.Model(&model.Ticket{}).Where("status = ? AND user_id = ?", 0, user.ID).Count(&openTickets)
database.DB.Model(&model.User{}).Where("invite_user_id = ?", user.ID).Count(&invitedUsers)
Success(c, []int64{pendingOrders, openTickets, invitedUsers})
}
func UserGetSubscribe(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
if user.PlanID != nil {
var plan model.Plan
if err := database.DB.First(&plan, *user.PlanID).Error; err == nil {
user.Plan = &plan
}
}
baseURL := requestBaseURL(c)
subscribePath := service.GetSubscribePath()
Success(c, gin.H{
"plan_id": user.PlanID,
"token": user.Token,
"expired_at": user.ExpiredAt,
"u": user.U,
"d": user.D,
"transfer_enable": user.TransferEnable,
"email": user.Email,
"uuid": user.UUID,
"device_limit": user.DeviceLimit,
"speed_limit": user.SpeedLimit,
"next_reset_at": user.NextResetAt,
"plan": service.FormatPlan(user.Plan),
"subscribe_url": strings.TrimRight(baseURL, "/") + "/" + subscribePath + "/" + user.Token,
"reset_day": service.GetResetDay(user),
})
}
func UserCheckLogin(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Success(c, gin.H{"is_login": false})
return
}
Success(c, gin.H{
"is_login": true,
"is_admin": user.IsAdmin,
})
}
func UserResetSecurity(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
newUUID := uuid.New().String()
newToken := service.GenerateSubscriptionToken()
if err := database.DB.Model(&model.User{}).
Where("id = ?", user.ID).
Updates(map[string]any{"uuid": newUUID, "token": newToken, "updated_at": time.Now().Unix()}).Error; err != nil {
Fail(c, 500, "reset failed")
return
}
baseURL := requestBaseURL(c)
Success(c, strings.TrimRight(baseURL, "/")+"/"+service.GetSubscribePath()+"/"+newToken)
}
func UserUpdate(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
var req struct {
RemindExpire *int `json:"remind_expire"`
RemindTraffic *int `json:"remind_traffic"`
}
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, 400, "invalid request body")
return
}
updates := map[string]any{"updated_at": time.Now().Unix()}
if req.RemindExpire != nil {
updates["remind_expire"] = *req.RemindExpire
}
if req.RemindTraffic != nil {
updates["remind_traffic"] = *req.RemindTraffic
}
if err := database.DB.Model(&model.User{}).Where("id = ?", user.ID).Updates(updates).Error; err != nil {
Fail(c, 500, "save failed")
return
}
Success(c, true)
}
func UserChangePassword(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
var req struct {
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=8"`
}
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, 400, "invalid request body")
return
}
if !utils.CheckPassword(req.OldPassword, user.Password, user.PasswordAlgo, user.PasswordSalt) {
Fail(c, 400, "the old password is wrong")
return
}
hashed, err := utils.HashPassword(req.NewPassword)
if err != nil {
Fail(c, 500, "failed to hash password")
return
}
if err := database.DB.Model(&model.User{}).
Where("id = ?", user.ID).
Updates(map[string]any{
"password": hashed,
"password_algo": nil,
"password_salt": nil,
"updated_at": time.Now().Unix(),
}).Error; err != nil {
Fail(c, 500, "save failed")
return
}
Success(c, true)
}
func UserServerFetch(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, http.StatusUnauthorized, "user not found")
return
}
servers, err := service.AvailableServersForUser(user)
if err != nil {
Fail(c, 500, "failed to fetch servers")
return
}
// Fetch all servers for parent lookup
var allServers []model.Server
if err := database.DB.Find(&allServers).Error; err != nil {
Fail(c, 500, "failed to fetch all servers")
return
}
serverMap := make(map[int]model.Server, len(allServers))
for _, s := range allServers {
serverMap[s.ID] = s
}
now := time.Now().Unix()
result := make([]gin.H, 0, len(servers))
for _, server := range servers {
lastCheckAt, _ := database.CacheGetJSON[int64](nodeLastCheckKey(&server))
isOnline := 0
if lastCheckAt > 0 && now-lastCheckAt <= 300 {
isOnline = 1
}
// Inherit online status from parent if current is offline
if isOnline == 0 && server.ParentID != nil {
if parent, ok := serverMap[*server.ParentID]; ok {
pLastCheckAt, _ := database.CacheGetJSON[int64](nodeLastCheckKey(&parent))
if pLastCheckAt > 0 && now-pLastCheckAt <= 300 {
isOnline = 1
lastCheckAt = pLastCheckAt
}
}
}
result = append(result, gin.H{
"id": server.ID,
"type": server.Type,
"name": server.Name,
"rate": server.Rate,
"tags": server.Tags,
"is_online": isOnline,
"last_check_at": lastCheckAt,
"cache_key": fmt.Sprintf("%s_%d_%d", server.Type, server.ID, server.UpdatedAt.Unix()),
})
}
Success(c, result)
}
func currentUser(c *gin.Context) (*model.User, bool) {
if value, exists := c.Get("user"); exists {
if user, ok := value.(*model.User); ok {
return user, true
}
}
userIDValue, exists := c.Get("user_id")
if !exists {
return nil, false
}
userID, ok := userIDValue.(int)
if !ok {
return nil, false
}
var user model.User
if err := database.DB.First(&user, userID).Error; err != nil {
return nil, false
}
c.Set("user", &user)
return &user, true
}