package main import ( "io" "log" "os" "path/filepath" "strings" "xboard-go/internal/config" "xboard-go/internal/database" "xboard-go/internal/handler" "xboard-go/internal/middleware" "xboard-go/internal/service" "github.com/gin-gonic/gin" ) func main() { config.LoadConfig() configureRuntimeLogging() database.InitDB() database.InitCache() router := gin.New() if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") { router.Use(gin.Logger()) } router.Use(gin.Recovery()) api := router.Group("/api") registerV1(api.Group("/v1")) registerV2(api.Group("/v2")) registerWebRoutes(router) if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") { log.Printf("server starting on port %s (log_level: %s)", config.AppConfig.AppPort, config.AppConfig.LogLevel) } if err := router.Run(":" + config.AppConfig.AppPort); err != nil { log.Fatalf("failed to start server: %v", err) } } func configureRuntimeLogging() { if config.NormalizeLogLevel(config.AppConfig.LogLevel) == "debug" { gin.SetMode(gin.DebugMode) } else { gin.SetMode(gin.ReleaseMode) } if config.NormalizeLogLevel(config.AppConfig.LogLevel) == "silent" { gin.DefaultWriter = io.Discard gin.DefaultErrorWriter = io.Discard } } func registerV1(v1 *gin.RouterGroup) { registerPassportRoutes(v1) registerGuestRoutes(v1) registerUserRoutes(v1) registerClientRoutes(v1) registerServerRoutesV1(v1) } func registerV2(v2 *gin.RouterGroup) { registerPassportRoutes(v2) registerUserRoutesV2(v2) registerClientRoutesV2(v2) registerServerRoutesV2(v2) registerAdminRoutesV2(v2) } func registerPassportRoutes(group *gin.RouterGroup) { passport := group.Group("/passport") passport.POST("/auth/register", handler.Register) passport.POST("/auth/login", handler.Login) passport.GET("/auth/token2Login", handler.Token2Login) passport.POST("/auth/forget", handler.ForgetPassword) passport.POST("/auth/getQuickLoginUrl", handler.PassportGetQuickLoginURL) passport.POST("/auth/loginWithMailLink", handler.PassportLoginWithMailLink) passport.POST("/comm/sendEmailVerify", handler.SendEmailVerify) passport.POST("/comm/pv", handler.PassportPV) } func registerGuestRoutes(v1 *gin.RouterGroup) { guest := v1.Group("/guest") guest.GET("/plan/fetch", handler.GuestPlanFetch) guest.POST("/telegram/webhook", handler.NotImplemented("guest.telegram.webhook")) guest.Any("/payment/notify/:method/:uuid", handler.NotImplemented("guest.payment.notify")) guest.GET("/comm/config", handler.GuestConfig) } func registerUserRoutes(v1 *gin.RouterGroup) { user := v1.Group("/user") user.Use(middleware.Auth()) user.GET("/resetSecurity", handler.UserResetSecurity) user.GET("/info", handler.UserInfo) user.POST("/changePassword", handler.UserChangePassword) user.POST("/update", handler.UserUpdate) user.GET("/getSubscribe", handler.UserGetSubscribe) user.GET("/getStat", handler.UserGetStat) user.GET("/checkLogin", handler.UserCheckLogin) user.GET("/plan/fetch", handler.GuestPlanFetch) user.GET("/server/fetch", handler.UserServerFetch) user.GET("/comm/config", handler.UserCommConfig) user.POST("/transfer", handler.UserTransfer) user.POST("/getQuickLoginUrl", handler.UserGetQuickLoginURL) user.GET("/notice/fetch", handler.UserNoticeFetch) user.POST("/coupon/check", handler.UserCouponCheck) user.POST("/gift-card/check", handler.UserGiftCardCheck) user.POST("/gift-card/redeem", handler.UserGiftCardRedeem) user.GET("/gift-card/history", handler.UserGiftCardHistory) user.GET("/gift-card/detail", handler.UserGiftCardDetail) user.GET("/gift-card/types", handler.UserGiftCardTypes) user.GET("/telegram/getBotInfo", handler.UserTelegramBotInfo) user.POST("/comm/getStripePublicKey", handler.UserGetStripePublicKey) user.GET("/stat/getTrafficLog", handler.UserTrafficLog) user.POST("/order/save", handler.UserOrderSave) user.POST("/order/checkout", handler.UserOrderCheckout) user.GET("/order/check", handler.UserOrderCheck) user.GET("/order/detail", handler.UserOrderDetail) user.GET("/order/fetch", handler.UserOrderFetch) user.GET("/order/getPaymentMethod", handler.UserOrderGetPaymentMethod) user.POST("/order/cancel", handler.UserOrderCancel) user.GET("/invite/save", handler.UserInviteSave) user.GET("/invite/fetch", handler.UserInviteFetch) user.GET("/invite/details", handler.UserInviteDetails) user.GET("/getActiveSession", handler.UserGetActiveSession) user.POST("/removeActiveSession", handler.UserRemoveActiveSession) user.GET("/knowledge/fetch", handler.UserKnowledgeFetch) user.GET("/knowledge/getCategory", handler.UserKnowledgeCategories) user.POST("/ticket/reply", handler.UserTicketReply) user.POST("/ticket/close", handler.UserTicketClose) user.POST("/ticket/save", handler.UserTicketSave) user.GET("/ticket/fetch", handler.UserTicketFetch) user.POST("/ticket/withdraw", handler.UserTicketWithdraw) // Integrated User Features user.GET("/real-name-verification/status", handler.PluginRealNameStatus) user.POST("/real-name-verification/submit", handler.PluginRealNameSubmit) user.GET("/user-online-devices/get-ip", handler.PluginUserOnlineDevicesGetIP) // User IPv6 subscription - read-only status check (enable/disable is admin-only) user.GET("/user-add-ipv6-subscription/check", handler.PluginUserAddIPv6Check) } func registerUserRoutesV2(v2 *gin.RouterGroup) { user := v2.Group("/user") user.Use(middleware.Auth()) user.GET("/resetSecurity", handler.UserResetSecurity) user.GET("/info", handler.UserInfo) } func registerClientRoutes(v1 *gin.RouterGroup) { client := v1.Group("/client") client.Use(middleware.ClientAuth()) client.GET("/subscribe", handler.ClientSubscribe) client.GET("/app/getConfig", handler.ClientAppConfigV1) client.GET("/app/getVersion", handler.ClientAppVersion) } func registerClientRoutesV2(v2 *gin.RouterGroup) { client := v2.Group("/client") client.Use(middleware.ClientAuth()) client.GET("/app/getConfig", handler.ClientAppConfigV2) client.GET("/app/getVersion", handler.ClientAppVersion) } func registerServerRoutesV1(v1 *gin.RouterGroup) { server := v1.Group("/server") uniProxy := server.Group("/UniProxy") uniProxy.Use(middleware.NodeAuth()) uniProxy.GET("/config", handler.NodeConfig) uniProxy.GET("/user", handler.NodeUser) uniProxy.POST("/push", handler.NodePush) uniProxy.GET("/alive", handler.NodeAlive) uniProxy.POST("/alive", handler.NodeAlive) uniProxy.GET("/alivelist", handler.NodeAliveList) uniProxy.POST("/status", handler.NodeStatus) shadowsocks := server.Group("/ShadowsocksTidalab") shadowsocks.Use(middleware.NodeAuth()) shadowsocks.GET("/user", handler.NodeShadowsocksTidalabUser) shadowsocks.POST("/submit", handler.NodeTidalabSubmit) trojan := server.Group("/TrojanTidalab") trojan.Use(middleware.NodeAuth()) trojan.GET("/config", handler.NodeTrojanTidalabConfig) trojan.GET("/user", handler.NodeTrojanTidalabUser) trojan.POST("/submit", handler.NodeTidalabSubmit) } func registerServerRoutesV2(v2 *gin.RouterGroup) { server := v2.Group("/server") server.Use(middleware.NodeAuth()) server.POST("/handshake", handler.NodeHandshake) server.POST("/report", handler.NodeReport) server.GET("/config", handler.NodeConfig) server.GET("/user", handler.NodeUser) server.POST("/push", handler.NodePush) server.GET("/alive", handler.NodeAlive) server.POST("/alive", handler.NodeAlive) server.GET("/alivelist", handler.NodeAliveList) server.POST("/status", handler.NodeStatus) } func registerAdminRoutesV2(v2 *gin.RouterGroup) { admin := v2.Group("/" + service.GetAdminSecurePath()) admin.Use(middleware.Auth(), middleware.AdminAuth()) admin.GET("/config/fetch", handler.AdminConfigFetch) admin.POST("/config/save", handler.AdminConfigSave) admin.GET("/config/getEmailTemplate", handler.AdminGetEmailTemplate) admin.POST("/config/testSendMail", handler.AdminTestSendMail) admin.POST("/config/setTelegramWebhook", handler.AdminSetTelegramWebhook) admin.GET("/dashboard/summary", handler.AdminDashboardSummary) admin.GET("/stat/getStats", handler.AdminDashboardSummary) admin.GET("/stat/getOverride", handler.AdminDashboardSummary) admin.GET("/stat/getTrafficRank", handler.AdminGetTrafficRank) admin.GET("/stat/getOrder", handler.AdminGetOrderStats) admin.POST("/stat/getStatUser", handler.AdminGetStatUser) admin.GET("/system/getSystemStatus", handler.AdminSystemStatus) admin.GET("/system/getQueueStats", handler.AdminSystemQueueStats) admin.GET("/system/getQueueWorkload", handler.AdminSystemQueueStats) admin.GET("/system/getQueueMasters", handler.AdminSystemQueueStats) admin.GET("/system/getHorizonFailedJobs", handler.AdminSystemQueueStats) admin.GET("/server/group/fetch", handler.AdminServerGroupsFetch) admin.POST("/server/group/save", handler.AdminServerGroupSave) admin.POST("/server/group/drop", handler.AdminServerGroupDrop) admin.GET("/server/route/fetch", handler.AdminServerRoutesFetch) admin.POST("/server/route/save", handler.AdminServerRouteSave) admin.POST("/server/route/drop", handler.AdminServerRouteDrop) admin.GET("/server/manage/getNodes", handler.AdminServerManageGetNodes) admin.POST("/server/manage/sort", handler.AdminServerManageSort) admin.POST("/server/manage/update", handler.AdminServerManageUpdate) admin.POST("/server/manage/save", handler.AdminServerManageSave) admin.POST("/server/manage/drop", handler.AdminServerManageDrop) admin.POST("/server/manage/copy", handler.AdminServerManageCopy) admin.POST("/server/manage/batchDelete", handler.AdminServerManageBatchDelete) admin.POST("/server/manage/resetTraffic", handler.AdminServerManageResetTraffic) admin.POST("/server/manage/batchResetTraffic", handler.AdminServerManageBatchResetTraffic) admin.GET("/plan/fetch", handler.AdminPlansFetch) admin.POST("/plan/save", handler.AdminPlanSave) admin.POST("/plan/drop", handler.AdminPlanDrop) admin.POST("/plan/sort", handler.AdminPlanSort) admin.GET("/order/fetch", handler.AdminOrdersFetch) admin.POST("/order/fetch", handler.AdminOrdersFetch) admin.POST("/order/detail", handler.AdminOrderDetail) admin.POST("/order/paid", handler.AdminOrderPaid) admin.POST("/order/cancel", handler.AdminOrderCancel) admin.POST("/order/update", handler.AdminOrderUpdate) admin.POST("/order/assign", handler.AdminOrderAssign) admin.GET("/user/fetch", handler.AdminUsersFetch) admin.POST("/user/fetch", handler.AdminUsersFetch) admin.POST("/user/update", handler.AdminUserUpdate) admin.POST("/user/ban", handler.AdminUserBan) admin.POST("/user/resetSecret", handler.AdminUserResetSecret) admin.POST("/user/sendMail", handler.AdminUserSendMail) admin.POST("/user/destroy", handler.AdminUserDelete) admin.POST("/user/resetTraffic", handler.AdminUserResetTraffic) admin.POST("/user/drop", handler.AdminUserDelete) admin.GET("/ticket/fetch", handler.AdminTicketsFetch) admin.POST("/ticket/fetch", handler.AdminTicketsFetch) admin.GET("/traffic-reset/fetch", handler.AdminTrafficResetFetch) admin.GET("/traffic-reset/logs", handler.AdminTrafficResetFetch) admin.POST("/traffic-reset/reset-user", handler.AdminTrafficResetUser) admin.GET("/traffic-reset/user/:id/history", handler.AdminTrafficResetUserHistory) admin.GET("/notice/fetch", handler.AdminNoticeFetch) admin.POST("/notice/save", handler.AdminNoticeSave) admin.POST("/notice/drop", handler.AdminNoticeDrop) admin.POST("/notice/show", handler.AdminNoticeShow) admin.POST("/notice/sort", handler.AdminNoticeSort) admin.GET("/knowledge/fetch", handler.AdminKnowledgeFetch) admin.POST("/knowledge/save", handler.AdminKnowledgeSave) admin.POST("/knowledge/drop", handler.AdminKnowledgeDrop) admin.POST("/knowledge/sort", handler.AdminKnowledgeSort) // Integrated Admin Features admin.GET("/realname/records", handler.PluginRealNameRecords) admin.POST("/realname/clear-cache", handler.PluginRealNameClearCache) admin.POST("/realname/review/:userId", handler.PluginRealNameReview) admin.POST("/realname/reset/:userId", handler.PluginRealNameReset) admin.POST("/realname/sync-all", handler.PluginRealNameSyncAll) admin.POST("/realname/approve-all", handler.PluginRealNameApproveAll) admin.GET("/user-online-devices/users", handler.PluginUserOnlineDevicesUsers) admin.GET("/user-add-ipv6-subscription/users", handler.AdminIPv6SubscriptionUsers) admin.GET("/user-add-ipv6-subscription/config", handler.AdminIPv6SubscriptionConfigFetch) admin.POST("/user-add-ipv6-subscription/config", handler.AdminIPv6SubscriptionConfigSave) admin.POST("/user-add-ipv6-subscription/enable/:userId", handler.AdminIPv6SubscriptionEnable) admin.POST("/user-add-ipv6-subscription/disable/:userId", handler.AdminIPv6SubscriptionDisable) admin.POST("/user-add-ipv6-subscription/sync-password/:userId", handler.AdminIPv6SubscriptionSyncPassword) } func registerWebRoutes(router *gin.Engine) { themeRoot := filepath.Join(".", "frontend", "theme") adminRoot := filepath.Join(".", "frontend", "admin") if _, err := os.Stat(themeRoot); err == nil { router.Static("/theme", themeRoot) } if _, err := os.Stat(adminRoot); err == nil { router.Static("/admin-assets", adminRoot) } securePath := "/" + service.GetAdminSecurePath() subscribePath := "/" + service.GetSubscribePath() router.GET("/", handler.UserThemePage) router.GET("/dashboard", handler.UserThemePage) router.GET(subscribePath+"/:token", handler.Subscribe) router.GET(securePath, handler.AdminAppPage) router.GET(securePath+"/", handler.AdminAppPage) router.GET(securePath+"/plugin-panel/:kind", handler.AdminPluginPanelPage) router.GET(securePath+"/plugins/:plugin", handler.AdminAppPage) router.NoRoute(func(c *gin.Context) { path := c.Request.URL.Path if path == securePath || strings.HasPrefix(path, securePath+"/") { handler.AdminAppPage(c) return } c.JSON(404, gin.H{"message": "not found"}) }) }