172 lines
5.6 KiB
Go
172 lines
5.6 KiB
Go
//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)
|
||
}
|
||
}
|