diff --git a/.agent/skills/new-api/SKILL.md b/.agent/skills/new-api/SKILL.md index deb93bd5..556e2eba 100644 --- a/.agent/skills/new-api/SKILL.md +++ b/.agent/skills/new-api/SKILL.md @@ -17,8 +17,9 @@ description: "Wavelet 项目专用:当新增或修改自定义业务 API、新 | 目录/包名 | 职责定位 | 框架依赖限制 | 常见包含内容 | | :--- | :--- | :--- | :--- | -| **`internal/router/`** | 核心路由入口 | 依赖 Gin 框架 | `router.go` (全局路由配置与服务启动) | -| **`internal/router/v1/`** | 路由分发层 | 依赖 Gin 框架 | `admin.go`, `user.go`, `public.go`, `custom.go` (各分类路由注册入口) | +| **`internal/router/`** | 核心路由入口 | 无/仅用于启动服务 | `router.go` (全局路由配置与服务启动) | +| **`internal/router/root/`** | 根路径路由注册包 | 依赖 Gin 框架 | `root.go` (统一入口), `frontend.go` (前端服务), `default.go` (默认根路由,包含 Swagger、文件服务及 robots.txt), `custom.go` (根路径自定义路由) | +| **`internal/router/v1/`** | 路由分发层 | 依赖 Gin 框架 | `v1.go` (v1 路由统一入口), `admin.go`, `user.go`, `public.go`, `custom.go` (各分类路由注册实现) | | **`internal/apps/custom/`** | 功能模块层 (Feature Module) | 依赖 Gin 框架 (仅限路由/Handler 部分) | 高度内聚的业务功能块。接收 HTTP 请求、解析请求体、校验基础参数、提取 Session。业务服务逻辑直接实现在当前目录下的 `service.go` 或 `logics.go` 中,不依赖 Gin/HTTP 框架。 | | **`internal/model/`** | 数据模型层 | 依赖 GORM / SQL 基础 | GORM 实体定义、表结构、主键生成、单表极简 SQL 查询方法。 | | **`internal/db/`** | 数据存储层 | 依赖 SQL 驱动 / GORM 连接 | PostgreSQL, SQLite 等数据库连接管理与 Goose 数据库迁移文件。 | @@ -33,8 +34,14 @@ description: "Wavelet 项目专用:当新增或修改自定义业务 API、新 internal/ ├── router/ │ ├── router.go # 核心路由入口,委派路由给各分类 +│ ├── root/ # 根路径路由注册包 +│ │ ├── root.go # 统一路由注册入口 +│ │ ├── frontend.go # 前端静态服务注册(支持 embed_frontend 编译标签) +│ │ ├── default.go # 默认根路由注册(/f/:id, /robots.txt, Swagger 等) +│ │ └── custom.go # [修改/创建] 仅用于注册挂载到根路径的自定义路由 │ └── v1/ -│ └── custom.go # [修改/创建] 仅用于注册定制路由,将路由委托给 apps/custom +│ ├── v1.go # [新建] v1 路由统一入口,集中注册 v1 下的所有路径 +│ └── custom.go # [修改/创建] 仅用于注册 v1 下的定制路由,将路由委托给 apps/custom └── apps/ └── custom/ ├── routers.go # [新建] HTTP Handlers (Gin),负责参数绑定、校验与响应 @@ -76,7 +83,25 @@ func RegisterCustomRoutes(apiV1Router *gin.RouterGroup) { } } ``` -并在 [router.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/router.go) 中的 `/v1` 路由组内通过 `v1.RegisterCustomRoutes(apiV1Router)` 进行调用。 +并在 [v1.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/v1.go) 中调用 `RegisterCustomRoutes(apiV1Router)`。 +最后在 [router.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/router.go) 中的 `/v1` 路由组内通过 `v1.RegisterV1Routes(apiV1Router, apiGroup)` 统一加载。 + +### 步骤 4.2:若需要注册根路径下的自定义路由 (Root Custom Routes) +若自定义接口需要挂载到根路径(例如 `/my-custom-endpoint`),则不应该挂载在 `/v1` 下,而是应当在 [custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go) 中进行注册: +```go +package root + +import ( + "github.com/Rain-kl/Wavelet/internal/apps/custom" + "github.com/gin-gonic/gin" +) + +// RegisterCustomRootRoutes registers custom business routes that belong to the root path. +func RegisterCustomRootRoutes(r *gin.Engine) { + r.GET("/my-custom-endpoint", custom.DoRootActionHandler) +} +``` +并在 [root.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/root.go) 内被统一调用(由核心路由 `router.go` 间接调用)。 --- diff --git a/.gitignore b/.gitignore index a7af12bd..f7d24dad 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ s3_cache /uploads/ /*-source/ /.cache/ +/internal/router/root/dist/ diff --git a/internal/router/root/custom.go b/internal/router/root/custom.go new file mode 100644 index 00000000..c34b2222 --- /dev/null +++ b/internal/router/root/custom.go @@ -0,0 +1,14 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +// Package root registers custom business routes and frontend serving. +package root + +import ( + "github.com/gin-gonic/gin" +) + +// RegisterCustomRootRoutes registers custom business routes that belong to the root path. +func RegisterCustomRootRoutes(_ *gin.Engine) { + // Add custom root routes here +} diff --git a/internal/router/root/default.go b/internal/router/root/default.go new file mode 100644 index 00000000..61616fe4 --- /dev/null +++ b/internal/router/root/default.go @@ -0,0 +1,28 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package root + +import ( + _ "github.com/Rain-kl/Wavelet/docs" // Swagger documentation generation setup + publicconfig "github.com/Rain-kl/Wavelet/internal/apps/config" + "github.com/Rain-kl/Wavelet/internal/apps/upload" + "github.com/Rain-kl/Wavelet/internal/config" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +// RegisterDefaultRootRoutes registers default routes that belong to the root path. +func RegisterDefaultRootRoutes(r *gin.Engine) { + // 1. Serve files by ID + r.GET("/f/:id", upload.ServeFileByID) + + // 2. Dynamic robots.txt serving + r.GET("/robots.txt", publicconfig.GetRobotsTXT) + + // 3. Swagger routes (Non-production only) + if !config.Config.App.IsProduction() { + r.GET(config.Config.App.APIPrefix+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + } +} diff --git a/internal/router/frontend.go b/internal/router/root/frontend.go similarity index 97% rename from internal/router/frontend.go rename to internal/router/root/frontend.go index 4ee51c37..5efe4218 100644 --- a/internal/router/frontend.go +++ b/internal/router/root/frontend.go @@ -3,7 +3,7 @@ // Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 -package router +package root import ( "embed" @@ -45,7 +45,7 @@ func serveFileDirect(c *gin.Context, subFS fs.FS, filePath string) bool { } func init() { - registerFrontend = func(r *gin.Engine) { + RegisterFrontend = func(r *gin.Engine) { subFS, err := fs.Sub(frontendFS, "dist") if err != nil { panic(err) diff --git a/internal/router/root/root.go b/internal/router/root/root.go new file mode 100644 index 00000000..6eca0c49 --- /dev/null +++ b/internal/router/root/root.go @@ -0,0 +1,26 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +// Package root registers custom business routes and frontend serving. +package root + +import ( + "github.com/gin-gonic/gin" +) + +// RegisterFrontend is a package-level variable overridden by frontend.go when built with embed_frontend. +var RegisterFrontend = func(_ *gin.Engine) { + // No-op by default +} + +// RegisterRootRoutes registers custom business routes that belong to the root path. +func RegisterRootRoutes(r *gin.Engine) { + // 1. Default root routes (/f/:id, /robots.txt, and /swagger/*any) + RegisterDefaultRootRoutes(r) + + // 2. Register custom serving + RegisterCustomRootRoutes(r) + + // 3. Register frontend serving + RegisterFrontend(r) +} diff --git a/internal/router/router.go b/internal/router/router.go index a99aac89..6e3ef722 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -16,13 +16,11 @@ import ( "time" admin_push "github.com/Rain-kl/Wavelet/internal/apps/admin/push" - publicconfig "github.com/Rain-kl/Wavelet/internal/apps/config" "github.com/Rain-kl/Wavelet/internal/apps/risk_control" - "github.com/Rain-kl/Wavelet/internal/apps/upload" + router_root "github.com/Rain-kl/Wavelet/internal/router/root" v1 "github.com/Rain-kl/Wavelet/internal/router/v1" // Swagger 文档生成 - _ "github.com/Rain-kl/Wavelet/docs" _ "github.com/Rain-kl/Wavelet/internal/apps/admin/push/custom_events" "github.com/Rain-kl/Wavelet/internal/apps/oauth" "github.com/Rain-kl/Wavelet/internal/config" @@ -30,8 +28,6 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" - swaggerFiles "github.com/swaggo/files" - ginSwagger "github.com/swaggo/gin-swagger" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) @@ -123,40 +119,15 @@ func Serve() { } func registerRoutes(r *gin.Engine) { - // Serve files by ID - r.GET("/f/:id", upload.ServeFileByID) - - // Dynamic robots.txt serving - r.GET("/robots.txt", publicconfig.GetRobotsTXT) + // Register custom root routes, Swagger, and frontend serving + router_root.RegisterRootRoutes(r) apiGroup := r.Group(config.Config.App.APIPrefix) { - if !config.Config.App.IsProduction() { - // Swagger - apiGroup.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) - } - // API V1 apiV1Router := apiGroup.Group("/v1") { - // Public (captcha, health, config) - v1.RegisterPublicRoutes(apiV1Router, apiGroup) - - // OAuth, User, Upload - v1.RegisterUserRoutes(apiV1Router) - - // Admin - v1.RegisterAdminRoutes(apiV1Router) - - // Register custom business routes - v1.RegisterCustomRoutes(apiV1Router) + v1.RegisterV1Routes(apiV1Router, apiGroup) } } - - // 注册前端静态路由(当启用 embed_frontend 编译标签时) - registerFrontend(r) -} - -var registerFrontend = func(_ *gin.Engine) { - // No-op when not embedding frontend } diff --git a/internal/router/v1/admin.go b/internal/router/v1/admin.go index a7777aff..4aa631ab 100644 --- a/internal/router/v1/admin.go +++ b/internal/router/v1/admin.go @@ -12,136 +12,206 @@ import ( admin_logs "github.com/Rain-kl/Wavelet/internal/apps/admin/logs" admin_push "github.com/Rain-kl/Wavelet/internal/apps/admin/push" admin_status "github.com/Rain-kl/Wavelet/internal/apps/admin/status" + "github.com/Rain-kl/Wavelet/internal/apps/admin/system_config" admin_task "github.com/Rain-kl/Wavelet/internal/apps/admin/task" admin_template "github.com/Rain-kl/Wavelet/internal/apps/admin/template" admin_updater "github.com/Rain-kl/Wavelet/internal/apps/admin/updater" admin_user "github.com/Rain-kl/Wavelet/internal/apps/admin/user" - "github.com/Rain-kl/Wavelet/internal/apps/admin/system_config" "github.com/Rain-kl/Wavelet/internal/apps/oauth" "github.com/Rain-kl/Wavelet/internal/apps/upload" "github.com/gin-gonic/gin" ) -// RegisterAdminRoutes registers all admin-related routes. +// RegisterAdminRoutes registers all admin-related routes with sub-group categorizations. func RegisterAdminRoutes(apiV1Router *gin.RouterGroup) { adminRouter := apiV1Router.Group("/admin") adminRouter.Use(oauth.LoginRequired(), admin.LoginAdminRequired()) { - // System status - adminRouter.GET("/status", admin_status.GetSystemStatus) + // 1. Diagnostics & Infrastructure Management + registerAdminDiagnosticRoutes(adminRouter) - // Database info & export - adminRouter.GET("/db-info", admin_status.GetDatabaseInfo) - adminRouter.GET("/db-export", admin_status.ExportDatabase) + // 2. Identity & Access Management (IAM) + registerAdminIAMRoutes(adminRouter) - // Database management - adminRouter.GET("/db-manage/overview", admin_db_manage.GetDBOverview) - adminRouter.GET("/db-manage/tables", admin_db_manage.ListDBTables) - adminRouter.GET("/db-manage/table-data", admin_db_manage.GetDBTableData) - adminRouter.POST("/db-manage/query", admin_db_manage.ExecuteSQL) + // 3. System Configuration & Templates Settings + registerAdminConfigRoutes(adminRouter) - // Cache management - adminRouter.GET("/cache/status", admin_cache.GetCacheStatus) - adminRouter.POST("/cache/config", admin_cache.UpdateCacheConfig) - adminRouter.POST("/cache/clear", admin_cache.ClearCache) + // 4. Storage & Asset Management + registerAdminStorageRoutes(adminRouter) - // Application update - adminRouter.GET("/update", admin_updater.GetUpdateStatus) - adminRouter.POST("/update/apply", admin_updater.ApplyUpdate) - - // System logs - adminRouter.GET("/logs", admin_logs.GetLogs) - adminRouter.GET("/logs/access", admin_logs.GetAccessLogs) - adminRouter.GET("/logs/analytics", admin_logs.GetLogsAnalytics) - adminRouter.GET("/logs/ws", admin_logs.HandleLogWebSocket) - - // Task dispatch + // 5. Task Orchestration & Automation registerAdminTaskRoutes(adminRouter) - // Users - adminRouter.GET("/users", admin_user.ListUsers) - adminRouter.POST("/users", admin_user.CreateUser) - adminRouter.GET("/users/:id", admin_user.GetUser) - adminRouter.PUT("/users/:id/status", admin_user.UpdateUserStatus) - adminRouter.DELETE("/users/:id", admin_user.DeleteUser) + // 6. Messaging & Push Notifications + registerAdminPushRoutes(adminRouter) + } +} - // Uploads - registerAdminUploadRoutes(adminRouter) +// registerAdminDiagnosticRoutes registers infrastructure, system status, caching, database, and logs diagnostics. +func registerAdminDiagnosticRoutes(adminRouter *gin.RouterGroup) { + // System status + adminRouter.GET("/status", admin_status.GetSystemStatus) - // System Config - adminRouter.POST("/system-configs", system_config.CreateSystemConfig) - adminRouter.GET("/system-configs", system_config.ListSystemConfigs) - adminRouter.POST("/system-configs/smtp/test", system_config.TestSMTP) + // Database basic info & backup export + adminRouter.GET("/db-info", admin_status.GetDatabaseInfo) + adminRouter.GET("/db-export", admin_status.ExportDatabase) - systemConfigRouter := adminRouter.Group("/system-configs/:key") + // Database management & interactive browser + dbManage := adminRouter.Group("/db-manage") + { + dbManage.GET("/overview", admin_db_manage.GetDBOverview) + dbManage.GET("/tables", admin_db_manage.ListDBTables) + dbManage.GET("/table-data", admin_db_manage.GetDBTableData) + dbManage.POST("/query", admin_db_manage.ExecuteSQL) + } + + // Cache management (TTL, LRU eviction and clear operations) + cache := adminRouter.Group("/cache") + { + cache.GET("/status", admin_cache.GetCacheStatus) + cache.POST("/config", admin_cache.UpdateCacheConfig) + cache.POST("/clear", admin_cache.ClearCache) + } + + // Application updater + update := adminRouter.Group("/update") + { + update.GET("", admin_updater.GetUpdateStatus) + update.POST("/apply", admin_updater.ApplyUpdate) + } + + // System & access logs analytics + logs := adminRouter.Group("/logs") + { + logs.GET("", admin_logs.GetLogs) + logs.GET("/access", admin_logs.GetAccessLogs) + logs.GET("/analytics", admin_logs.GetLogsAnalytics) + logs.GET("/ws", admin_logs.HandleLogWebSocket) + } +} + +// registerAdminIAMRoutes registers Identity & Access Management endpoints (Users & Auth Sources). +func registerAdminIAMRoutes(adminRouter *gin.RouterGroup) { + // Users management + users := adminRouter.Group("/users") + { + users.GET("", admin_user.ListUsers) + users.POST("", admin_user.CreateUser) + users.GET("/:id", admin_user.GetUser) + users.PUT("/:id/status", admin_user.UpdateUserStatus) + users.DELETE("/:id", admin_user.DeleteUser) + } + + // Authentication Sources (LDAP, OAuth sources, etc.) + authSources := adminRouter.Group("/auth-sources") + { + authSources.GET("", admin_auth_source.ListAuthSources) + authSources.POST("", admin_auth_source.CreateAuthSource) + authSources.PUT("/:id", admin_auth_source.UpdateAuthSource) + authSources.PUT("/:id/toggle", admin_auth_source.ToggleAuthSource) + authSources.DELETE("/:id", admin_auth_source.DeleteAuthSource) + } +} + +// registerAdminConfigRoutes registers system configurations and template settings. +func registerAdminConfigRoutes(adminRouter *gin.RouterGroup) { + // System configs + configs := adminRouter.Group("/system-configs") + { + configs.GET("", system_config.ListSystemConfigs) + configs.POST("", system_config.CreateSystemConfig) + configs.POST("/smtp/test", system_config.TestSMTP) + + keyGroup := configs.Group("/:key") { - systemConfigRouter.GET("", system_config.GetSystemConfig) - systemConfigRouter.PUT("", system_config.UpdateSystemConfig) + keyGroup.GET("", system_config.GetSystemConfig) + keyGroup.PUT("", system_config.UpdateSystemConfig) + } + } + + // Email/Notification Templates + templates := adminRouter.Group("/templates") + { + templates.GET("", admin_template.ListTemplates) + templates.POST("", admin_template.CreateTemplate) + + keyGroup := templates.Group("/:key") + { + keyGroup.GET("", admin_template.GetTemplate) + keyGroup.PUT("", admin_template.UpdateTemplate) + keyGroup.DELETE("", admin_template.DeleteTemplate) + } + } +} + +// registerAdminStorageRoutes registers file and asset storage administration. +func registerAdminStorageRoutes(adminRouter *gin.RouterGroup) { + uploads := adminRouter.Group("/uploads") + { + uploads.GET("", upload.ListFiles) + uploads.GET("/stats", upload.GetFileStats) + uploads.DELETE("/:id", upload.DeleteFile) + uploads.GET("/download/:id", upload.DownloadFile) + uploads.POST("/download/batch", upload.BatchDownloadFiles) + uploads.GET("/types", upload.GetDistinctUploadTypes) + } +} + +// registerAdminTaskRoutes registers task orchestrations, execution logs and schedules. +func registerAdminTaskRoutes(adminRouter *gin.RouterGroup) { + tasks := adminRouter.Group("/tasks") + { + // Task dispatch & metadata + tasks.GET("/types", admin_task.ListTaskTypes) + tasks.POST("/dispatch", admin_task.DispatchTask) + + // Task execution logs & manual retry + executions := tasks.Group("/executions") + { + executions.GET("", admin_task.ListTaskExecutions) + executions.GET("/:id", admin_task.GetTaskExecution) + executions.POST("/:id/retry", admin_task.RetryTask) } - // Templates - adminRouter.GET("/templates", admin_template.ListTemplates) - adminRouter.POST("/templates", admin_template.CreateTemplate) - - templateRouter := adminRouter.Group("/templates/:key") + // Cron scheduler settings + schedules := tasks.Group("/schedules") { - templateRouter.GET("", admin_template.GetTemplate) - templateRouter.PUT("", admin_template.UpdateTemplate) - templateRouter.DELETE("", admin_template.DeleteTemplate) + schedules.GET("", admin_task.ListSchedules) + schedules.POST("", admin_task.CreateSchedule) + schedules.PUT("/:id", admin_task.UpdateSchedule) + schedules.DELETE("/:id", admin_task.DeleteSchedule) + } + } +} + +// registerAdminPushRoutes registers messaging channels and push notification events. +func registerAdminPushRoutes(adminRouter *gin.RouterGroup) { + push := adminRouter.Group("/push") + { + // Push Events + events := push.Group("/events") + { + events.GET("", admin_push.ListEvents) + events.GET("/builtin", admin_push.ListBuiltInEvents) + events.POST("", admin_push.CreateEvent) + events.PUT("/:id", admin_push.UpdateEvent) + events.DELETE("/:id", admin_push.DeleteEvent) + events.POST("/:id/toggle", admin_push.ToggleEvent) } - // Auth Sources - adminRouter.GET("/auth-sources", admin_auth_source.ListAuthSources) - adminRouter.POST("/auth-sources", admin_auth_source.CreateAuthSource) - adminRouter.PUT("/auth-sources/:id", admin_auth_source.UpdateAuthSource) - adminRouter.PUT("/auth-sources/:id/toggle", admin_auth_source.ToggleAuthSource) - adminRouter.DELETE("/auth-sources/:id", admin_auth_source.DeleteAuthSource) - - // Push Notifications - adminRouter.GET("/push/events", admin_push.ListEvents) - adminRouter.GET("/push/events/builtin", admin_push.ListBuiltInEvents) - adminRouter.POST("/push/events", admin_push.CreateEvent) - adminRouter.PUT("/push/events/:id", admin_push.UpdateEvent) - adminRouter.DELETE("/push/events/:id", admin_push.DeleteEvent) - adminRouter.POST("/push/events/:id/toggle", admin_push.ToggleEvent) - adminRouter.GET("/push/histories", admin_push.ListHistories) - adminRouter.POST("/push/test", admin_push.TestPush) + // Delivery histories and diagnostics test + push.GET("/histories", admin_push.ListHistories) + push.POST("/test", admin_push.TestPush) // Message Channels CRUD - adminRouter.GET("/push/channels/definitions", admin_push.ListChannelDefinitions) - adminRouter.GET("/push/channels", admin_push.ListChannels) - adminRouter.POST("/push/channels", admin_push.CreateChannel) - adminRouter.PUT("/push/channels/:id", admin_push.UpdateChannel) - adminRouter.DELETE("/push/channels/:id", admin_push.DeleteChannel) - adminRouter.POST("/push/channels/test", admin_push.TestChannel) - } -} - -func registerAdminTaskRoutes(adminRouter *gin.RouterGroup) { - // Task dispatch - adminRouter.GET("/tasks/types", admin_task.ListTaskTypes) - adminRouter.POST("/tasks/dispatch", admin_task.DispatchTask) - - // Task executions - adminRouter.GET("/tasks/executions", admin_task.ListTaskExecutions) - adminRouter.GET("/tasks/executions/:id", admin_task.GetTaskExecution) - adminRouter.POST("/tasks/executions/:id/retry", admin_task.RetryTask) - - // Task schedules - adminRouter.GET("/tasks/schedules", admin_task.ListSchedules) - adminRouter.POST("/tasks/schedules", admin_task.CreateSchedule) - adminRouter.PUT("/tasks/schedules/:id", admin_task.UpdateSchedule) - adminRouter.DELETE("/tasks/schedules/:id", admin_task.DeleteSchedule) -} - -func registerAdminUploadRoutes(adminRouter *gin.RouterGroup) { - adminUploadsRouter := adminRouter.Group("/uploads") - { - adminUploadsRouter.GET("", upload.ListFiles) - adminUploadsRouter.GET("/stats", upload.GetFileStats) - adminUploadsRouter.DELETE("/:id", upload.DeleteFile) - adminUploadsRouter.GET("/download/:id", upload.DownloadFile) - adminUploadsRouter.POST("/download/batch", upload.BatchDownloadFiles) - adminUploadsRouter.GET("/types", upload.GetDistinctUploadTypes) + channels := push.Group("/channels") + { + channels.GET("/definitions", admin_push.ListChannelDefinitions) + channels.GET("", admin_push.ListChannels) + channels.POST("", admin_push.CreateChannel) + channels.PUT("/:id", admin_push.UpdateChannel) + channels.DELETE("/:id", admin_push.DeleteChannel) + channels.POST("/test", admin_push.TestChannel) + } } } diff --git a/internal/router/v1/v1.go b/internal/router/v1/v1.go new file mode 100644 index 00000000..c8681d56 --- /dev/null +++ b/internal/router/v1/v1.go @@ -0,0 +1,24 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +// Package v1 contains router registrations for API V1 +package v1 + +import ( + "github.com/gin-gonic/gin" +) + +// RegisterV1Routes registers all routes under API V1. +func RegisterV1Routes(apiV1Router *gin.RouterGroup, apiGroup *gin.RouterGroup) { + // 1. Public (captcha, health, config) + RegisterPublicRoutes(apiV1Router, apiGroup) + + // 2. OAuth, User, Upload + RegisterUserRoutes(apiV1Router) + + // 3. Admin + RegisterAdminRoutes(apiV1Router) + + // 4. Register custom business routes + RegisterCustomRoutes(apiV1Router) +}