Files
rill/internal/api/api.go
T
kevin 55d024ae6c 新增多级视频分类、内容分级与用户偏好
- 迁移 v13 新增 video_categories(parent_id/level/path/slug/rating/icon/cover)与 video_category_translations,最多 3 级
- 公开树接口只返回启用且祖先启用的节点;管理员可增删改查,支持移动防环、子树层级同步、有子禁止删除、图标封面引用计数
- 迁移 v14 为分类增加分级字段(G/PG/PG13/NC16/M18/R21 = 0-5),存自身值并计算 effective_rating 向下覆盖;后台编辑器与树行展示覆盖提示
- 迁移 v15 为 users 增加 rating/locale/theme/theme_mode,PUT /me 支持部分更新与清空;前端登录/刷新应用账号偏好,切换语言/主题自动同步
- 分级常量统一为 model.Rating*,file.SyncRef 抽出供 site 与分类共用;后台新增视频分类管理页与三语文案
- 补充分类、分级、偏好相关测试,开发规范新增 4.1/4.2 说明,重新生成 Swagger 文档
2026-09-22 11:52:32 +08:00

130 lines
4.0 KiB
Go

// 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"
"rill/internal/videocategory"
)
// 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))
rg.GET("/video-categories", videocategory.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))
admin.GET("/video-categories/list", videocategory.ListAll(db))
admin.POST("/video-categories", videocategory.Create(db, cfg))
admin.PUT("/video-categories/:id", videocategory.Update(db, cfg))
admin.DELETE("/video-categories/:id", videocategory.Delete(db, cfg))
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"})
}
}