feat(api): expose sync status (#1635)

* feat(api): expose sync status

* fix(api): harden sync status review paths

* fix(api): address sync status review

* fix(api): tighten sync status review fixes

* fix(api): address sync status review

* test(api): avoid secret-like sync fixture key

* test(api): reuse sync status fixture key

* fix(api): align sync route helpers

* fix(api): tighten sync status scoping

* fix(api): make sync status schema nullable-compliant
This commit is contained in:
ghost
2026-05-06 14:02:21 -06:00
committed by GitHub
parent 1e0666eca2
commit 9e369831ce
12 changed files with 817 additions and 6 deletions

View File

@@ -15,7 +15,7 @@ class Sync < ApplicationRecord
belongs_to :parent, class_name: "Sync", optional: true
has_many :children, class_name: "Sync", foreign_key: :parent_id, dependent: :destroy
scope :ordered, -> { order(created_at: :desc) }
scope :ordered, -> { order(created_at: :desc, id: :desc) }
scope :incomplete, -> { where("syncs.status IN (?)", %w[pending syncing]) }
scope :visible, -> { incomplete.where("syncs.created_at > ?", VISIBLE_FOR.ago) }
@@ -57,6 +57,52 @@ class Sync < ApplicationRecord
def clean
incomplete.where("syncs.created_at < ?", STALE_AFTER.ago).find_each(&:mark_stale!)
end
def for_family(family, resource_owner: nil)
query = where(syncable_type: "Family", syncable_id: family.id)
query = query.or(where(syncable_type: "Account", syncable_id: account_syncable_ids(family, resource_owner)))
family_syncable_associations.each do |association|
query = query.or(
where(syncable_type: association.klass.name, syncable_id: family.public_send(association.name).select(:id))
)
end
query
end
private
def account_syncable_ids(family, resource_owner)
(resource_owner ? resource_owner.accessible_accounts : family.accounts)
.where(family_id: family.id)
.select(:id)
end
def family_syncable_associations
Family.reflect_on_all_associations(:has_many).select do |association|
association.name.to_s.end_with?("_items") &&
association.klass.included_modules.include?(Syncable)
rescue NameError
false
end
end
end
def in_progress?
pending? || syncing?
end
def terminal?
completed? || failed? || stale?
end
def api_error_payload
return unless failed? || stale?
return if stale? && error.blank?
{
message: stale? ? "Sync became stale before completion" : "Sync failed"
}
end
def perform