Release / build-and-push (push) Successful in 3m4s
Extends the device-authed koreader surface with the handoff item 4 Batch B contract: - GET/POST /api/sync/koreader/rating — the device mirrors the .sdr summary's 5-star rating against the 1-10 server scale (POST 0 clears). - GET/POST /api/sync/koreader/review — raw-markdown review mirror (summary.note); empty body clears, matching the first-party PUT. - POST /api/sync/koreader/stats — the device pushes its CUMULATIVE total reading seconds; the server watermark (highest total ever converted, read from the koreader sessions' own device_metadata) converts only the delta into a reading_history session attributed to the koreader device. Reinstalls cannot double-count; sub-minute deltas ride forward (no session, no watermark movement); a lagging device clamps to zero instead of dragging the total back. Book identity resolves through the existing priority chain (uuid → sha256 → alias → title/author). GET binding uses query tags; POST endpoints bind the ref+payload once (an earlier resolve-then-bind draft drained the body). Live-verified against the dev server with the koreader device token: rating set/get/clear, review mirror, and the stats sequence 900→session, 960→delta session, 970→rides forward, 500→clamps; the book's stats card and the By Device panel both pick up the split.
58 lines
2.8 KiB
Go
58 lines
2.8 KiB
Go
package router
|
|
|
|
import (
|
|
"bookhoard/internal/handlers"
|
|
)
|
|
|
|
func registerSyncRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
// JWT middleware for protected routes
|
|
jwtMiddleware := createJWTMiddleware(cfg)
|
|
|
|
protected := e.Group("/api", jwtMiddleware)
|
|
|
|
// Create handler for sync-specific routes
|
|
h := handlers.NewHandler(cfg.Queries, cfg.ConnManager, cfg.QueueProcessor, cfg.Cfg)
|
|
|
|
// 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("/resolve", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.ResolveBook))
|
|
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))
|
|
koreaderSync.GET("/rating", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetKOReaderRating))
|
|
koreaderSync.POST("/rating", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SetKOReaderRating))
|
|
koreaderSync.GET("/review", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetKOReaderReview))
|
|
koreaderSync.POST("/review", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SetKOReaderReview))
|
|
koreaderSync.POST("/stats", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.PushKOReaderStats))
|
|
|
|
// Kobo sync routes (device authentication required)
|
|
// Kobo devices use URL path: /api/sync/kobo/{token}/markup
|
|
// API clients can use Authorization header: Authorization: Bearer {token}
|
|
koboHandler := handlers.NewKoboHandler(cfg.Queries, cfg.ConnManager)
|
|
koboHandler.SetProgressService(cfg.ProgressService)
|
|
koboHandler.SetAnnotationService(cfg.AnnotationService)
|
|
koboHandler.SetLibraryService(cfg.LibraryService)
|
|
koboSync := e.Group("/api/sync/kobo/:token")
|
|
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
|
|
|
|
wsGroup := e.Group("/ws/sync")
|
|
wsGroup.GET("", cfg.WSHandler.HandleWebSocket)
|
|
}
|