package router import ( "net/http" "github.com/gin-gonic/gin" "gorm.io/gorm" "nl-pms-api/internal/config" "nl-pms-api/internal/controller" "nl-pms-api/internal/middleware" "nl-pms-api/internal/service" ) // New 组装路由:公开 healthz/auth,JWT 保护同步/设置/资料/团队/文件/后台。 // 若配置了 base_path(如 /pms-api),全部路由挂在该前缀下,适配不剥路径的反代。 func New(cfg *config.Config, db *gorm.DB) *gin.Engine { if cfg.Env != "dev" { gin.SetMode(gin.ReleaseMode) } r := gin.New() r.Use(gin.Logger(), gin.Recovery(), middleware.AttachClientIP()) r.MaxMultipartMemory = 32 << 20 if len(cfg.TrustedProxies) > 0 { _ = r.SetTrustedProxies(cfg.TrustedProxies) } else { // 空切片=不信任任何反代,ClientIP 用 RemoteAddr(避免默认信任全部) _ = r.SetTrustedProxies([]string{}) } root := r.Group(cfg.BasePath) root.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true, "service": "nl-pms-api"}) }) authSvc := &service.AuthService{DB: db, Cfg: cfg} authC := &controller.AuthController{Svc: authSvc} syncC := &controller.SyncController{Svc: &service.SyncService{DB: db}} settingsC := &controller.SettingsController{Svc: &service.SettingsService{DB: db}} profileC := &controller.ProfileController{ Svc: &service.ProfileService{DB: db}, AvatarHist: &service.AvatarHistoryService{DB: db}, } noticeC := &controller.NoticeController{Svc: &service.NoticeService{DB: db}} fileC := &controller.FileController{Svc: &service.FileService{DB: db, Cfg: cfg}} teamC := &controller.TeamController{Svc: &service.TeamService{DB: db}} adminSvc := &service.AdminService{DB: db} secSvc := &service.AdminSecurityService{DB: db} relSvc := &service.ReleaseService{DB: db, Cfg: cfg} actSvc := &service.ActivityService{DB: db} adminC := &controller.AdminController{Admin: adminSvc, Sec: secSvc, Rel: relSvc, Act: actSvc} actC := &controller.ActivityController{Svc: actSvc} aiC := &controller.AIController{Admin: adminSvc} appC := &controller.AppController{Rel: relSvc} pub := root.Group("/api/v1") pub.POST("/auth/register", authC.Register) pub.POST("/auth/login", authC.Login) pub.POST("/auth/refresh", authC.Refresh) api := root.Group("/api/v1", middleware.RequireJWT(cfg.JWTSecret), middleware.RejectDisabled(db)) api.POST("/auth/change-password", authC.ChangePassword) api.POST("/sync/push", syncC.Push) api.GET("/sync/pull", syncC.Pull) api.GET("/settings", settingsC.Get) // ?prefix=fest_img: api.GET("/settings/global/:name", settingsC.GetGlobal) api.GET("/settings/:name", settingsC.Get) api.PUT("/settings/:name", middleware.RequireStepUpForGlobalSetting(db), settingsC.Put) api.GET("/profile", profileC.Get) api.PUT("/profile", profileC.Put) api.GET("/profile/avatars", profileC.ListAvatars) api.POST("/profile/avatars", profileC.PushAvatar) api.DELETE("/profile/avatars/:id", profileC.DeleteAvatar) api.GET("/notices", noticeC.List) api.POST("/teams", teamC.Create) api.GET("/teams", teamC.List) api.PUT("/teams/:id", teamC.Rename) api.PUT("/teams/:id/digest-time", teamC.SetDigestTime) api.DELETE("/teams/:id", teamC.Dissolve) api.POST("/teams/:id/leave", teamC.Leave) api.GET("/teams/:id/members", teamC.Members) api.POST("/teams/:id/invite", teamC.Invite) api.PUT("/teams/:id/members/:userId/role", teamC.SetRole) api.DELETE("/teams/:id/members/:userId", teamC.RemoveMember) api.POST("/teams/:id/tasks", teamC.TaskSave) api.PUT("/teams/:id/tasks/:taskId", teamC.TaskSave) api.PUT("/teams/:id/tasks/:taskId/status", teamC.TaskSetStatus) api.POST("/teams/:id/tasks/:taskId/urge", teamC.TaskUrge) api.DELETE("/teams/:id/tasks/:taskId", teamC.TaskDelete) api.GET("/teams/:id/tasks", teamC.TaskList) api.GET("/teams/:id/shared", teamC.SharedItems) api.POST("/teams/:id/shared/urge", teamC.UrgeShared) api.POST("/teams/:id/reports", teamC.ReportSubmit) api.GET("/teams/:id/reports/:date", teamC.ReportBoardGet) api.POST("/teams/:id/reports/urge", teamC.ReportUrge) api.PUT("/teams/:id/digests/:date", teamC.DigestSave) api.POST("/files", fileC.Upload) api.GET("/files", fileC.List) api.DELETE("/files/:id", fileC.Delete) api.POST("/activity/ping", actC.Ping) api.GET("/ai/policy", aiC.Policy) api.POST("/ai/usage", aiC.Usage) api.GET("/app/latest", appC.Latest) api.GET("/app/download/:version", appC.Download) admin := api.Group("/admin", middleware.RequireAdmin()) admin.GET("/totp/status", adminC.TOTPStatus) admin.POST("/totp/setup", adminC.TOTPSetupBegin) admin.POST("/totp/confirm", adminC.TOTPSetupConfirm) admin.POST("/stepup", adminC.StepUp) admin.GET("/stats/overview", adminC.Overview) admin.GET("/users", adminC.ListUsers) admin.GET("/teams", adminC.ListTeams) admin.GET("/releases", adminC.ListReleases) adminWrite := admin.Group("", middleware.RequireAdminStepUp(db)) adminWrite.PATCH("/users/:id", adminC.PatchUser) adminWrite.PATCH("/teams/:id", adminC.PatchTeam) adminWrite.POST("/releases", adminC.UploadRelease) adminWrite.POST("/releases/:id/publish", adminC.PublishRelease) files := root.Group("/files", func(c *gin.Context) { c.Header("Cache-Control", "public, max-age=31536000, immutable") }) files.Static("/", cfg.StorageDir) return r }