Files
SingBox-Gopanel/internal/handler/plugin_api.go
CN-JS-HuiBai 06da23fbbc
All checks were successful
build / build (api, amd64, linux) (push) Successful in -45s
build / build (api, arm64, linux) (push) Successful in -48s
build / build (api.exe, amd64, windows) (push) Successful in -47s
进一步补充后端API
2026-04-17 10:46:15 +08:00

311 lines
11 KiB
Go

package handler
import (
"strings"
"time"
"xboard-go/internal/database"
"xboard-go/internal/model"
"xboard-go/internal/service"
"github.com/gin-gonic/gin"
)
func PluginUserOnlineDevicesUsers(c *gin.Context) {
page := parsePositiveInt(c.DefaultQuery("page", "1"), 1)
perPage := parsePositiveInt(c.DefaultQuery("per_page", "20"), 20)
keyword := strings.TrimSpace(c.Query("keyword"))
query := database.DB.Model(&model.User{}).Order("id DESC")
if keyword != "" {
query = query.Where("email LIKE ? OR id = ?", "%"+keyword+"%", keyword)
}
var total int64
query.Count(&total)
var users []model.User
if err := query.Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error; err != nil {
Fail(c, 500, "failed to fetch users")
return
}
userIDs := make([]int, 0, len(users))
for _, user := range users {
userIDs = append(userIDs, user.ID)
}
devices := service.GetUsersDevices(userIDs)
list := make([]gin.H, 0, len(users))
usersWithOnlineIP := 0
totalOnlineIPs := 0
for _, user := range users {
subscriptionName := "No subscription"
if user.PlanID != nil {
var plan model.Plan
if database.DB.First(&plan, *user.PlanID).Error == nil {
subscriptionName = plan.Name
}
}
ips := devices[user.ID]
if len(ips) > 0 {
usersWithOnlineIP++
totalOnlineIPs += len(ips)
}
list = append(list, gin.H{
"id": user.ID,
"email": user.Email,
"subscription_name": subscriptionName,
"online_count": len(ips),
"online_devices": ips,
"last_online_text": formatTimeValue(user.LastOnlineAt),
"created_text": formatUnixValue(user.CreatedAt),
})
}
Success(c, gin.H{
"list": list,
"filters": gin.H{
"keyword": keyword,
"per_page": perPage,
},
"summary": gin.H{
"page_users": len(users),
"users_with_online_ip": usersWithOnlineIP,
"total_online_ips": totalOnlineIPs,
"current_page": page,
},
"pagination": gin.H{
"current": page,
"last_page": calculateLastPage(total, perPage),
"per_page": perPage,
"total": total,
},
})
}
func PluginUserOnlineDevicesGetIP(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, 401, "unauthorized")
return
}
devices := service.GetUsersDevices([]int{user.ID})
ips := devices[user.ID]
authToken, _ := c.Get("auth_token")
currentToken, _ := authToken.(string)
sessions := service.GetUserSessions(user.ID, currentToken)
currentID := currentSessionID(c)
sessionItems := make([]gin.H, 0, len(sessions))
for _, session := range sessions {
sessionItems = append(sessionItems, gin.H{
"id": session.ID,
"name": session.Name,
"user_agent": session.UserAgent,
"ip": firstString(session.IP, "-"),
"created_at": session.CreatedAt,
"last_used_at": session.LastUsedAt,
"expires_at": session.ExpiresAt,
"is_current": session.ID == currentID,
})
}
Success(c, gin.H{
"ips": ips,
"session_overview": gin.H{
"online_ip_count": len(ips),
"online_ips": ips,
"online_device_count": len(ips),
"device_limit": user.DeviceLimit,
"last_online_at": user.LastOnlineAt,
"active_session_count": len(sessionItems),
"sessions": sessionItems,
},
})
}
func PluginUserAddIPv6Check(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, 401, "unauthorized")
return
}
if user.PlanID == nil {
Success(c, gin.H{"allowed": false, "reason": "No active plan"})
return
}
var plan model.Plan
if err := database.DB.First(&plan, *user.PlanID).Error; err != nil {
Success(c, gin.H{"allowed": false, "reason": "No active plan"})
return
}
ipv6Email := service.IPv6ShadowEmail(user.Email)
var count int64
database.DB.Model(&model.User{}).Where("email = ?", ipv6Email).Count(&count)
Success(c, gin.H{
"allowed": service.PluginPlanAllowed(&plan),
"is_active": count > 0,
})
}
func PluginUserAddIPv6Enable(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, 401, "unauthorized")
return
}
if !service.SyncIPv6ShadowAccount(user) {
Fail(c, 403, "your plan does not support IPv6 subscription")
return
}
SuccessMessage(c, "IPv6 subscription enabled/synced", true)
}
func PluginUserAddIPv6SyncPassword(c *gin.Context) {
user, ok := currentUser(c)
if !ok {
Fail(c, 401, "unauthorized")
return
}
ipv6Email := service.IPv6ShadowEmail(user.Email)
result := database.DB.Model(&model.User{}).Where("email = ?", ipv6Email).Update("password", user.Password)
if result.Error != nil {
Fail(c, 404, "IPv6 user not found")
return
}
if result.RowsAffected == 0 {
Fail(c, 404, "IPv6 user not found")
return
}
SuccessMessage(c, "Password synced to IPv6 account", true)
}
func AdminConfigFetch(c *gin.Context) {
Success(c, gin.H{
"invite": gin.H{
"invite_force": service.MustGetBool("invite_force", false),
"invite_commission": service.MustGetInt("invite_commission", 10),
"invite_gen_limit": service.MustGetInt("invite_gen_limit", 5),
"invite_never_expire": service.MustGetBool("invite_never_expire", false),
"commission_first_time_enable": service.MustGetBool("commission_first_time_enable", true),
"commission_auto_check_enable": service.MustGetBool("commission_auto_check_enable", true),
"commission_withdraw_limit": service.MustGetInt("commission_withdraw_limit", 100),
"withdraw_close_enable": service.MustGetBool("withdraw_close_enable", false),
"commission_distribution_enable": service.MustGetBool("commission_distribution_enable", false),
"commission_distribution_l1": service.MustGetInt("commission_distribution_l1", 0),
"commission_distribution_l2": service.MustGetInt("commission_distribution_l2", 0),
"commission_distribution_l3": service.MustGetInt("commission_distribution_l3", 0),
},
"site": gin.H{
"logo": service.MustGetString("logo", ""),
"force_https": service.MustGetBool("force_https", false),
"stop_register": service.MustGetBool("stop_register", false),
"app_name": service.MustGetString("app_name", "XBoard"),
"app_description": service.MustGetString("app_description", "XBoard is best!"),
"app_url": service.GetAppURL(),
"subscribe_url": service.MustGetString("subscribe_url", ""),
"try_out_enable": service.MustGetBool("try_out_enable", false),
"try_out_plan_id": service.MustGetInt("try_out_plan_id", 0),
"try_out_hour": service.MustGetInt("try_out_hour", 1),
"tos_url": service.MustGetString("tos_url", ""),
"currency": service.MustGetString("currency", "CNY"),
"currency_symbol": service.MustGetString("currency_symbol", "¥"),
"ticket_must_wait_reply": service.MustGetBool("ticket_must_wait_reply", false),
},
"subscribe": gin.H{
"plan_change_enable": service.MustGetBool("plan_change_enable", true),
"reset_traffic_method": service.MustGetInt("reset_traffic_method", 0),
"surplus_enable": service.MustGetBool("surplus_enable", true),
"new_order_event_id": service.MustGetInt("new_order_event_id", 0),
"renew_order_event_id": service.MustGetInt("renew_order_event_id", 0),
"change_order_event_id": service.MustGetInt("change_order_event_id", 0),
"show_info_to_server_enable": service.MustGetBool("show_info_to_server_enable", false),
"show_protocol_to_server_enable": service.MustGetBool("show_protocol_to_server_enable", false),
"subscribe_path": service.MustGetString("subscribe_path", "s"),
},
"frontend": gin.H{
"frontend_theme": service.MustGetString("frontend_theme", "Xboard"),
"frontend_theme_sidebar": service.MustGetString("frontend_theme_sidebar", "light"),
"frontend_theme_header": service.MustGetString("frontend_theme_header", "dark"),
"frontend_theme_color": service.MustGetString("frontend_theme_color", "default"),
"frontend_background_url": service.MustGetString("frontend_background_url", ""),
},
"server": gin.H{
"server_token": service.MustGetString("server_token", ""),
"server_pull_interval": service.MustGetInt("server_pull_interval", 60),
"server_push_interval": service.MustGetInt("server_push_interval", 60),
"device_limit_mode": service.MustGetInt("device_limit_mode", 0),
"server_ws_enable": service.MustGetBool("server_ws_enable", true),
"server_ws_url": service.MustGetString("server_ws_url", ""),
},
"email": gin.H{
"email_template": service.MustGetString("email_template", "default"),
"email_host": service.MustGetString("email_host", ""),
"email_port": service.MustGetString("email_port", ""),
"email_username": service.MustGetString("email_username", ""),
"email_password": service.MustGetString("email_password", ""),
"email_encryption": service.MustGetString("email_encryption", ""),
"email_from_address": service.MustGetString("email_from_address", ""),
"remind_mail_enable": service.MustGetBool("remind_mail_enable", false),
},
"telegram": gin.H{
"telegram_bot_enable": service.MustGetBool("telegram_bot_enable", false),
"telegram_bot_token": service.MustGetString("telegram_bot_token", ""),
"telegram_webhook_url": service.MustGetString("telegram_webhook_url", ""),
"telegram_discuss_link": service.MustGetString("telegram_discuss_link", ""),
},
"app": gin.H{
"windows_version": service.MustGetString("windows_version", ""),
"windows_download_url": service.MustGetString("windows_download_url", ""),
"macos_version": service.MustGetString("macos_version", ""),
"macos_download_url": service.MustGetString("macos_download_url", ""),
"android_version": service.MustGetString("android_version", ""),
"android_download_url": service.MustGetString("android_download_url", ""),
},
"safe": gin.H{
"email_verify": service.MustGetBool("email_verify", false),
"safe_mode_enable": service.MustGetBool("safe_mode_enable", false),
"secure_path": service.GetAdminSecurePath(),
"email_whitelist_enable": service.MustGetBool("email_whitelist_enable", false),
"email_whitelist_suffix": service.MustGetString("email_whitelist_suffix", ""),
"email_gmail_limit_enable": service.MustGetBool("email_gmail_limit_enable", false),
"captcha_enable": service.MustGetBool("captcha_enable", false),
"register_limit_by_ip_enable": service.MustGetBool("register_limit_by_ip_enable", false),
"register_limit_count": service.MustGetInt("register_limit_count", 3),
"register_limit_expire": service.MustGetInt("register_limit_expire", 60),
"password_limit_enable": service.MustGetBool("password_limit_enable", true),
"password_limit_count": service.MustGetInt("password_limit_count", 5),
"password_limit_expire": service.MustGetInt("password_limit_expire", 60),
},
})
}
func AdminSystemStatus(c *gin.Context) {
Success(c, gin.H{
"server_time": time.Now().Unix(),
"app_name": service.MustGetString("app_name", "XBoard"),
"app_url": service.GetAppURL(),
"version": "1.0.0",
"go_version": "1.21+",
})
}