refactor(annotations): one LWW decision core + one tombstone gate + typed queue decoders
The consolidation half of H3 — shared LOGIC with thin per-type remains: - lwwDecision: the three compareIncoming* functions were the same skeleton (client timestamp -> strictly-newer wins; no timestamp -> any content difference wins) drifting apart only in per-type equality and the schema-driven fallback timestamp (bookmarks carry no updated_at). The decision now lives in one function; each compare keeps only its equality expression. - idTargetOwnership + tombstoneWins: the id-targeted resolution blocks (HighlightID/BookmarkID/NoteID) shared the ownership check and the deleted-row gate — now one gate each instead of three copies of the resurrect semantics. - queue.go: syncNote/syncHighlight/syncBookmark decoded device payloads through three 10-key if-type-assertion chains; typed map accessors (mapStr/mapF64/mapI32/mapDeviceSyncData) replace them — same missing-key-is-zero semantics, declarative field lists. The progress decoder is untouched (H1 territory, separately tested). Per-type remains are the things that genuinely cannot merge: request structs, dedup triples, the per-table sqlc param builders, and the KOReader/Kobo payload adapters. Pins, H1/H3 tests, both unit suites green.
This commit is contained in:
1 parent
7d698ea0d8
commit
11609d13d1
2 files changed
+120
-161
No files matched your search
+61
-99
@@ -457,32 +457,42 @@ func (p *SyncQueueProcessor) syncProgress(ctx context.Context, userID pgtype.UUI
|
||||
return err
|
||||
}
|
||||
|
||||
// queue-map accessors: syncData entries arrive as JSON-decoded
|
||||
// interface{} values; a missing key or wrong type is simply the zero
|
||||
// value (the decoders only carry fields the device actually pushed).
|
||||
func mapStr(m map[string]interface{}, key string) string {
|
||||
v, _ := m[key].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
func mapF64(m map[string]interface{}, key string) float64 {
|
||||
v, _ := m[key].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
func mapI32(m map[string]interface{}, key string) int32 {
|
||||
return int32(mapF64(m, key))
|
||||
}
|
||||
|
||||
func mapDeviceSyncData(m map[string]interface{}) json.RawMessage {
|
||||
if v, ok := m["device_sync_data"].(map[string]interface{}); ok {
|
||||
raw, _ := json.Marshal(v)
|
||||
return raw
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SyncQueueProcessor) syncNote(ctx context.Context, userID pgtype.UUID, mediaItemID pgtype.UUID, syncData map[string]interface{}) error {
|
||||
req := SaveNoteRequest{
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if v, ok := syncData["content"].(string); ok {
|
||||
req.Content = v
|
||||
}
|
||||
if v, ok := syncData["position"].(string); ok {
|
||||
req.Position = v
|
||||
}
|
||||
if v, ok := syncData["source"].(string); ok {
|
||||
req.Source = v
|
||||
}
|
||||
if v, ok := syncData["epubcfi_location"].(string); ok {
|
||||
req.EpubcfiLocation = v
|
||||
}
|
||||
if v, ok := syncData["percentage_location"].(float64); ok {
|
||||
req.PercentageLocation = v
|
||||
}
|
||||
if v, ok := syncData["chapter_reference"].(float64); ok {
|
||||
req.ChapterReference = int32(v)
|
||||
}
|
||||
if v, ok := syncData["device_sync_data"].(map[string]interface{}); ok {
|
||||
req.DeviceSyncData, _ = json.Marshal(v)
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
Content: mapStr(syncData, "content"),
|
||||
Position: mapStr(syncData, "position"),
|
||||
Source: mapStr(syncData, "source"),
|
||||
EpubcfiLocation: mapStr(syncData, "epubcfi_location"),
|
||||
PercentageLocation: mapF64(syncData, "percentage_location"),
|
||||
ChapterReference: mapI32(syncData, "chapter_reference"),
|
||||
DeviceSyncData: mapDeviceSyncData(syncData),
|
||||
}
|
||||
|
||||
_, err := p.annotationSvc.SaveNote(ctx, req)
|
||||
@@ -491,45 +501,20 @@ func (p *SyncQueueProcessor) syncNote(ctx context.Context, userID pgtype.UUID, m
|
||||
|
||||
func (p *SyncQueueProcessor) syncHighlight(ctx context.Context, userID pgtype.UUID, mediaItemID pgtype.UUID, syncData map[string]interface{}) error {
|
||||
req := SaveHighlightRequest{
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if v, ok := syncData["selection_text"].(string); ok {
|
||||
req.SelectionText = v
|
||||
}
|
||||
if v, ok := syncData["start_position"].(string); ok {
|
||||
req.StartPosition = v
|
||||
}
|
||||
if v, ok := syncData["end_position"].(string); ok {
|
||||
req.EndPosition = v
|
||||
}
|
||||
if v, ok := syncData["color"].(string); ok {
|
||||
req.Color = v
|
||||
}
|
||||
if v, ok := syncData["note_text"].(string); ok {
|
||||
req.NoteText = v
|
||||
}
|
||||
if v, ok := syncData["source"].(string); ok {
|
||||
req.Source = v
|
||||
}
|
||||
if v, ok := syncData["epubcfi_start"].(string); ok {
|
||||
req.EpubcfiStart = v
|
||||
}
|
||||
if v, ok := syncData["epubcfi_end"].(string); ok {
|
||||
req.EpubcfiEnd = v
|
||||
}
|
||||
if v, ok := syncData["percentage_start"].(float64); ok {
|
||||
req.PercentageStart = v
|
||||
}
|
||||
if v, ok := syncData["percentage_end"].(float64); ok {
|
||||
req.PercentageEnd = v
|
||||
}
|
||||
if v, ok := syncData["chapter_reference"].(float64); ok {
|
||||
req.ChapterReference = int32(v)
|
||||
}
|
||||
if v, ok := syncData["device_sync_data"].(map[string]interface{}); ok {
|
||||
req.DeviceSyncData, _ = json.Marshal(v)
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
SelectionText: mapStr(syncData, "selection_text"),
|
||||
StartPosition: mapStr(syncData, "start_position"),
|
||||
EndPosition: mapStr(syncData, "end_position"),
|
||||
Color: mapStr(syncData, "color"),
|
||||
NoteText: mapStr(syncData, "note_text"),
|
||||
PercentageStart: mapF64(syncData, "percentage_start"),
|
||||
PercentageEnd: mapF64(syncData, "percentage_end"),
|
||||
EpubcfiStart: mapStr(syncData, "epubcfi_start"),
|
||||
EpubcfiEnd: mapStr(syncData, "epubcfi_end"),
|
||||
ChapterReference: mapI32(syncData, "chapter_reference"),
|
||||
Source: mapStr(syncData, "source"),
|
||||
DeviceSyncData: mapDeviceSyncData(syncData),
|
||||
}
|
||||
|
||||
_, err := p.annotationSvc.SaveHighlight(ctx, req)
|
||||
@@ -538,42 +523,19 @@ func (p *SyncQueueProcessor) syncHighlight(ctx context.Context, userID pgtype.UU
|
||||
|
||||
func (p *SyncQueueProcessor) syncBookmark(ctx context.Context, userID pgtype.UUID, mediaItemID pgtype.UUID, syncData map[string]interface{}) error {
|
||||
req := SaveBookmarkRequest{
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if v, ok := syncData["title"].(string); ok {
|
||||
req.Title = v
|
||||
}
|
||||
if v, ok := syncData["position"].(string); ok {
|
||||
req.Position = v
|
||||
}
|
||||
if v, ok := syncData["notes"].(string); ok {
|
||||
req.Notes = v
|
||||
}
|
||||
if v, ok := syncData["source"].(string); ok {
|
||||
req.Source = v
|
||||
}
|
||||
if v, ok := syncData["cfi_position"].(string); ok {
|
||||
req.CFIPosition = v
|
||||
}
|
||||
if v, ok := syncData["epubcfi_location"].(string); ok {
|
||||
req.EpubcfiLocation = v
|
||||
}
|
||||
if v, ok := syncData["percentage_loc"].(float64); ok {
|
||||
req.PercentageLoc = v
|
||||
}
|
||||
if v, ok := syncData["page_number"].(float64); ok {
|
||||
req.PageNumber = int32(v)
|
||||
}
|
||||
if v, ok := syncData["chapter_number"].(float64); ok {
|
||||
req.ChapterNumber = int32(v)
|
||||
}
|
||||
if v, ok := syncData["chapter_reference"].(float64); ok {
|
||||
req.ChapterReference = int32(v)
|
||||
}
|
||||
if v, ok := syncData["device_sync_data"].(map[string]interface{}); ok {
|
||||
req.DeviceSyncData, _ = json.Marshal(v)
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
Title: mapStr(syncData, "title"),
|
||||
Position: mapStr(syncData, "position"),
|
||||
Notes: mapStr(syncData, "notes"),
|
||||
Source: mapStr(syncData, "source"),
|
||||
CFIPosition: mapStr(syncData, "cfi_position"),
|
||||
EpubcfiLocation: mapStr(syncData, "epubcfi_location"),
|
||||
PercentageLoc: mapF64(syncData, "percentage_loc"),
|
||||
PageNumber: mapI32(syncData, "page_number"),
|
||||
ChapterNumber: mapI32(syncData, "chapter_number"),
|
||||
ChapterReference: mapI32(syncData, "chapter_reference"),
|
||||
DeviceSyncData: mapDeviceSyncData(syncData),
|
||||
}
|
||||
|
||||
_, err := p.annotationSvc.SaveBookmark(ctx, req)
|
||||
|
||||
Reference in new issue
Block a user