// Package api 负责 HTTP 接口路由装配。 package api import ( "net/http" "path" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "gorm.io/gorm" "rill/internal/auth" "rill/internal/avatar" "rill/internal/config" "rill/internal/database" "rill/internal/file" "rill/internal/nav" "rill/internal/note" "rill/internal/site" "rill/internal/user" "rill/internal/usergroup" ) // RegisterRoutes 注册 API 路由。health、swagger、auth、站点信息、文件查看与头部导航读取公开;notes、个人资料与文件上传删除需登录;站点信息更新、导航维护、用户与用户组管理仅限管理员。 func RegisterRoutes(rg *gin.RouterGroup, db *gorm.DB, cfg *config.Config) { authn := auth.NewAuthenticator(cfg) rg.GET("/health", health(db)) swagger := rg.Group("/swagger") { swagger.GET("", func(c *gin.Context) { c.Redirect(http.StatusFound, path.Join("/", rg.BasePath(), "swagger", "index.html")) }) swagger.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("doc.json"))) } authGroup := rg.Group("/auth") { authGroup.POST("/register", auth.Register(db)) authGroup.POST("/login", auth.Login(db, authn)) } rg.GET("/site", site.Get(db)) rg.GET("/files/:id", file.View(db, cfg)) rg.GET("/nav-links", nav.List(db)) authed := rg.Group("", authn.RequireAuth(db)) { authed.GET("/me", auth.Me()) authed.PUT("/me", auth.UpdateMe(db)) authed.PUT("/me/avatar", avatar.Update(db, cfg)) authed.DELETE("/me/avatar", avatar.Delete(db, cfg)) authed.POST("/files", file.Upload(db, cfg)) authed.DELETE("/files/:id", file.Delete(db, cfg)) notes := authed.Group("/notes") { notes.GET("", note.List(db)) notes.POST("", note.Create(db)) notes.GET("/:id", note.Get(db)) notes.PUT("/:id", note.Update(db)) notes.DELETE("/:id", note.Delete(db)) } } admin := rg.Group("", authn.RequireAuth(db), auth.RequireAdmin()) { admin.PUT("/site", site.Update(db, cfg)) admin.PUT("/site/logo", site.UploadLogo(db, cfg)) admin.DELETE("/site/logo", site.DeleteLogo(db, cfg)) admin.PUT("/site/favicon", site.UploadFavicon(db, cfg)) admin.DELETE("/site/favicon", site.DeleteFavicon(db, cfg)) admin.GET("/nav-links/list", nav.ListAll(db)) admin.POST("/nav-links", nav.Create(db)) admin.PUT("/nav-links/:id", nav.Update(db)) admin.DELETE("/nav-links/:id", nav.Delete(db)) users := admin.Group("/users") { users.GET("", user.List(db)) users.POST("", user.Create(db)) users.GET("/:id", user.Get(db)) users.PUT("/:id", user.Update(db)) users.DELETE("/:id", user.Delete(db)) } groups := admin.Group("/user-groups") { groups.GET("", usergroup.List(db)) groups.POST("", usergroup.Create(db)) groups.GET("/:id", usergroup.Get(db)) groups.PUT("/:id", usergroup.Update(db)) groups.DELETE("/:id", usergroup.Delete(db)) } } } // HealthResponse 健康检查响应。 type HealthResponse struct { Status string `json:"status" example:"ok"` Error string `json:"error,omitempty" example:"database unavailable"` } // @Summary Health check // @Description Check service and database connectivity; returns 503 when the database is unavailable. // @Tags public // @Produce json // @Success 200 {object} api.HealthResponse // @Failure 503 {object} api.HealthResponse // @Router /health [get] func health(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { if err := database.Ping(c.Request.Context(), db); err != nil { c.JSON(http.StatusServiceUnavailable, HealthResponse{Status: "error", Error: "database unavailable"}) return } c.JSON(http.StatusOK, HealthResponse{Status: "ok"}) } }