package main import ( "log" "net/http" "os" "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "sproutgate-backend/internal/handlers" "sproutgate-backend/internal/storage" ) func main() { dataDir := os.Getenv("DATA_DIR") store, err := storage.NewStore(dataDir) if err != nil { log.Fatalf("failed to init storage: %v", err) } router := gin.Default() router.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Admin-Token", "X-Visit-Ip", "X-Visit-Location", "X-Auth-Client", "X-Auth-Client-Name"}, MaxAge: 12 * time.Hour, })) handler := handlers.NewHandler(store) apiIntro := gin.H{ "name": "SproutGate API", "title": "萌芽账户认证中心", "description": "统一认证、用户资料、每日签到、公开用户主页与管理端等 JSON HTTP 接口。", "version": "0.1.0", "links": gin.H{ "apiDocs": "GET /api/docs — Markdown 接口说明(本仓库 API_DOCS.md)", "health": "GET /api/health", }, "routePrefixes": []string{ "/api/auth — 登录、注册、邮箱验证、令牌校验、当前用户、资料、签到、辅助邮箱;可选 X-Auth-Client 记录应用接入", "/api/public — 公开用户资料、注册策略(是否强制邀请码)", "/api/admin — 用户 CRUD、签到与注册/邀请码配置(请求头 X-Admin-Token 或 Query token)", }, } router.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, apiIntro) }) router.GET("/api", func(c *gin.Context) { c.JSON(http.StatusOK, apiIntro) }) router.GET("/api/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "dataDir": store.DataDir(), }) }) router.GET("/api/docs", func(c *gin.Context) { c.File("API_DOCS.md") }) router.POST("/api/auth/login", handler.Login) router.POST("/api/auth/register", handler.Register) router.POST("/api/auth/verify-email", handler.VerifyEmail) router.POST("/api/auth/forgot-password", handler.ForgotPassword) router.POST("/api/auth/reset-password", handler.ResetPassword) router.POST("/api/auth/secondary-email/request", handler.RequestSecondaryEmail) router.POST("/api/auth/secondary-email/verify", handler.VerifySecondaryEmail) router.POST("/api/auth/verify", handler.Verify) router.GET("/api/auth/me", handler.Me) router.POST("/api/auth/check-in", handler.CheckIn) router.PUT("/api/auth/profile", handler.UpdateProfile) router.GET("/api/public/users/:account", handler.GetPublicUser) router.GET("/api/public/registration-policy", handler.GetPublicRegistrationPolicy) admin := router.Group("/api/admin") admin.Use(handler.AdminMiddleware()) admin.GET("/users", handler.ListUsers) admin.POST("/users", handler.CreateUser) admin.PUT("/users/:account", handler.UpdateUser) admin.DELETE("/users/:account", handler.DeleteUser) admin.GET("/check-in/config", handler.GetCheckInConfig) admin.PUT("/check-in/config", handler.UpdateCheckInConfig) admin.GET("/registration", handler.GetAdminRegistration) admin.PUT("/registration", handler.PutAdminRegistrationPolicy) admin.POST("/registration/invites", handler.PostAdminInvite) admin.DELETE("/registration/invites/:code", handler.DeleteAdminInvite) port := os.Getenv("PORT") if port == "" { port = "8080" } if err := router.Run(":" + port); err != nil { log.Fatalf("server stopped: %v", err) } }