diff --git a/app/services/edge_cache/bust.rb b/app/services/edge_cache/bust.rb index 25f1d45b8..053823234 100644 --- a/app/services/edge_cache/bust.rb +++ b/app/services/edge_cache/bust.rb @@ -1,5 +1,12 @@ module EdgeCache class Bust + TIMEFRAMES = [ + [-> { 1.week.ago }, "week"], + [-> { 1.month.ago }, "month"], + [-> { 1.year.ago }, "year"], + [-> { 5.years.ago }, "infinity"], + ].freeze + def self.call(path) bust(path) end diff --git a/app/services/edge_cache/bust_article.rb b/app/services/edge_cache/bust_article.rb new file mode 100644 index 000000000..85322eb1b --- /dev/null +++ b/app/services/edge_cache/bust_article.rb @@ -0,0 +1,90 @@ +module EdgeCache + class BustArticle < Bust + def self.call(article) + return unless article + + article.purge + + bust(article.path) + bust("/#{article.user.username}") + bust("#{article.path}/") + bust("#{article.path}?i=i") + bust("#{article.path}/?i=i") + bust("#{article.path}/comments") + bust("#{article.path}?preview=#{article.password}") + bust("#{article.path}?preview=#{article.password}&i=i") + bust("/#{article.organization.slug}") if article.organization.present? + bust_home_pages(article) + bust_tag_pages(article) + bust("/api/articles/#{article.id}") + + return unless article.collection_id + + article.collection.articles.find_each do |collection_article| + bust(collection_article.path) + end + end + + def self.bust_home_pages(article) + if article.featured_number.to_i > Time.current.to_i + bust("/") + bust("?i=i") + end + + if article.video.present? && article.featured_number.to_i > 10.days.ago.to_i + bust("/videos") + bust("/videos?i=i") + end + + TIMEFRAMES.each do |timestamp, interval| + next unless Article.published.where("published_at > ?", timestamp.call) + .order(public_reactions_count: :desc).limit(3).ids.include?(article.id) + + bust("/top/#{interval}") + bust("/top/#{interval}?i=i") + bust("/top/#{interval}/?i=i") + end + + if article.published && article.published_at > 1.hour.ago + bust("/latest") + bust("/latest?i=i") + end + + bust("/") if Article.published.order(hotness_score: :desc).limit(4).ids.include?(article.id) + end + + private_class_method :bust_home_pages + + def self.bust_tag_pages(article) + return unless article.published + + article.tag_list.each do |tag| + if article.published_at.to_i > 2.minutes.ago.to_i + bust("/t/#{tag}/latest") + bust("/t/#{tag}/latest?i=i") + end + + TIMEFRAMES.each do |timestamp, interval| + next unless Article.published.where("published_at > ?", timestamp.call).tagged_with(tag) + .order(public_reactions_count: :desc).limit(3).ids.include?(article.id) + + bust("/top/#{interval}") + bust("/top/#{interval}?i=i") + bust("/top/#{interval}/?i=i") + 12.times do |i| + bust("/api/articles?tag=#{tag}&top=#{i}") + end + end + + next unless rand(2) == 1 && + Article.published.tagged_with(tag) + .order(hotness_score: :desc).limit(2).ids.include?(article.id) + + bust("/t/#{tag}") + bust("/t/#{tag}?i=i") + end + end + + private_class_method :bust_tag_pages + end +end diff --git a/spec/services/edge_cache/bust_article_spec.rb b/spec/services/edge_cache/bust_article_spec.rb new file mode 100644 index 000000000..8bad19348 --- /dev/null +++ b/spec/services/edge_cache/bust_article_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe EdgeCache::BustArticle, type: :service do + let(:article) { create(:article) } + + before do + allow(article).to receive(:purge) + allow(described_class).to receive(:bust) + allow(described_class).to receive(:bust_home_pages) + allow(described_class).to receive(:bust_tag_pages) + end + + it "busts the cache" do + described_class.call(article) + expect(article).to have_received(:purge).once + expect(described_class).to have_received(:bust).exactly(9).times + expect(described_class).to have_received(:bust).with(article.path).once + expect(described_class).to have_received(:bust_home_pages).with(article).once + expect(described_class).to have_received(:bust_tag_pages).with(article).once + end + + context "when an article is part of an organization" do + it "busts the organization slug" do + organization = create(:organization) + article.organization = organization + + described_class.call(article) + expect(described_class).to have_received(:bust).with("/#{article.organization.slug}").once + end + end +end diff --git a/spec/services/edge_cache/bust_spec.rb b/spec/services/edge_cache/bust_spec.rb index 8fc5e632a..91310a579 100644 --- a/spec/services/edge_cache/bust_spec.rb +++ b/spec/services/edge_cache/bust_spec.rb @@ -4,6 +4,19 @@ RSpec.describe EdgeCache::Bust, type: :service do let(:user) { create(:user) } let(:path) { "/#{user.username}" } + it "defines TIMEFRAMES" do + expect(described_class.const_defined?(:TIMEFRAMES)).to be true + end + + it "adjusts TIMEFRAMES according to the current time" do + current_year = Time.current.year + + Timecop.freeze(3.years.ago) do + timestamp, _interval = described_class::TIMEFRAMES.first + expect(timestamp.call.year).to be <= current_year - 3 + end + end + describe "#bust_fastly_cache" do let(:fastly_provider_class) { EdgeCache::Bust::Fastly }