feat(sync): add read-only KOReader book resolve endpoint
GET /api/sync/koreader/resolve?sha256={hash} maps a file content hash to
the book's UUID through the shared format-aware BookResolver (primary
media_items hash, then per-format hashes so converted KEPUB/PDF files
match) without touching any progress state.
Devices need the UUID to pull metadata, but a freshly downloaded book has
none cached. The old way of learning it was to push once, which
transmitted the device's first-page position and manufactured a progress
conflict for books already mid-read from another source. A read-only
lookup lets clients link (and pull) without ever pushing bootstrap
progress: resolve, then pull, then push.
Returns 200 {book_uuid, sha256, title, author}, 400 for a missing or
malformed hash, 404 when no library item matches.
This commit is contained in:
@@ -883,6 +883,43 @@ func int64PtrToPgInt8(i *int64) pgtype.Int8 {
|
|||||||
return pgtype.Int8{}
|
return pgtype.Int8{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KOReaderResolveResponse struct {
|
||||||
|
BookUUID string `json:"book_uuid"`
|
||||||
|
SHA256 string `json:"sha256,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveBook maps a file SHA-256 to the book's UUID without touching any
|
||||||
|
// progress state. Devices need the UUID to pull metadata, but a freshly
|
||||||
|
// downloaded book has none cached yet — the old way of learning it was to
|
||||||
|
// push once, which transmitted the device's first-page position to the
|
||||||
|
// server and manufactured a progress conflict for books already mid-read
|
||||||
|
// from another source. This read-only lookup lets the client link (and
|
||||||
|
// pull) without ever pushing bootstrap progress.
|
||||||
|
func (h *KOReaderHandler) ResolveBook(c *echo.Context) error {
|
||||||
|
sha256 := c.QueryParam("sha256")
|
||||||
|
if sha256 == "" || len(sha256) != 64 {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "sha256 query parameter is required (64 hex characters)",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaItem, _, err := h.bookResolver.ResolveBySHA256(c.Request().Context(), sha256)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, map[string]string{
|
||||||
|
"error": "book not found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, KOReaderResolveResponse{
|
||||||
|
BookUUID: uuid.UUID(mediaItem.ID.Bytes).String(),
|
||||||
|
SHA256: sha256,
|
||||||
|
Title: mediaItem.Title,
|
||||||
|
Author: mediaItem.Author.String,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (h *KOReaderHandler) GetMetadata(c *echo.Context) error {
|
func (h *KOReaderHandler) GetMetadata(c *echo.Context) error {
|
||||||
device := c.Get("device").(database.Devices)
|
device := c.Get("device").(database.Devices)
|
||||||
userID := device.UserID.Bytes
|
userID := device.UserID.Bytes
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func registerSyncRoutes(cfg *Config) {
|
|||||||
// KOReader sync routes (device authentication required)
|
// KOReader sync routes (device authentication required)
|
||||||
koreaderSync := e.Group("/api/sync/koreader")
|
koreaderSync := e.Group("/api/sync/koreader")
|
||||||
koreaderSync.POST("/progress", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncProgress))
|
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("/metadata/:uuid", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetMetadata))
|
||||||
koreaderSync.GET("/library", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetLibrary))
|
koreaderSync.GET("/library", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.GetLibrary))
|
||||||
koreaderSync.POST("/bookmarks", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncBookmarks))
|
koreaderSync.POST("/bookmarks", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncBookmarks))
|
||||||
|
|||||||
Reference in New Issue
Block a user