Files
SproutGate/sproutgate-backend/main.go
2026-05-13 12:19:36 +08:00

172 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//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 JWTAuthorization 头填写「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)
}
}