feat: add server sync to Kobo endpoint and media routes
This commit is contained in:
@@ -199,6 +199,15 @@ func main() {
|
|||||||
koboSync.POST("/bookmark", deviceAuthMiddleware.Authenticate(koboHandler.Bookmark))
|
koboSync.POST("/bookmark", deviceAuthMiddleware.Authenticate(koboHandler.Bookmark))
|
||||||
koboSync.POST("/v1/analytics/gettests", deviceAuthMiddleware.Authenticate(koboHandler.AnalyticsGettests))
|
koboSync.POST("/v1/analytics/gettests", deviceAuthMiddleware.Authenticate(koboHandler.AnalyticsGettests))
|
||||||
koboSync.GET("/v1/initialization", deviceAuthMiddleware.Authenticate(koboHandler.Initialization))
|
koboSync.GET("/v1/initialization", deviceAuthMiddleware.Authenticate(koboHandler.Initialization))
|
||||||
|
koboSync.POST("/sync-from-server", deviceAuthMiddleware.Authenticate(koboHandler.SyncFromServer))
|
||||||
|
|
||||||
|
// Media item routes (download and shelf management)
|
||||||
|
mediaHandler := handlers.NewMediaHandler(queries)
|
||||||
|
e.GET("/api/books/:uuid/download", mediaHandler.DownloadBook)
|
||||||
|
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)
|
||||||
|
|
||||||
// Device management routes (protected - require user auth)
|
// Device management routes (protected - require user auth)
|
||||||
devices := protected.Group("/devices")
|
devices := protected.Group("/devices")
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ type KoboLibraryBook struct {
|
|||||||
PagesRemaining *int `json:"PagesRemaining,omitempty"`
|
PagesRemaining *int `json:"PagesRemaining,omitempty"`
|
||||||
BookmarkCount int `json:"BookmarkCount"`
|
BookmarkCount int `json:"BookmarkCount"`
|
||||||
LastModified string `json:"LastModified"`
|
LastModified string `json:"LastModified"`
|
||||||
|
EntitlementId string `json:"EntitlementId,omitempty"`
|
||||||
|
Revision int `json:"Revision"`
|
||||||
|
MimeType string `json:"MimeType"`
|
||||||
|
FileSize int64 `json:"FileSize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type KoboLibraryResponse struct {
|
type KoboLibraryResponse struct {
|
||||||
@@ -82,6 +86,20 @@ type KoboSyncStatus struct {
|
|||||||
BookmarksSynced int `json:"BookmarksSynced"`
|
BookmarksSynced int `json:"BookmarksSynced"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KoboServerSyncData struct {
|
||||||
|
ContentId string `json:"ContentId"`
|
||||||
|
PercentRead float64 `json:"PercentRead"`
|
||||||
|
Bookmarks []KoboBookmarkSync `json:"Bookmarks,omitempty"`
|
||||||
|
Highlights []KoboBookmarkSync `json:"Highlights,omitempty"`
|
||||||
|
LastModified string `json:"LastModified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KoboServerSyncResponse struct {
|
||||||
|
BooksSynced int `json:"BooksSynced"`
|
||||||
|
BookmarksSent int `json:"BookmarksSent"`
|
||||||
|
HighlightsSent int `json:"HighlightsSent"`
|
||||||
|
}
|
||||||
|
|
||||||
type KoboAnalyticsTest struct {
|
type KoboAnalyticsTest struct {
|
||||||
ContentId string `json:"ContentId"`
|
ContentId string `json:"ContentId"`
|
||||||
ReadingEvent string `json:"ReadingEvent"`
|
ReadingEvent string `json:"ReadingEvent"`
|
||||||
@@ -136,6 +154,27 @@ func (h *KoboHandler) Initialization(c echo.Context) error {
|
|||||||
author = item.Author.String
|
author = item.Author.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entitlementId := item.EntitlementID.String
|
||||||
|
|
||||||
|
if entitlementId == "" || !item.EntitlementID.Valid {
|
||||||
|
entitlementId = "kobo_" + uuid.UUID(item.ID.Bytes).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeType := item.MimeType.String
|
||||||
|
if !item.MimeType.Valid {
|
||||||
|
mimeType = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize := int64(0)
|
||||||
|
if item.FileSize.Valid {
|
||||||
|
fileSize = item.FileSize.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := 1
|
||||||
|
if item.RevisionNumber.Valid {
|
||||||
|
revision = int(item.RevisionNumber.Int32)
|
||||||
|
}
|
||||||
|
|
||||||
librarySync = append(librarySync, KoboLibraryBook{
|
librarySync = append(librarySync, KoboLibraryBook{
|
||||||
ContentId: uuid.UUID(item.ID.Bytes).String(),
|
ContentId: uuid.UUID(item.ID.Bytes).String(),
|
||||||
ContentType: "6",
|
ContentType: "6",
|
||||||
@@ -145,6 +184,10 @@ func (h *KoboHandler) Initialization(c echo.Context) error {
|
|||||||
PagesRemaining: pagesRemaining,
|
PagesRemaining: pagesRemaining,
|
||||||
BookmarkCount: bookmarkCount,
|
BookmarkCount: bookmarkCount,
|
||||||
LastModified: lastModified,
|
LastModified: lastModified,
|
||||||
|
EntitlementId: entitlementId,
|
||||||
|
Revision: revision,
|
||||||
|
MimeType: mimeType,
|
||||||
|
FileSize: fileSize,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,3 +432,91 @@ func parseKoboDeviceHeader(c echo.Context) (KoboDeviceInfo, error) {
|
|||||||
|
|
||||||
return device, nil
|
return device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *KoboHandler) SyncFromServer(c echo.Context) error {
|
||||||
|
device := c.Get("device").(database.Devices)
|
||||||
|
userID := device.UserID.Bytes
|
||||||
|
|
||||||
|
pgUserID := pgtype.UUID{Bytes: userID, Valid: true}
|
||||||
|
|
||||||
|
var req []KoboServerSyncData
|
||||||
|
if err := c.Bind(&req); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid request format",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
booksSynced := 0
|
||||||
|
bookmarksSent := 0
|
||||||
|
highlightsSent := 0
|
||||||
|
|
||||||
|
for _, syncData := range req {
|
||||||
|
mediaUUID, err := uuid.Parse(syncData.ContentId)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pgMediaUUID := pgtype.UUID{Bytes: mediaUUID, Valid: true}
|
||||||
|
|
||||||
|
percentage := syncData.PercentRead / 100.0
|
||||||
|
|
||||||
|
_, err = h.db.UpdateUniversalProgress(c.Request().Context(), database.UpdateUniversalProgressParams{
|
||||||
|
MediaItemID: pgMediaUUID,
|
||||||
|
UserID: pgUserID,
|
||||||
|
Percentage: pgtype.Float8{Float64: percentage, Valid: true},
|
||||||
|
LastSyncDevice: pgtype.Text{String: "kobo", Valid: true},
|
||||||
|
LastSyncSource: pgtype.Text{String: "bookmann", Valid: true},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
booksSynced++
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bookmark := range syncData.Bookmarks {
|
||||||
|
if bookmark.BookmarkType == "bookmark" {
|
||||||
|
h.db.CreateMediaNote(c.Request().Context(), database.CreateMediaNoteParams{
|
||||||
|
MediaItemID: pgMediaUUID,
|
||||||
|
UserID: pgUserID,
|
||||||
|
Content: bookmark.BookmarkText,
|
||||||
|
Position: pgtype.Text{String: bookmark.BookmarkId, Valid: true},
|
||||||
|
})
|
||||||
|
bookmarksSent++
|
||||||
|
} else if bookmark.BookmarkType == "annotation" {
|
||||||
|
h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
|
||||||
|
MediaItemID: pgMediaUUID,
|
||||||
|
UserID: pgUserID,
|
||||||
|
SelectionText: bookmark.BookmarkText,
|
||||||
|
StartPosition: pgtype.Text{String: bookmark.BookmarkId, Valid: true},
|
||||||
|
EndPosition: pgtype.Text{String: bookmark.BookmarkId, Valid: true},
|
||||||
|
Color: pgtype.Text{String: "#ffff00", Valid: true},
|
||||||
|
})
|
||||||
|
highlightsSent++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, highlight := range syncData.Highlights {
|
||||||
|
h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
|
||||||
|
MediaItemID: pgMediaUUID,
|
||||||
|
UserID: pgUserID,
|
||||||
|
SelectionText: highlight.BookmarkText,
|
||||||
|
StartPosition: pgtype.Text{String: highlight.BookmarkId, Valid: true},
|
||||||
|
EndPosition: pgtype.Text{String: highlight.BookmarkId, Valid: true},
|
||||||
|
Color: pgtype.Text{String: "#ffff00", Valid: true},
|
||||||
|
})
|
||||||
|
highlightsSent++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := h.db.UpdateDeviceLastSync(c.Request().Context(), device.ID)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||||||
|
"error": "failed to update device timestamp",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, KoboServerSyncResponse{
|
||||||
|
BooksSynced: booksSynced,
|
||||||
|
BookmarksSent: bookmarksSent,
|
||||||
|
HighlightsSent: highlightsSent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user