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("/v1/analytics/gettests", deviceAuthMiddleware.Authenticate(koboHandler.AnalyticsGettests))
|
||||
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)
|
||||
devices := protected.Group("/devices")
|
||||
|
||||
@@ -63,6 +63,10 @@ type KoboLibraryBook struct {
|
||||
PagesRemaining *int `json:"PagesRemaining,omitempty"`
|
||||
BookmarkCount int `json:"BookmarkCount"`
|
||||
LastModified string `json:"LastModified"`
|
||||
EntitlementId string `json:"EntitlementId,omitempty"`
|
||||
Revision int `json:"Revision"`
|
||||
MimeType string `json:"MimeType"`
|
||||
FileSize int64 `json:"FileSize"`
|
||||
}
|
||||
|
||||
type KoboLibraryResponse struct {
|
||||
@@ -82,6 +86,20 @@ type KoboSyncStatus struct {
|
||||
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 {
|
||||
ContentId string `json:"ContentId"`
|
||||
ReadingEvent string `json:"ReadingEvent"`
|
||||
@@ -136,6 +154,27 @@ func (h *KoboHandler) Initialization(c echo.Context) error {
|
||||
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{
|
||||
ContentId: uuid.UUID(item.ID.Bytes).String(),
|
||||
ContentType: "6",
|
||||
@@ -145,6 +184,10 @@ func (h *KoboHandler) Initialization(c echo.Context) error {
|
||||
PagesRemaining: pagesRemaining,
|
||||
BookmarkCount: bookmarkCount,
|
||||
LastModified: lastModified,
|
||||
EntitlementId: entitlementId,
|
||||
Revision: revision,
|
||||
MimeType: mimeType,
|
||||
FileSize: fileSize,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -389,3 +432,91 @@ func parseKoboDeviceHeader(c echo.Context) (KoboDeviceInfo, error) {
|
||||
|
||||
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