feat(handlers): integrate ProgressService into media, koreader, kobo, and queue

All four progress write paths now delegate to ProgressService.SaveProgress:

- MediaHandler: UpdateMediaReadingProgress uses ProgressService for web
  saves with richer request body (reading_mode, zoom_level, scroll). GET
  now uses GetUniversalProgress query that JOINs media_items for
  format_group, total_characters, chapter_count.

- KOReaderHandler: updateProgressForBook delegates to ProgressService.
  Fixed device ID bug (was using userID, now uses deviceID). Removed
  duplicate UpdateDeviceLastSync with zero UUID. Added pgtype helper
  functions (textPtrToPgText, intPtrToPgInt4, int64PtrToPgInt8).

- KoboHandler: all four progress write points (Markup ReadingSync, Markup
  last-read-place, AnalyticsGettests, SyncFromServer) delegate to
  ProgressService. Fixed empty epubcfi string now correctly set to
  Valid: false. SyncFromServer preserves last_sync_source=bookhoard
  and Broadcast: false.

- QueueProcessor: syncProgress delegates to ProgressService.

- main.go: creates ProgressService after ConnectionManager, injects via
  SetProgressService() on all handlers and queue processor.

Handler tests cover pgtype conversion helpers (textPtrToPgText, etc.)
and device icon mapping.
This commit is contained in:
2026-04-25 21:16:29 -04:00
parent f699899408
commit 283b2f2ed7
6 changed files with 415 additions and 237 deletions
+41 -5
View File
@@ -36,6 +36,7 @@ const (
type SyncQueueProcessor struct {
db *database.Queries
progressSvc *ProgressService
progressChan chan *ProgressUpdate
interval time.Duration
batchSize int
@@ -79,6 +80,10 @@ func NewSyncQueueProcessor(db *database.Queries) *SyncQueueProcessor {
}
}
func (p *SyncQueueProcessor) SetProgressService(svc *ProgressService) {
p.progressSvc = svc
}
func (p *SyncQueueProcessor) Start(ctx context.Context) {
log.Printf("Starting sync queue processor (interval: %v, batch: %d)", p.interval, p.batchSize)
@@ -310,6 +315,42 @@ func (p *SyncQueueProcessor) syncProgress(ctx context.Context, userID pgtype.UUI
return fmt.Errorf("missing percentage in sync data")
}
source := "queue"
if v, ok := syncData["source"].(string); ok {
source = v
}
if p.progressSvc != nil {
req := SaveProgressRequest{
MediaItemID: mediaItemID,
UserID: userID,
Source: source,
Percentage: &percentage,
Broadcast: false,
}
if v, ok := syncData["epubcfi"].(string); ok {
req.Epubcfi = &v
}
if v, ok := syncData["chapter"].(float64); ok {
ch := int(v)
req.Chapter = &ch
}
if v, ok := syncData["character"].(float64); ok {
co := int64(v)
req.CharacterOffset = &co
}
if v, ok := syncData["page"].(float64); ok {
pg := int(v)
req.CurrentPage = &pg
}
if v, ok := syncData["total_pages"].(float64); ok {
tp := int(v)
req.TotalPages = &tp
}
_, err := p.progressSvc.SaveProgress(ctx, req)
return err
}
var epubcfi pgtype.Text
if v, ok := syncData["epubcfi"].(string); ok {
epubcfi = pgtype.Text{String: v, Valid: true}
@@ -335,11 +376,6 @@ func (p *SyncQueueProcessor) syncProgress(ctx context.Context, userID pgtype.UUI
totalPages = pgtype.Int4{Int32: int32(v), Valid: true}
}
source := "queue"
if v, ok := syncData["source"].(string); ok {
source = v
}
_, err := p.db.UpdateUniversalProgress(ctx, database.UpdateUniversalProgressParams{
MediaItemID: mediaItemID,
UserID: userID,