diff --git a/app/assets/images/refurrer-logo-icon.png b/app/assets/images/refurrer-logo-icon.png new file mode 100644 index 00000000..c5fbc1bd Binary files /dev/null and b/app/assets/images/refurrer-logo-icon.png differ diff --git a/app/assets/images/refurrer-logo-md.png b/app/assets/images/refurrer-logo-md.png new file mode 100644 index 00000000..d66aa085 Binary files /dev/null and b/app/assets/images/refurrer-logo-md.png differ diff --git a/app/controllers/blob_entries_controller.rb b/app/controllers/blob_entries_controller.rb index 94ccd24b..bea8ce07 100644 --- a/app/controllers/blob_entries_controller.rb +++ b/app/controllers/blob_entries_controller.rb @@ -1,7 +1,8 @@ -# typed: true +# typed: strict class BlobEntriesController < ApplicationController skip_before_action :authenticate_user!, only: [:show] + sig { void } def show thumb = params[:thumb] raise("invalid thumb #{thumb}") if thumb.present? && !thumb_params(thumb) @@ -15,14 +16,10 @@ class BlobEntriesController < ApplicationController etag += "-#{thumb}" if thumb return unless stale?(last_modified: Time.at(0), strong_etag: etag) - sha256bin = HexUtil.hex2bin(sha256) - if show_blob_file(sha256, thumb) return elsif BlobFile.migrate_sha256!(sha256) && show_blob_file(sha256, thumb) return - elsif blob_entry = BlobEntry.find_by(sha256: sha256bin) - show_blob_entry(blob_entry, thumb) else raise ActiveRecord::RecordNotFound end @@ -37,13 +34,16 @@ class BlobEntriesController < ApplicationController filename = T.must(filename[..File.extname(filename).length]) filename += ".jpeg" cache_key = "vips:thumbnail:#{sha256}:#{thumb}" - width, height = thumb_params(thumb) - + thumb_params = thumb_params(thumb) + if thumb_params.nil? + raise ActionController::BadRequest.new("invalid thumbnail: #{thumb}") + end + width, height = thumb_params thumb_data = Rack::MiniProfiler.step("vips: load from cache") do Rails .cache - .fetch(cache_key, expires_in: 1.hour) do + .fetch(cache_key, expires_in: 1.day) do blob_file = BlobFile.find_by(sha256: HexUtil.hex2bin(sha256)) if blob_file content_type = @@ -134,58 +134,7 @@ class BlobEntriesController < ApplicationController end end - sig { params(blob_entry: BlobEntry, thumb: T.nilable(String)).void } - def show_blob_entry(blob_entry, thumb) - sha256 = HexUtil.bin2hex(T.must(blob_entry.sha256)) - content_type = blob_entry.content_type || "application/octet-stream" - # images, videos, etc - if helpers.is_send_data_content_type?(content_type) - if !thumb.blank? && helpers.is_renderable_image_type?(content_type) - filename = "thumb-#{thumb}-#{sha256}" - filename = T.must(filename[..File.extname(filename).length]) - filename += ".jpeg" - - width, height = thumb_params(thumb) - image = - T.unsafe(Vips::Image).thumbnail_buffer( - blob_entry.contents, - width, - height: height, - ) - resized_image_contents = image.jpegsave_buffer - - send_data( - resized_image_contents, - type: "image/jpg", - disposition: "inline", - filename: filename, - ) - elsif !thumb.blank? && helpers.is_renderable_video_type?(content_type) - render plain: "no thumbnail" - else - ext = helpers.ext_for_content_type(content_type) - ext = ".#{ext}" if ext - send_data( - T.must(blob_entry.contents), - type: content_type, - disposition: "inline", - filename: "data#{ext}", - ) - end - elsif content_type =~ %r{text/plain} - render plain: blob_entry.contents - elsif content_type.starts_with? "text/html" - render html: blob_entry.contents&.html_safe - elsif content_type.starts_with? "application/json" - pretty_json = - JSON.pretty_generate(JSON.parse(T.must(blob_entry.contents))) - render html: - "
#{pretty_json}
".html_safe - else - render plain: "no renderer for #{content_type}" - end - end - + sig { params(thumb: String).returns(T.nilable([Integer, Integer])) } def thumb_params(thumb) case thumb when "32-avatar" @@ -198,6 +147,8 @@ class BlobEntriesController < ApplicationController [400, 300] when "medium" [800, 600] + when "content-container" + [2048, 2048] end end end diff --git a/app/controllers/log_entries_controller.rb b/app/controllers/log_entries_controller.rb index 4e520448..c5dbf12c 100644 --- a/app/controllers/log_entries_controller.rb +++ b/app/controllers/log_entries_controller.rb @@ -56,7 +56,7 @@ class LogEntriesController < ApplicationController HttpLogEntry .joins(:response) .includes(:response) - .select("http_log_entries.*, blob_entries_p.size") + .select("http_log_entries.*, blob_files.size_bytes") .find_each(batch_size: 100, order: :desc) do |log_entry| break if log_entry.created_at < @time_window.ago @last_window_count += 1 diff --git a/app/helpers/log_entries_helper.rb b/app/helpers/log_entries_helper.rb index 310fb28f..8c1f72a9 100644 --- a/app/helpers/log_entries_helper.rb +++ b/app/helpers/log_entries_helper.rb @@ -87,7 +87,7 @@ module LogEntriesHelper sig { params(log_entry: HttpLogEntry).returns(T.nilable(String)) } def render_msword_content(log_entry) - docx_body = log_entry.response&.contents + docx_body = log_entry.response_bytes return nil if docx_body.blank? # Invoke abiword to convert doc / docx to html # Run abiword conversion with pipes diff --git a/app/jobs/domain/fa/job/base.rb b/app/jobs/domain/fa/job/base.rb index 89467ba6..a44a93d2 100644 --- a/app/jobs/domain/fa/job/base.rb +++ b/app/jobs/domain/fa/job/base.rb @@ -419,7 +419,7 @@ class Domain::Fa::Job::Base < Scraper::JobBase } raise("unsupported content type: #{log_entry.content_type}") end - document = log_entry.response&.contents || return + document = log_entry.response_bytes || return link_finder = Scraper::LinkFinder.new(T.must(log_entry.uri_host), document) link_finder.logger.level = :error diff --git a/app/jobs/domain/twitter/job/media_job.rb b/app/jobs/domain/twitter/job/media_job.rb index 5f8e363b..f6ab6452 100644 --- a/app/jobs/domain/twitter/job/media_job.rb +++ b/app/jobs/domain/twitter/job/media_job.rb @@ -17,7 +17,7 @@ class Domain::Twitter::Job::MediaJob < Domain::Twitter::Job::TwitterJobBase response = http_client.get(@media.url_str) - logger.debug "#{HexUtil.humansize(T.must(response.log_entry.response&.size))} / " + + logger.debug "#{HexUtil.humansize(T.must(response.log_entry.response&.size_bytes))} / " + "#{response.log_entry.content_type} / " + "#{response.log_entry.response_time_ms} ms" diff --git a/app/lib/domain/e621/tag_util.rb b/app/lib/domain/e621/tag_util.rb index 12a3dcb9..ce729965 100644 --- a/app/lib/domain/e621/tag_util.rb +++ b/app/lib/domain/e621/tag_util.rb @@ -8,7 +8,7 @@ class Domain::E621::TagUtil sig do params( post_json: T::Hash[String, T.untyped], - caused_by_entry: T.nilable(ReduxApplicationRecord), + caused_by_entry: T.nilable(HttpLogEntry), force_update: T::Boolean, ).returns(Domain::Post::E621Post) end diff --git a/app/lib/domain/e621/task/fix_e621_post_missing_files.rb b/app/lib/domain/e621/task/fix_e621_post_missing_files.rb index 6931c121..adb400b0 100644 --- a/app/lib/domain/e621/task/fix_e621_post_missing_files.rb +++ b/app/lib/domain/e621/task/fix_e621_post_missing_files.rb @@ -11,7 +11,7 @@ class Domain::E621::Task::FixE621PostMissingFiles return end - index_page_contents = index_page.response&.contents + index_page_contents = index_page.response_bytes unless index_page_contents logger.error("no index page contents for post #{post.id}") return diff --git a/app/lib/fa_backfill_favs.rb b/app/lib/fa_backfill_favs.rb index b1a6b21d..106e071a 100644 --- a/app/lib/fa_backfill_favs.rb +++ b/app/lib/fa_backfill_favs.rb @@ -48,10 +48,10 @@ class FaBackfillFavs ) entries.each do |entry| - response = T.let(entry.response, T.nilable(BlobEntry)) + response = T.let(entry.response, T.nilable(BlobFile)) next unless response - contents = T.let(response.contents, T.nilable(String)) + contents = T.let(response.content_bytes, T.nilable(String)) next unless contents page = diff --git a/app/lib/scraper/http_client.rb b/app/lib/scraper/http_client.rb index 61c177fc..2a53ac07 100644 --- a/app/lib/scraper/http_client.rb +++ b/app/lib/scraper/http_client.rb @@ -79,7 +79,7 @@ class Scraper::HttpClient from_cache ? "[" + "CACHED".light_green.bold + "]" : nil, "[entry #{log_entry.id.to_s.bold} /", "GET #{response_code_colorized} /", - "#{HexUtil.humansize(T.must(response_blob_entry.bytes_stored)).bold} / #{HexUtil.humansize(T.must(response_blob_entry.size)).bold}]", + "#{HexUtil.humansize(T.must(response_blob_entry.size_bytes)).bold}]", "[#{response_time_ms.to_s.bold} ms / #{total_time_ms.to_s.bold} ms]", log_entry.uri.to_s.black, ].compact.join(" "), @@ -112,7 +112,7 @@ class Scraper::HttpClient if use_http_cache if (cached_response = HttpLogEntry.find_by_uri(uri)) && (status_code = cached_response.status_code) && - (body = cached_response.response&.contents) + (body = cached_response.response&.content_bytes) print_request_performed_log_line( from_cache: true, log_entry: cached_response, @@ -175,10 +175,9 @@ class Scraper::HttpClient total_time_ms = -1 begin response_blob_entry = - BlobEntry.find_or_build( - content_type: content_type, - contents: response_body, - ) + BlobFile.find_or_initialize_from_contents(response_body) do |blob_file| + blob_file.content_type = content_type + end scrubbed_uri = @config.scrub_stored_uri(uri) log_entry = @@ -200,9 +199,6 @@ class Scraper::HttpClient }, ) - # double write blob_file while migrating - response_blob_file = - BlobFile.find_or_initialize_from_blob_entry(response_blob_entry) total_time_ms = ((Time.now - requested_at) * 1000).to_i Scraper::Metrics::HttpClientMetrics.observe_request_finish( @@ -215,11 +211,6 @@ class Scraper::HttpClient ) log_entry.save! - begin - response_blob_file.save unless response_blob_file.persisted? - rescue => e - puts "error saving blob file #{HexUtil.bin2hex(T.must(response_blob_file.sha256))}: #{e}" - end rescue StandardError retries += 1 retry if retries < 2 diff --git a/app/models/blob_file.rb b/app/models/blob_file.rb index bce15657..4fbaae9d 100644 --- a/app/models/blob_file.rb +++ b/app/models/blob_file.rb @@ -68,6 +68,23 @@ class BlobFile < ReduxApplicationRecord end end + sig do + params( + contents: String, + block: T.proc.params(blob_file: BlobFile).void, + ).returns(BlobFile) + end + def self.find_or_initialize_from_contents(contents, &block) + sha256 = Digest::SHA256.digest(contents) + BlobFile.migrate_sha256!(sha256) || + BlobFile.find_or_initialize_by( + sha256: Digest::SHA256.digest(contents), + ) do |blob_file| + blob_file.content_bytes = contents + block.call(blob_file) + end + end + sig { params(blob_entry: BlobEntry).returns(BlobFile) } def self.initialize_from_blob_entry(blob_entry) BlobFile.new( @@ -82,6 +99,8 @@ class BlobFile < ReduxApplicationRecord def self.migrate_sha256!(sha256) # convert to binary if hex formatted sha256 = HexUtil.hex2bin(sha256) if sha256.length == 64 + blob_file = BlobFile.find_by(sha256: sha256) + return blob_file if blob_file blob_entry = BlobEntry.find_by(sha256: sha256) return nil unless blob_entry blob_file = BlobFile.find_or_initialize_from_blob_entry(blob_entry) diff --git a/app/models/domain/fa/post.rb b/app/models/domain/fa/post.rb index 9fa63c14..3898edac 100644 --- a/app/models/domain/fa/post.rb +++ b/app/models/domain/fa/post.rb @@ -153,7 +153,7 @@ class Domain::Fa::Post < ReduxApplicationRecord pa = posted_at return pa if pa begin - contents = guess_last_submission_page&.response&.contents + contents = guess_last_submission_page&.response_bytes if contents parser = Domain::Fa::Parser::Page.new(contents) parser.submission.posted_date if parser.probably_submission? diff --git a/app/models/domain/fa/user_avatar.rb b/app/models/domain/fa/user_avatar.rb index ef8e7749..582b3682 100644 --- a/app/models/domain/fa/user_avatar.rb +++ b/app/models/domain/fa/user_avatar.rb @@ -17,12 +17,12 @@ class Domain::Fa::UserAvatar < ReduxApplicationRecord belongs_to :user, class_name: "::Domain::Fa::User" belongs_to :file, foreign_key: :file_sha256, - class_name: "::BlobEntry", + class_name: "::BlobFile", optional: true belongs_to :log_entry, class_name: "::HttpLogEntry", optional: true def file - @file_model ||= BlobEntry.ensure(file_sha256) if file_sha256 + @file_model ||= BlobFile.migrate_sha256!(file_sha256) if file_sha256 end def file_uri diff --git a/app/models/domain/post/fa_post.rb b/app/models/domain/post/fa_post.rb index 42a1c5be..23ccac60 100644 --- a/app/models/domain/post/fa_post.rb +++ b/app/models/domain/post/fa_post.rb @@ -171,7 +171,7 @@ class Domain::Post::FaPost < Domain::Post pa = super return pa if pa begin - contents = guess_last_submission_log_entry&.response&.contents + contents = guess_last_submission_log_entry&.response_bytes if contents parser = Domain::Fa::Parser::Page.new(contents) parser.submission.posted_date if parser.probably_submission? diff --git a/app/models/domain/post/inkbunny_post.rb b/app/models/domain/post/inkbunny_post.rb index 9ed153d6..cacf17b9 100644 --- a/app/models/domain/post/inkbunny_post.rb +++ b/app/models/domain/post/inkbunny_post.rb @@ -120,8 +120,7 @@ class Domain::Post::InkbunnyPost < Domain::Post # TODO - this is a mess, need to backfill descriptions on inkbunny posts description || self.ib_detail_raw&.dig("submission_json", "description") || begin - contents = self.deep_update_log_entry&.response&.contents - if contents.present? + if contents = self.deep_update_log_entry&.response_bytes json = JSON.parse(contents) json["submissions"] .find { |s| s["submission_id"] == self.ib_id&.to_s } diff --git a/app/models/domain/post_file.rb b/app/models/domain/post_file.rb index b9712c41..7eb87c65 100644 --- a/app/models/domain/post_file.rb +++ b/app/models/domain/post_file.rb @@ -6,7 +6,7 @@ class Domain::PostFile < ReduxApplicationRecord belongs_to :post, foreign_key: :post_id, class_name: "::Domain::Post" belongs_to :log_entry, class_name: "::HttpLogEntry", optional: true belongs_to :blob, - class_name: "::BlobEntry", + class_name: "::BlobFile", optional: true, foreign_key: :blob_sha256 @@ -34,6 +34,17 @@ class Domain::PostFile < ReduxApplicationRecord self.type ||= self.class.name if new_record? end + sig { returns(T.nilable(BlobFile)) } + def blob + super || + begin + @blob_file_model = T.let(@blob_file_model, T.nilable(BlobFile)) + @blob_file_model ||= + ((sha256 = self.blob_sha256) ? BlobFile.migrate_sha256!(sha256) : nil) + @blob_file_model + end + end + sig { params(le: T.nilable(HttpLogEntry)).returns(T.nilable(HttpLogEntry)) } def log_entry=(le) self.blob_sha256 ||= le.response_sha256 if le.present? diff --git a/app/models/domain/user/fa_user.rb b/app/models/domain/user/fa_user.rb index 581162a4..b5bdb7f3 100644 --- a/app/models/domain/user/fa_user.rb +++ b/app/models/domain/user/fa_user.rb @@ -109,7 +109,7 @@ class Domain::User::FaUser < Domain::User account_status || begin if (hle = guess_last_user_page_log_entry) && - (response = hle.response) && (contents = response.contents) + (contents = hle.response_bytes) parser = Domain::Fa::Parser::Page.new(contents, require_logged_in: false) parser.user_page.account_status.to_s if parser.probably_user_page? diff --git a/app/models/http_log_entry.rb b/app/models/http_log_entry.rb index f9820f45..fc38d356 100644 --- a/app/models/http_log_entry.rb +++ b/app/models/http_log_entry.rb @@ -10,7 +10,7 @@ class HttpLogEntry < ReduxApplicationRecord belongs_to :response, foreign_key: :response_sha256, - class_name: "::BlobEntry", + class_name: "::BlobFile", autosave: true belongs_to :request_headers, class_name: "::HttpLogEntryHeader" @@ -78,11 +78,24 @@ class HttpLogEntry < ReduxApplicationRecord sig { returns(T.nilable(Integer)) } def response_size - if association(:response).loaded? - T.must(self.response).size - else - BlobEntry.where(sha256: response_sha256).pick(:size) - end + response&.size_bytes + end + + sig { returns(T.nilable(String)) } + def response_bytes + response&.content_bytes + end + + sig { returns(T.nilable(BlobFile)) } + def response + super || + begin + @response_blob_file ||= T.let(nil, T.nilable(BlobFile)) + if sha256 = response_sha256 + @response_blob_file ||= BlobFile.migrate_sha256!(sha256) + end + @response_blob_file + end end sig { params(uri: T.any(String, Addressable::URI)).void } diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 1b827be8..d2aef3f6 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -12,7 +12,7 @@ window.MiniProfiler.patchesApplied = true; <% end %> - <%= favicon_link_tag "refurrer-logo.png" %> + <%= favicon_link_tag "refurrer-logo-icon.png" %> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= javascript_pack_tag "application-bundle" %> @@ -25,7 +25,7 @@

<%= link_to root_path, class: "flex items-center" do %> - <%= image_tag asset_path("refurrer-logo.png"), class: "w-12 h-12 mr-2" %> + <%= image_tag asset_path("refurrer-logo-md.png"), class: "w-12 h-12 mr-2" %> ReFurrer diff --git a/app/views/log_entries/renderers/_image.html.erb b/app/views/log_entries/renderers/_image.html.erb index 50db1777..3170eb30 100644 --- a/app/views/log_entries/renderers/_image.html.erb +++ b/app/views/log_entries/renderers/_image.html.erb @@ -1 +1,2 @@ +<% path = blob_path(HexUtil.bin2hex(log_entry.response_sha256), format: "jpg", thumb: "content-container") %> image diff --git a/config/initializers/prometheus_exporter.rb b/config/initializers/prometheus_exporter.rb index ffdf732e..468eba7e 100644 --- a/config/initializers/prometheus_exporter.rb +++ b/config/initializers/prometheus_exporter.rb @@ -40,6 +40,28 @@ else # connect to a Prometheus server, which we can't rely on in a test environment. $stderr.puts "PrometheusExporter is disabled in test, console, and rake task environments" module PrometheusExporter + module Instrumentation + class PeriodicStats + class << self + extend T::Sig + sig do + params( + args: T.untyped, + frequency: T.untyped, + client: T.untyped, + kwargs: T.untyped, + ).void + end + def start(*args, frequency:, client: T.unsafe(nil), **kwargs) + end + + sig { params(blk: T.proc.void).void } + def worker_loop(&blk) + end + end + end + end + class Client class RemoteMetric end diff --git a/sorbet/rbi/dsl/domain/fa/user_avatar.rbi b/sorbet/rbi/dsl/domain/fa/user_avatar.rbi index c283a1a2..163e19d2 100644 --- a/sorbet/rbi/dsl/domain/fa/user_avatar.rbi +++ b/sorbet/rbi/dsl/domain/fa/user_avatar.rbi @@ -452,7 +452,7 @@ class Domain::Fa::UserAvatar end module GeneratedAssociationMethods - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def build_file(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } @@ -461,10 +461,10 @@ class Domain::Fa::UserAvatar sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Fa::User) } def build_user(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_file(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_file!(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } @@ -479,10 +479,10 @@ class Domain::Fa::UserAvatar sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Fa::User) } def create_user!(*args, &blk); end - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def file; end - sig { params(value: T.nilable(::BlobEntry)).void } + sig { params(value: T.nilable(::BlobFile)).void } def file=(value); end sig { returns(T::Boolean) } @@ -503,7 +503,7 @@ class Domain::Fa::UserAvatar sig { returns(T::Boolean) } def log_entry_previously_changed?; end - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def reload_file; end sig { returns(T.nilable(::HttpLogEntry)) } diff --git a/sorbet/rbi/dsl/domain/post_file.rbi b/sorbet/rbi/dsl/domain/post_file.rbi index a23f5d66..fc848950 100644 --- a/sorbet/rbi/dsl/domain/post_file.rbi +++ b/sorbet/rbi/dsl/domain/post_file.rbi @@ -453,10 +453,10 @@ class Domain::PostFile end module GeneratedAssociationMethods - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def blob; end - sig { params(value: T.nilable(::BlobEntry)).void } + sig { params(value: T.nilable(::BlobFile)).void } def blob=(value); end sig { returns(T::Boolean) } @@ -465,7 +465,7 @@ class Domain::PostFile sig { returns(T::Boolean) } def blob_previously_changed?; end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def build_blob(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } @@ -474,10 +474,10 @@ class Domain::PostFile sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Post) } def build_post(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_blob(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_blob!(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } @@ -516,7 +516,7 @@ class Domain::PostFile sig { returns(T::Boolean) } def post_previously_changed?; end - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def reload_blob; end sig { returns(T.nilable(::HttpLogEntry)) } diff --git a/sorbet/rbi/dsl/domain/post_file/inkbunny_post_file.rbi b/sorbet/rbi/dsl/domain/post_file/inkbunny_post_file.rbi index b00ff569..1b11fa45 100644 --- a/sorbet/rbi/dsl/domain/post_file/inkbunny_post_file.rbi +++ b/sorbet/rbi/dsl/domain/post_file/inkbunny_post_file.rbi @@ -454,13 +454,13 @@ class Domain::PostFile::InkbunnyPostFile module EnumMethodsModule; end module GeneratedAssociationMethods - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def blob; end - sig { params(value: T.nilable(::BlobEntry)).void } + sig { params(value: T.nilable(::BlobFile)).void } def blob=(value); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def build_blob(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } @@ -469,10 +469,10 @@ class Domain::PostFile::InkbunnyPostFile sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Post) } def build_post(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_blob(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_blob!(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } @@ -499,7 +499,7 @@ class Domain::PostFile::InkbunnyPostFile sig { params(value: T.nilable(::Domain::Post)).void } def post=(value); end - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def reload_blob; end sig { returns(T.nilable(::HttpLogEntry)) } diff --git a/sorbet/rbi/dsl/good_job/job.rbi b/sorbet/rbi/dsl/good_job/job.rbi index 1923e936..ef6a7a2c 100644 --- a/sorbet/rbi/dsl/good_job/job.rbi +++ b/sorbet/rbi/dsl/good_job/job.rbi @@ -526,9 +526,6 @@ class GoodJob::Job sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } def active_job_id(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } - def adapter_class(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } def advisory_lock(*args, &blk); end @@ -550,12 +547,6 @@ class GoodJob::Job sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } def arel_columns(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } - def bind_value(*args, &blk); end - - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } - def coalesce_scheduled_at_created_at(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } def create_with(*args, &blk); end @@ -674,9 +665,6 @@ class GoodJob::Job end def page(num = nil); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) } - def params_execution_count(*args, &blk); end - sig do params( num: Integer @@ -2176,9 +2164,6 @@ class GoodJob::Job sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } def active_job_id(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } - def adapter_class(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } def advisory_lock(*args, &blk); end @@ -2200,12 +2185,6 @@ class GoodJob::Job sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } def arel_columns(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } - def bind_value(*args, &blk); end - - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } - def coalesce_scheduled_at_created_at(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } def create_with(*args, &blk); end @@ -2324,9 +2303,6 @@ class GoodJob::Job end def page(num = nil); end - sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) } - def params_execution_count(*args, &blk); end - sig do params( num: Integer diff --git a/sorbet/rbi/dsl/http_log_entry.rbi b/sorbet/rbi/dsl/http_log_entry.rbi index cfc72543..8f942516 100644 --- a/sorbet/rbi/dsl/http_log_entry.rbi +++ b/sorbet/rbi/dsl/http_log_entry.rbi @@ -453,7 +453,7 @@ class HttpLogEntry sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntryHeader) } def build_request_headers(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def build_response(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntryHeader) } @@ -483,10 +483,10 @@ class HttpLogEntry sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntryHeader) } def create_request_headers!(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_response(*args, &blk); end - sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) } + sig { params(args: T.untyped, blk: T.untyped).returns(::BlobFile) } def create_response!(*args, &blk); end sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntryHeader) } @@ -501,7 +501,7 @@ class HttpLogEntry sig { returns(T.nilable(::HttpLogEntryHeader)) } def reload_request_headers; end - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def reload_response; end sig { returns(T.nilable(::HttpLogEntryHeader)) } @@ -531,10 +531,10 @@ class HttpLogEntry sig { void } def reset_response_headers; end - sig { returns(T.nilable(::BlobEntry)) } + sig { returns(T.nilable(::BlobFile)) } def response; end - sig { params(value: T.nilable(::BlobEntry)).void } + sig { params(value: T.nilable(::BlobFile)).void } def response=(value); end sig { returns(T::Boolean) } diff --git a/sorbet/tapioca/require.rb b/sorbet/tapioca/require.rb index f3896ec7..41e61d85 100644 --- a/sorbet/tapioca/require.rb +++ b/sorbet/tapioca/require.rb @@ -32,5 +32,6 @@ require "prometheus_exporter/client" require "prometheus_exporter/metric" require "prometheus_exporter/instrumentation" require "prometheus_exporter/instrumentation/active_record" +require "prometheus_exporter/instrumentation/periodic_stats" require "prometheus_exporter/instrumentation/good_job" require "prometheus_exporter/middleware" diff --git a/spec/factories/blob_file.rb b/spec/factories/blob_file.rb new file mode 100644 index 00000000..ff8b8e94 --- /dev/null +++ b/spec/factories/blob_file.rb @@ -0,0 +1,10 @@ +# typed: false +FactoryBot.define do + factory :blob_file do + transient { contents { "test content #{SecureRandom.alphanumeric(10)}" } } + content_bytes { contents } + size_bytes { contents.bytesize } + content_type { "text/plain" } + sha256 { Digest::SHA256.digest(contents) } + end +end diff --git a/spec/factories/domain/fa/users.rb b/spec/factories/domain/fa/users.rb index 4fb78e5c..52c30f17 100644 --- a/spec/factories/domain/fa/users.rb +++ b/spec/factories/domain/fa/users.rb @@ -9,7 +9,7 @@ FactoryBot.define do trait :with_avatar do after(:create) do |user| - create(:domain_fa_user_avatar, user: user, file: create(:blob_entry)) + create(:domain_fa_user_avatar, user: user, file: create(:blob_file)) end end end diff --git a/spec/factories/http_log_entries.rb b/spec/factories/http_log_entries.rb index 862d32b2..0c0d7d12 100644 --- a/spec/factories/http_log_entries.rb +++ b/spec/factories/http_log_entries.rb @@ -15,7 +15,7 @@ FactoryBot.define do performed_by { "direct" } # Create associated records - association :response, factory: :blob_entry + association :response, factory: :blob_file association :request_headers, factory: :http_log_entry_header association :response_headers, factory: :http_log_entry_header diff --git a/spec/helpers/http_client_mock_helpers.rb b/spec/helpers/http_client_mock_helpers.rb index 2c99adbc..93cdfe4f 100644 --- a/spec/helpers/http_client_mock_helpers.rb +++ b/spec/helpers/http_client_mock_helpers.rb @@ -30,11 +30,11 @@ class HttpClientMockHelpers request_headers: build(:http_log_entry_header), response_headers: build(:http_log_entry_header), response: - BlobEntry.find_by(sha256: sha256) || + BlobFile.find_by(sha256: sha256) || build( - :blob_entry, + :blob_file, content_type: request[:content_type], - content: request[:contents], + contents: request[:contents], ), ) log_entry.save! @@ -58,7 +58,7 @@ class HttpClientMockHelpers logger.info "[mock http client] [#{method}] [#{uri}] [#{opts.inspect.truncate(80)}]" Scraper::HttpClient::Response.new( status_code: log_entry.status_code, - body: log_entry.response.contents, + body: log_entry.response.content_bytes, log_entry: log_entry, ) end, @@ -83,11 +83,11 @@ class HttpClientMockHelpers performed_by: "direct", response_time_ms: rand(20..100), response: - BlobEntry.find_by(sha256: sha256) || + BlobFile.find_by(sha256: sha256) || build( - :blob_entry, + :blob_file, content_type: request[:content_type], - content: request[:contents], + contents: request[:contents], ), ) [request[:uri], log_entry] @@ -103,7 +103,7 @@ class HttpClientMockHelpers logger.info "[mock http client] [get] [#{uri}] [#{opts.inspect.truncate(80)}]" Scraper::HttpClient::Response.new( status_code: log_entry.status_code, - body: log_entry.response.contents, + body: log_entry.response.content_bytes, log_entry: log_entry, ) end diff --git a/spec/helpers/log_entries_helper_spec.rb b/spec/helpers/log_entries_helper_spec.rb index 71bf2ea1..3d39f8a5 100644 --- a/spec/helpers/log_entries_helper_spec.rb +++ b/spec/helpers/log_entries_helper_spec.rb @@ -7,7 +7,7 @@ RSpec.describe LogEntriesHelper, type: :helper do :http_log_entry, response: build( - :blob_entry, + :blob_file, content_type: "application/msword", contents: File.binread( diff --git a/spec/jobs/domain/e621/job/posts_index_job_spec.rb b/spec/jobs/domain/e621/job/posts_index_job_spec.rb index bbc67007..07fe38bb 100644 --- a/spec/jobs/domain/e621/job/posts_index_job_spec.rb +++ b/spec/jobs/domain/e621/job/posts_index_job_spec.rb @@ -23,7 +23,7 @@ describe Domain::E621::Job::PostsIndexJob do ], ) - described_class.perform_now({ caused_by_entry: file }) + perform_now({ caused_by_entry: file }) expect(Domain::Post::E621Post.count).to eq(5) post = Domain::Post::E621Post.find_by(e621_id: 4_247_443) diff --git a/spec/jobs/domain/fa/job/user_avatar_job_spec.rb b/spec/jobs/domain/fa/job/user_avatar_job_spec.rb index 566ffe18..80df780d 100644 --- a/spec/jobs/domain/fa/job/user_avatar_job_spec.rb +++ b/spec/jobs/domain/fa/job/user_avatar_job_spec.rb @@ -103,8 +103,8 @@ describe Domain::Fa::Job::UserAvatarJob do :http_log_entry, response: create( - :blob_entry, - content: avatar_fixture_file_2, + :blob_file, + contents: avatar_fixture_file_2, content_type: "image/gif", ), ) @@ -143,8 +143,8 @@ describe Domain::Fa::Job::UserAvatarJob do :http_log_entry, response: create( - :blob_entry, - content: avatar_fixture_file, + :blob_file, + contents: avatar_fixture_file, content_type: "image/gif", ), ) diff --git a/spec/lib/fa_backfill_favs_spec.rb b/spec/lib/fa_backfill_favs_spec.rb index d6e7cea7..abe8ce85 100644 --- a/spec/lib/fa_backfill_favs_spec.rb +++ b/spec/lib/fa_backfill_favs_spec.rb @@ -52,13 +52,7 @@ describe FaBackfillFavs do requested_at: Time.current, request_headers: empty_headers, response_headers: empty_headers, - response: - BlobEntry.create!( - contents: iiszed_html, - content_type: "text/html", - sha256: Digest::SHA256.digest(iiszed_html), - size: iiszed_html.bytesize, - ), + response: create(:blob_file, contents: iiszed_html), ) HttpLogEntry.create!( @@ -73,13 +67,7 @@ describe FaBackfillFavs do requested_at: Time.current, request_headers: empty_headers, response_headers: empty_headers, - response: - BlobEntry.create!( - contents: renamonzeo_html, - content_type: "text/html", - sha256: Digest::SHA256.digest(renamonzeo_html), - size: renamonzeo_html.bytesize, - ), + response: create(:blob_file, contents: renamonzeo_html), ) HttpLogEntry.create!( @@ -94,13 +82,7 @@ describe FaBackfillFavs do requested_at: Time.current, request_headers: empty_headers, response_headers: empty_headers, - response: - BlobEntry.create!( - contents: stickiest_html, - content_type: "text/html", - sha256: Digest::SHA256.digest(stickiest_html), - size: stickiest_html.bytesize, - ), + response: create(:blob_file, contents: stickiest_html), ) end diff --git a/spec/lib/scraper/http_client_spec.rb b/spec/lib/scraper/http_client_spec.rb index 0c9ba7f9..e2f960ee 100644 --- a/spec/lib/scraper/http_client_spec.rb +++ b/spec/lib/scraper/http_client_spec.rb @@ -24,7 +24,7 @@ describe Scraper::HttpClient do client = Scraper::HttpClient.new( TestHttpClientConfig.new, - SpecUtil.mock_http_performer(:get, "https://example.com") + SpecUtil.mock_http_performer(:get, "https://example.com"), ) assert_raises(Scraper::HttpClient::InvalidURLError) do client.get("https://foobar.com") @@ -39,32 +39,32 @@ describe Scraper::HttpClient do :get, "https://www.example.com/", request_headers: { - "cookie" => "" + "cookie" => "", }, response_code: 200, response_time_ms: 15, response_headers: { - "content-type" => "text/plain" + "content-type" => "text/plain", }, - response_body: "the response " + SpecUtil.random_string(16) - ) + response_body: "the response " + SpecUtil.random_string(16), + ), ) client.logger.level = :error # note the lack of trailing slash - http client should set path to '/' response = client.get("https://www.example.com") - assert_equal 200, response.status_code - assert_match /the response /, response.body + assert_equal(200, response.status_code) + assert_match(/the response /, response.body) log_entry = response.log_entry - assert log_entry.persisted? - assert_equal "text/plain", log_entry.content_type - assert_in_delta Time.now, log_entry.requested_at, 50 - assert_equal 15, log_entry.response_time_ms - assert_equal "get", log_entry.verb - assert_equal 200, log_entry.status_code - assert_equal "text/plain", log_entry.response.content_type - assert_match /the response/, log_entry.response.contents + assert(log_entry.persisted?) + assert_equal("text/plain", log_entry.content_type) + assert_in_delta(Time.now, log_entry.requested_at, 50) + assert_equal(15, log_entry.response_time_ms) + assert_equal("get", log_entry.verb) + assert_equal(200, log_entry.status_code) + assert_equal("text/plain", log_entry.response.content_type) + assert_match(/the response/, log_entry.response.content_bytes) end end diff --git a/spec/models/http_log_entry_spec.rb b/spec/models/http_log_entry_spec.rb index 8de3e58f..767ced29 100644 --- a/spec/models/http_log_entry_spec.rb +++ b/spec/models/http_log_entry_spec.rb @@ -19,7 +19,7 @@ RSpec.describe HttpLogEntry, type: :model do end describe "associations" do - it { should belong_to(:response).class_name("::BlobEntry") } + it { should belong_to(:response).class_name("::BlobFile") } it { should belong_to(:request_headers).class_name("::HttpLogEntryHeader") } it do should belong_to(:response_headers).class_name("::HttpLogEntryHeader") @@ -144,14 +144,14 @@ RSpec.describe HttpLogEntry, type: :model do context "when response association is loaded" do it "returns size from response object" do test_content = "test content" - entry.response = build(:blob_entry, content: test_content) + entry.response = build(:blob_file, contents: test_content) expect(entry.response_size).to eq(test_content.bytesize) end end context "when response association is not loaded" do it "queries size directly from database" do - size = entry.response.size + size = entry.response.size_bytes entry.association(:response).reset expect(entry.response_size).to eq(size) end