1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# frozen_string_literal: true

require_relative "models"

module RankKing
  # Reconciles a list's items with a fresh snapshot of entries from its
  # source. Items missing from the snapshot are archived rather than deleted
  # so their results survive, and come back with their history if they
  # reappear. A source that fetches its items calls this from its own sync.
  module Sync
    # Signed in sig/rank_king/sync.rbs: a Data class.
    # @rbs skip
    Summary = Data.define(:added, :restored, :archived)

    #: (List list, Array[Sources::Entry] entries, source: Sources::_Synced, ?now: Time) -> Summary
    def self.call(list, entries, source:, now: Time.now)
      DB.transaction do
        existing = source.items_by_key
        added = restored = 0

        entries.each do |entry|
          attrs = { name: entry.name, archived_at: nil }
          if (item = existing.delete(entry.key))
            restored += 1 if item.archived_at
            item.update(**attrs)
          else
            item = Item.create(**attrs, list_id: list.id)
            added += 1
          end
          source.saved(item, entry)
        end

        gone = existing.values.select {|item| item.archived_at.nil? }
        gone.each {|item| item.update(archived_at: now) }

        list.update(synced_at: now)
        Summary.new(added:, restored:, archived: gone.size)
      end
    end
  end
end