Batch H1 step 3a. The ceiling census (exhaustive UpdateUniversalProgress
grep) found 9 nil-guarded `if progressSvc != nil { funnel } else { raw
erase-write }` fallbacks — the survey's "four" under-counted kobo.go,
which has FOUR sites (Markup ReadingSync, Markup last-read-place,
AnalyticsGettests, SyncFromServer), plus one each in media.go, queue.go
and koreader.go. All unreachable: the service is created unconditionally
at main.go:127 and wired into every handler and the queue processor; the
test harness wires them too.
The erasure pattern is now unrepresentable:
- NewMediaHandler / NewKOReaderHandler / NewKoboHandler / both
NewSyncQueueProcessor flavors take the ProgressService as a REQUIRED
constructor arg; the SetProgressService setters are gone.
- Every fallback body (raw UpdateUniversalProgress with explicit-NULL
pgtypes for viewport/scroll/panel/reading_mode/zoom and no
audio_offset_seconds/context_text) is deleted; each write funnels
through SaveProgress with the LWW seed, so fields the writer doesn't
know survive.
- Removed the now-dead koreader pg-type helpers (textPtrToPgText,
intPtrToPgInt4, int64PtrToPgInt8) and their self-tests.
Behavior deltas (stored-row content, no wire shape changes —
KOReader/Kobo response pins stayed green):
- KOReader fallback used to write explicit NULL viewports and drop the
context_text the koplugin sends; the funneled mainline already maps
book.ContextText.
- Queue flushes no longer overwrite chapter_progress with the book
percentage (bogus data; preserved via the seed).
- A web PUT without percentage no longer writes 0% via the deleted
fallback; percentage seeds from the stored row.
Contract suite: KOReader×4 + Kobo×3 pins green.
57 lines
2.8 KiB
Go
57 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, 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)
|
|
}
|