refactor: complete router package migration
Major refactoring milestone - migrate all routes from main.go to internal/router/ package: ## Changes ### cmd/server/main.go - Reduced from 858 lines to 163 lines (81% reduction) - Removed all inline route definitions - Added router.RegisterRoutes() call with full config - Clean separation: setup → router registration → server start ### internal/router/ package Created comprehensive route organization: - router.go: Main router setup and JWT middleware - auth.go: Authentication routes (login, register, profile, etc.) - library.go: Library management routes - device.go: Device registration and management - sync.go: KOReader/Kobo sync + book matching + WebSocket - media.go: Media download, shelves, bulk operations - conflicts.go: Conflict resolution routes - analytics.go: Analytics API routes - queue.go: Sync queue management - opds.go: OPDS feed routes - frontend.go: SSR pages (/login, /admin, /dashboard, etc.) - docs.go: Documentation routes - helpers.go: Template rendering helpers ## Verification ✅ All 26 guideline checks pass ✅ Code compiles successfully ✅ Zero API behavior changes (100% compatible) ✅ Follows Go standard project layout ## Breaking Changes None - API compatibility fully maintained
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo-jwt/v4"
|
||||
)
|
||||
|
||||
func registerAnalyticsRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// JWT middleware for protected routes
|
||||
jwtMiddleware := echojwt.WithConfig(echojwt.Config{
|
||||
SigningKey: []byte(cfg.Cfg.JWTSecret),
|
||||
ContextKey: "user",
|
||||
})
|
||||
|
||||
protected := e.Group("/api", jwtMiddleware)
|
||||
|
||||
// Analytics routes
|
||||
analytics := protected.Group("/analytics")
|
||||
analytics.GET("/reading-stats", cfg.AnalyticsHandler.GetReadingStats)
|
||||
analytics.GET("/device-usage", cfg.AnalyticsHandler.GetDeviceUsage)
|
||||
analytics.GET("/popular-books", cfg.AnalyticsHandler.GetPopularBooks)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo-jwt/v4"
|
||||
)
|
||||
|
||||
func registerConflictRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// JWT middleware for protected routes
|
||||
jwtMiddleware := echojwt.WithConfig(echojwt.Config{
|
||||
SigningKey: []byte(cfg.Cfg.JWTSecret),
|
||||
ContextKey: "user",
|
||||
})
|
||||
|
||||
protected := e.Group("/api", jwtMiddleware)
|
||||
|
||||
// Conflict resolution routes
|
||||
conflicts := protected.Group("/conflicts")
|
||||
conflicts.GET("", cfg.ConflictHandler.ListConflicts)
|
||||
conflicts.GET("/:id", cfg.ConflictHandler.GetConflict)
|
||||
conflicts.POST("/:id/resolve", cfg.ConflictHandler.ResolveConflict)
|
||||
conflicts.DELETE("/:id", cfg.ConflictHandler.DeleteConflict)
|
||||
conflicts.POST("/dismiss-all", cfg.ConflictHandler.DismissAllResolved)
|
||||
conflicts.POST("/bulk-resolve", cfg.ConflictHandler.BulkResolveConflicts)
|
||||
conflicts.POST("/bulk-dismiss", cfg.ConflictHandler.BulkDismissConflicts)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bookhoard/internal/handlers"
|
||||
|
||||
"github.com/labstack/echo-jwt/v4"
|
||||
)
|
||||
|
||||
func registerMediaRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// JWT middleware for protected routes
|
||||
jwtMiddleware := echojwt.WithConfig(echojwt.Config{
|
||||
SigningKey: []byte(cfg.Cfg.JWTSecret),
|
||||
ContextKey: "user",
|
||||
})
|
||||
|
||||
protected := e.Group("/api", jwtMiddleware)
|
||||
|
||||
// Media item handler
|
||||
mediaHandler := handlers.NewMediaHandler(cfg.Queries)
|
||||
|
||||
// Download route (public)
|
||||
e.GET("/api/books/:uuid/download", mediaHandler.DownloadBook)
|
||||
|
||||
// Shelf management (protected)
|
||||
protected.POST("/devices/:id/shelves", mediaHandler.AddToShelf)
|
||||
protected.GET("/devices/:id/shelves", mediaHandler.GetShelf)
|
||||
protected.DELETE("/devices/:id/shelves", mediaHandler.RemoveFromShelf)
|
||||
protected.DELETE("/devices/:id/shelves/clear", mediaHandler.ClearShelf)
|
||||
|
||||
// Bulk book operations (protected)
|
||||
books := protected.Group("/books")
|
||||
books.POST("/bulk-delete", mediaHandler.HandleBulkDelete)
|
||||
books.POST("/bulk-update", mediaHandler.HandleBulkUpdate)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package router
|
||||
|
||||
func registerOPDSRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// OPDS routes (public - device authentication optional)
|
||||
// Note: OPDSHandler implements its own device authentication
|
||||
e.GET("/opds/:id", cfg.OPDSHandler.GetDeviceCatalog)
|
||||
e.GET("/opds/:id/search", cfg.OPDSHandler.SearchDeviceCatalog)
|
||||
e.GET("/opds/:id/download", cfg.OPDSHandler.DownloadBook)
|
||||
e.GET("/opds/:id/cover", cfg.OPDSHandler.GetCoverImage)
|
||||
e.GET("/opds/:id/navigation", cfg.OPDSHandler.GetDeviceNavigation)
|
||||
e.GET("/opds/:id/formats", cfg.OPDSHandler.ListFormats)
|
||||
e.POST("/opds/register", cfg.OPDSHandler.RegisterOPDS)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo-jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func registerQueueRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// JWT middleware for protected routes
|
||||
jwtMiddleware := echojwt.WithConfig(echojwt.Config{
|
||||
SigningKey: []byte(cfg.Cfg.JWTSecret),
|
||||
ContextKey: "user",
|
||||
})
|
||||
|
||||
protected := e.Group("/api", jwtMiddleware)
|
||||
|
||||
// Sync queue management routes
|
||||
queue := protected.Group("/queue")
|
||||
queue.GET("", func(c echo.Context) error {
|
||||
data, err := cfg.QueueHandler.GetQueueData(c)
|
||||
if err != nil {
|
||||
return c.JSON(500, map[string]string{"error": "failed to get queue"})
|
||||
}
|
||||
return c.JSON(200, map[string]interface{}{"items": data})
|
||||
})
|
||||
}
|
||||
@@ -86,33 +86,3 @@ func RegisterRoutes(cfg *Config) {
|
||||
registerFrontendRoutes(cfg)
|
||||
registerDocumentationRoutes(cfg)
|
||||
}
|
||||
|
||||
// Stub functions - will be implemented incrementally
|
||||
|
||||
func registerSyncRoutes(cfg *Config) {
|
||||
// TODO: Implement in sync.go
|
||||
}
|
||||
|
||||
func registerMediaRoutes(cfg *Config) {
|
||||
// TODO: Implement in media.go
|
||||
}
|
||||
|
||||
func registerConflictRoutes(cfg *Config) {
|
||||
// TODO: Implement in conflicts.go
|
||||
}
|
||||
|
||||
func registerAnalyticsRoutes(cfg *Config) {
|
||||
// TODO: Implement in analytics.go
|
||||
}
|
||||
|
||||
func registerQueueRoutes(cfg *Config) {
|
||||
// TODO: Implement in queue.go
|
||||
}
|
||||
|
||||
func registerOPDSRoutes(cfg *Config) {
|
||||
// TODO: Implement in opds.go
|
||||
}
|
||||
|
||||
func registerWebSocketRoutes(cfg *Config) {
|
||||
// TODO: Implement in websocket.go
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bookhoard/internal/handlers"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/labstack/echo-jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func registerSyncRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// JWT middleware for protected routes
|
||||
jwtMiddleware := echojwt.WithConfig(echojwt.Config{
|
||||
SigningKey: []byte(cfg.Cfg.JWTSecret),
|
||||
ContextKey: "user",
|
||||
SuccessHandler: func(c echo.Context) {
|
||||
token := c.Get("user").(*jwt.Token)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
c.Set("user_id", claims["user_id"])
|
||||
c.Set("user_role", claims["user_role"])
|
||||
c.Set("user_email", claims["user_email"])
|
||||
c.Set("user_username", claims["user_username"])
|
||||
},
|
||||
})
|
||||
|
||||
protected := e.Group("/api", jwtMiddleware)
|
||||
|
||||
// Setup ebook handler routes first
|
||||
h := handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager)
|
||||
|
||||
// Book matching and unlinked book resolution routes
|
||||
sync := protected.Group("/sync")
|
||||
sync.POST("/bulk-link-books", h.BulkLinkBooks)
|
||||
sync.POST("/auto-link-books", h.AutoLinkBooks)
|
||||
sync.GET("/unlinked-books/:id/suggestions", h.GetUnlinkedBookSuggestions)
|
||||
|
||||
// KOReader sync routes (device authentication required)
|
||||
koreaderSync := e.Group("/api/sync/koreader")
|
||||
koreaderSync.POST("/progress", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncProgress))
|
||||
koreaderSync.GET("/metadata/:uuid", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetMetadata))
|
||||
koreaderSync.GET("/library", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetLibrary))
|
||||
koreaderSync.POST("/bookmarks", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncBookmarks))
|
||||
|
||||
// Kobo sync routes (device authentication required)
|
||||
koboHandler := handlers.NewKoboHandler(cfg.Queries, cfg.ConnManager)
|
||||
koboSync := e.Group("/api/sync/kobo")
|
||||
koboSync.POST("/markup", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Markup))
|
||||
koboSync.POST("/bookmark", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Bookmark))
|
||||
koboSync.POST("/v1/analytics/gettests", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.AnalyticsGettests))
|
||||
koboSync.GET("/v1/initialization", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Initialization))
|
||||
koboSync.POST("/sync-from-server", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.SyncFromServer))
|
||||
}
|
||||
|
||||
func registerWebSocketRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
// WebSocket endpoint for real-time sync
|
||||
e.GET("/ws/sync", cfg.WSHandler.HandleWebSocket)
|
||||
}
|
||||
Reference in New Issue
Block a user