//go:generate swag init -g main.go -o docs --parseInternal // @title 萌芽账户认证中心 API // @version 0.1.0 // @description 统一认证、用户资料、每日签到、公开用户主页与管理端等 JSON HTTP 接口。 // @host localhost:8080 // @BasePath / // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization // @description JWT:Authorization 头填写「Bearer 」+ token(示例:Bearer eyJ...)。 // @securityDefinitions.apikey AdminToken // @in header // @name X-Admin-Token // @description 管理端令牌;也可使用 Query `token` 或与部分客户端相同的 `Authorization` 头(见实现)。 // @schemes http https package main import ( "log" "net/http" "os" "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" _ "sproutgate-backend/docs" "sproutgate-backend/internal/database" "sproutgate-backend/internal/handlers" "sproutgate-backend/internal/storage" ) func apiIntroPayload() gin.H { return gin.H{ "name": "SproutGate API", "title": "萌芽账户认证中心", "description": "统一认证、用户资料、每日签到、公开用户主页与管理端等 JSON HTTP 接口。", "version": "0.1.0", "links": gin.H{ "health": "GET /api/health", "docs": "GET /swagger/index.html", }, "routePrefixes": []string{ "/api/auth — 登录、注册、邮箱验证、令牌校验、当前用户、资料、签到、辅助邮箱;可选 X-Auth-Client 记录应用接入", "/api/public — 公开用户目录与资料、主页点赞、注册策略", "/api/admin — 用户 CRUD、签到与注册/邀请码配置(请求头 X-Admin-Token 或 Query token)", }, } } // GetRoot // @Summary API 简介 JSON // @Description 与 GET /api 相同,返回名称、版本与路由前缀说明。 // @Tags meta // @Produce json // @Success 200 {object} map[string]interface{} // @Router / [get] func GetRoot(c *gin.Context) { c.JSON(http.StatusOK, apiIntroPayload()) } // GetAPI // @Summary API 简介 JSON // @Description 与 GET / 相同。 // @Tags meta // @Produce json // @Success 200 {object} map[string]interface{} // @Router /api [get] func GetAPI(c *gin.Context) { c.JSON(http.StatusOK, apiIntroPayload()) } // GetHealth // @Summary 健康检查 // @Tags meta // @Produce json // @Success 200 {object} map[string]interface{} // @Router /api/health [get] func GetHealth(c *gin.Context) { env := os.Getenv("APP_ENV") if env == "" { env = "development" } c.JSON(http.StatusOK, gin.H{ "status": "ok", "env": env, }) } func main() { db, err := database.Open() if err != nil { log.Fatalf("初始化数据库失败: %v", err) } store, err := storage.NewStore(db) if err != nil { log.Fatalf("初始化储存失败: %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) router.GET("/", GetRoot) router.GET("/api", GetAPI) router.GET("/api/health", GetHealth) router.GET("/api/docs", func(c *gin.Context) { c.Redirect(http.StatusFound, "/swagger/index.html") }) router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 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/auth/oauth/:provider/start", handler.OAuthStart) router.GET("/api/auth/oauth/:provider/callback", handler.OAuthCallback) router.POST("/api/auth/oauth/:provider/bind", handler.OAuthBindURL) router.DELETE("/api/auth/oauth/:provider", handler.OAuthUnlink) router.GET("/api/public/users", handler.ListPublicUsers) router.GET("/api/public/users/:account", handler.GetPublicUser) router.POST("/api/public/users/:account/like", handler.PostPublicProfileLike) 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) admin.GET("/oauth", handler.GetAdminOAuth) admin.PUT("/oauth", handler.PutAdminOAuth) admin.GET("/turnstile", handler.GetAdminTurnstile) admin.PUT("/turnstile", handler.PutAdminTurnstile) port := os.Getenv("PORT") if port == "" { port = "8080" } if err := router.Run(":" + port); err != nil { log.Fatalf("server stopped: %v", err) } }