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:
2026-02-06 11:49:28 -05:00
parent 2dd0238ef2
commit 6784c25b2e
9 changed files with 704 additions and 760 deletions
+61
View File
@@ -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)
}