create multiple fingerprints wip
This commit is contained in:
36
Rakefile
36
Rakefile
@@ -503,3 +503,39 @@ task clear_e621_user_favs_migrated_at: :environment do
|
||||
# pb.progress += b.size
|
||||
# end
|
||||
end
|
||||
|
||||
task create_post_file_fingerprints: :environment do
|
||||
def migrate_posts_for_user(user)
|
||||
puts "migrating posts for #{user.to_param}"
|
||||
pb =
|
||||
ProgressBar.create(
|
||||
total: user.posts.count,
|
||||
format: "%t: %c/%C %B %p%% %a %e",
|
||||
)
|
||||
user
|
||||
.posts
|
||||
.includes(files: :blob)
|
||||
.find_in_batches(batch_size: 16) do |batch|
|
||||
ReduxApplicationRecord.transaction do
|
||||
batch.each do |post|
|
||||
post.files.each { |file| file.ensure_fingerprint! }
|
||||
puts "migrated #{post.id} / #{post.to_param} / '#{post.title_for_view}'"
|
||||
pb.progress = [pb.progress + 1, pb.total].min
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ENV["user"].present?
|
||||
for_user = ENV["user"] || raise("need 'user'")
|
||||
user = DomainController.find_model_from_param(Domain::User, for_user)
|
||||
raise "user '#{for_user}' not found" unless user
|
||||
migrate_posts_for_user(user)
|
||||
elsif ENV["users_descending"].present?
|
||||
# all users with posts, ordered by post count descending
|
||||
users = Domain::User::FaUser.order(num_watched_by: :desc).limit(20)
|
||||
users.find_each(batch_size: 1) { |user| migrate_posts_for_user(user) }
|
||||
else
|
||||
raise "need 'user' or 'users_descending'"
|
||||
end
|
||||
end
|
||||
|
||||
1
TODO.md
1
TODO.md
@@ -38,3 +38,4 @@
|
||||
- [ ] fix for IDs that have a dot in them - e.g. https://refurrer.com/users/fa@jakke.
|
||||
- [ ] Rich inline links to e621 e.g. https://refurrer.com/posts/fa@60070060
|
||||
- [ ] Find FaPost that have favs recorded but no scan / file, enqueue scan
|
||||
- [ ] Bunch of posts with empty responses: posts = Domain::Post.joins(files: :log_entry).where(files: { http_log_entries: { response_sha256: BlobFile::EMPTY_FILE_SHA256 }}).limit(10)
|
||||
|
||||
@@ -205,8 +205,6 @@ class BlobEntriesController < ApplicationController
|
||||
[800, 600]
|
||||
when "content-container"
|
||||
[768, 2048]
|
||||
# [2048, 2048]
|
||||
# [128, 128]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ class Domain::PostsController < DomainController
|
||||
return false unless content_type
|
||||
|
||||
ret =
|
||||
Domain::PostFileFingerprint::VALID_CONTENT_TYPES.any? do |type|
|
||||
Domain::PostFile::Thumbnail::THUMBABLE_CONTENT_TYPES.any? do |type|
|
||||
content_type.match?(type)
|
||||
end
|
||||
|
||||
@@ -231,16 +231,16 @@ class Domain::PostsController < DomainController
|
||||
sig { params(image_path: String).returns(String) }
|
||||
def generate_fingerprint(image_path)
|
||||
# Use the new from_file_path method to create a fingerprint
|
||||
fingerprint = Domain::PostFileFingerprint.from_file_path(image_path)
|
||||
# The hash_value is guaranteed to be present by the from_file_path implementation
|
||||
T.must(fingerprint.hash_value)
|
||||
Domain::PostFile::BitFingerprint.from_file_path(image_path)
|
||||
end
|
||||
|
||||
# Find similar images based on the fingerprint
|
||||
sig { params(hash_value: String).returns(ActiveRecord::Relation) }
|
||||
def find_similar_fingerprints(hash_value)
|
||||
sig { params(fingerprint_value: String).returns(ActiveRecord::Relation) }
|
||||
def find_similar_fingerprints(fingerprint_value)
|
||||
# Use the model's similar_to_fingerprint method directly
|
||||
Domain::PostFileFingerprint.similar_to_fingerprint(hash_value).limit(10)
|
||||
Domain::PostFile::BitFingerprint.order_by_fingerprint_distance(
|
||||
fingerprint_value,
|
||||
).limit(10)
|
||||
end
|
||||
|
||||
sig { override.returns(DomainController::DomainParamConfig) }
|
||||
|
||||
@@ -53,44 +53,21 @@ class DomainController < ApplicationController
|
||||
) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
sig(:final) do
|
||||
type_parameters(:Klass)
|
||||
.params(
|
||||
klass:
|
||||
T.all(
|
||||
T.class_of(ReduxApplicationRecord),
|
||||
HasCompositeToParam::ClassMethods,
|
||||
T::Class[T.type_parameter(:Klass)],
|
||||
HasCompositeToParam::ClassMethods[T.type_parameter(:Klass)],
|
||||
),
|
||||
param: T.nilable(String),
|
||||
)
|
||||
.returns(T.nilable(T.type_parameter(:Klass)))
|
||||
end
|
||||
def self.find_model_from_param(klass, param)
|
||||
composite_param = HasCompositeToParam.parse_composite_param(param)
|
||||
if composite_param.nil?
|
||||
raise ActionController::BadRequest, "invalid id: #{param.inspect}"
|
||||
end
|
||||
|
||||
klass_param, id = composite_param
|
||||
group_klass =
|
||||
klass.subclasses.find do |subclass|
|
||||
param_prefix, _ =
|
||||
T.cast(
|
||||
subclass,
|
||||
HasCompositeToParam::ClassMethods,
|
||||
).param_prefix_and_attribute
|
||||
param_prefix == klass_param
|
||||
end
|
||||
|
||||
if group_klass.nil?
|
||||
raise ActionController::BadRequest, "unknown model type: #{klass_param}"
|
||||
end
|
||||
_, param_attribute =
|
||||
T.cast(
|
||||
group_klass,
|
||||
HasCompositeToParam::ClassMethods,
|
||||
).param_prefix_and_attribute
|
||||
group_klass.find_by(param_attribute => id)
|
||||
klass.find_by_param(param)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -287,6 +287,7 @@ module Domain::PostsHelper
|
||||
hosts: FA_HOSTS,
|
||||
patterns: [
|
||||
%r{/view/(\d+)/?},
|
||||
%r{/full/(\d+)/?},
|
||||
%r{/controls/submissions/changeinfo/(\d+)/?},
|
||||
],
|
||||
find_proc: ->(helper, match, _) do
|
||||
|
||||
@@ -26,7 +26,10 @@ export const getPreviewContainerClassName = (
|
||||
maxWidth: string,
|
||||
maxHeight: string,
|
||||
) => {
|
||||
// calculate if the page is mobile
|
||||
const isMobile = window.innerWidth < 640;
|
||||
return [
|
||||
isMobile ? 'hidden' : '',
|
||||
`max-w-[${maxWidth}] max-h-[${maxHeight}] rounded-lg`,
|
||||
'border border-slate-400 bg-slate-100',
|
||||
'divide-y divide-slate-300',
|
||||
|
||||
@@ -11,7 +11,7 @@ class Domain::Fa::Job::Base < Scraper::JobBase
|
||||
|
||||
protected
|
||||
|
||||
BUGGY_USER_URL_NAMES = T.let(["click here", ".."], T::Array[String])
|
||||
BUGGY_USER_URL_NAMES = T.let(["click here", "..", "."], T::Array[String])
|
||||
|
||||
sig { params(user: Domain::User::FaUser).returns(T::Boolean) }
|
||||
def buggy_user?(user)
|
||||
|
||||
16
app/jobs/domain/post_file_thumbnail_job.rb
Normal file
16
app/jobs/domain/post_file_thumbnail_job.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# typed: strict
|
||||
class Domain::PostFileThumbnailJob < Scraper::JobBase
|
||||
queue_as :thumbnails
|
||||
|
||||
sig { override.returns(Symbol) }
|
||||
def self.http_factory_method
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
sig { override.params(args: T::Hash[Symbol, T.untyped]).void }
|
||||
def perform(args)
|
||||
post_file = T.cast(args[:post_file], Domain::PostFile)
|
||||
Domain::PostFile::Thumbnail.create_for_post_file!(post_file)
|
||||
Domain::PostFile::BitFingerprint.create_for_post_file!(post_file)
|
||||
end
|
||||
end
|
||||
@@ -1,35 +1,22 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
class Domain::Fa::PostEnqueuer
|
||||
include HasBulkEnqueueJobs
|
||||
include HasColorLogger
|
||||
include HasMeasureDuration
|
||||
include Domain::Fa::HasCountFailedInQueue
|
||||
|
||||
def initialize(
|
||||
reverse_scan_holes:,
|
||||
start_at:,
|
||||
low_water_mark:,
|
||||
high_water_mark:
|
||||
)
|
||||
def initialize(start_at:, stop_at:, low_water_mark:, high_water_mark:)
|
||||
stop_at ||= 0
|
||||
@low_water_mark = low_water_mark
|
||||
@high_water_mark = high_water_mark
|
||||
raise if @high_water_mark <= @low_water_mark
|
||||
@post_iterator =
|
||||
Enumerator.new do |e|
|
||||
if reverse_scan_holes
|
||||
while start_at > 0
|
||||
if !Domain::Post::FaPost.exists?(fa_id: start_at)
|
||||
e << [nil, start_at, nil]
|
||||
end
|
||||
start_at -= 1
|
||||
while start_at > stop_at
|
||||
if !Domain::Post::FaPost.exists?(fa_id: start_at)
|
||||
e << [nil, start_at, nil]
|
||||
end
|
||||
else
|
||||
Domain::Post::FaPost
|
||||
.where("id >= ?", start_at)
|
||||
.where
|
||||
.missing(:file)
|
||||
.where(state: "ok")
|
||||
.find_each { |p| e << [p.id, p.fa_id, p.file_url_str] }
|
||||
start_at -= 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -42,11 +29,7 @@ class Domain::Fa::PostEnqueuer
|
||||
"enqueuing #{to_enqueue.to_s.bold} more posts - #{already_enqueued.to_s.bold} already enqueued",
|
||||
)
|
||||
rows =
|
||||
measure(
|
||||
proc do |p|
|
||||
p && "gathered #{p.length.to_s.bold} posts" || "gathering posts..."
|
||||
end,
|
||||
) do
|
||||
measure("gathering posts...") do
|
||||
to_enqueue
|
||||
.times
|
||||
.map do
|
||||
|
||||
@@ -35,6 +35,11 @@ class BlobFile < ReduxApplicationRecord
|
||||
enum :version, { v1: 1 }
|
||||
after_initialize { self.version ||= :v1 }
|
||||
|
||||
has_many :phash_thumbnails,
|
||||
class_name: "Phash::Thumbnail",
|
||||
foreign_key: :blob_sha256,
|
||||
primary_key: :sha256
|
||||
|
||||
validates_presence_of(:sha256, :content_type, :size_bytes)
|
||||
validates :sha256, length: { is: 32 }
|
||||
validates :content_bytes,
|
||||
|
||||
@@ -4,27 +4,60 @@ module HasCompositeToParam
|
||||
extend T::Helpers
|
||||
abstract!
|
||||
requires_ancestor { Kernel }
|
||||
requires_ancestor { ReduxApplicationRecord }
|
||||
|
||||
sig(:final) { returns(T.nilable(String)) }
|
||||
def to_param
|
||||
klass, param = self.class.param_prefix_and_attribute
|
||||
klass, param = T.unsafe(self.class).param_prefix_and_attribute
|
||||
param_value = send(param)
|
||||
param_value.present? ? "#{klass}@#{param_value}" : nil
|
||||
end
|
||||
|
||||
sig { params(param: T.nilable(String)).returns(T.nilable([String, String])) }
|
||||
def self.parse_composite_param(param)
|
||||
return nil unless param.present?
|
||||
klass, param_value = param.split("@", 2)
|
||||
return nil unless klass.present? && param_value.present?
|
||||
[klass, param_value]
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
extend T::Generic
|
||||
requires_ancestor { Kernel }
|
||||
has_attached_class!
|
||||
abstract!
|
||||
|
||||
sig(:final) do
|
||||
params(param: T.nilable(String)).returns(T.nilable([String, String]))
|
||||
end
|
||||
def parse_composite_param(param)
|
||||
return nil unless param.present?
|
||||
klass, param_value = param.split("@", 2)
|
||||
return nil unless klass.present? && param_value.present?
|
||||
[klass, param_value]
|
||||
end
|
||||
|
||||
sig(:final) do
|
||||
params(param: T.nilable(String)).returns(T.nilable(T.attached_class))
|
||||
end
|
||||
def find_by_param(param)
|
||||
composite_param = parse_composite_param(param)
|
||||
if composite_param.nil?
|
||||
raise ActionController::BadRequest, "invalid id: #{param.inspect}"
|
||||
end
|
||||
|
||||
klass_param, id = composite_param
|
||||
klass = self
|
||||
group_klass =
|
||||
T
|
||||
.unsafe(klass)
|
||||
.subclasses
|
||||
.find do |subclass|
|
||||
param_prefix, _ = subclass.param_prefix_and_attribute
|
||||
param_prefix == klass_param
|
||||
end
|
||||
|
||||
if group_klass.nil?
|
||||
raise ActionController::BadRequest, "unknown model type: #{klass_param}"
|
||||
end
|
||||
_, param_attribute = group_klass.param_prefix_and_attribute
|
||||
group_klass.find_by(param_attribute => id)
|
||||
end
|
||||
|
||||
sig { abstract.returns([String, Symbol]) }
|
||||
def param_prefix_and_attribute
|
||||
end
|
||||
|
||||
@@ -10,12 +10,15 @@ class Domain::PostFile < ReduxApplicationRecord
|
||||
optional: true,
|
||||
foreign_key: :blob_sha256
|
||||
|
||||
has_one :fingerprint,
|
||||
class_name: "::Domain::PostFileFingerprint",
|
||||
foreign_key: :blob_sha256,
|
||||
primary_key: :blob_sha256,
|
||||
dependent: :destroy,
|
||||
inverse_of: :post_file
|
||||
has_many :bit_fingerprints,
|
||||
class_name: "::Domain::PostFile::BitFingerprint",
|
||||
dependent: :destroy,
|
||||
inverse_of: :post_file
|
||||
|
||||
has_many :thumbnails,
|
||||
class_name: "::Domain::PostFile::Thumbnail",
|
||||
dependent: :destroy,
|
||||
inverse_of: :post_file
|
||||
|
||||
attr_json :state, :string
|
||||
attr_json :url_str, :string
|
||||
@@ -41,19 +44,27 @@ class Domain::PostFile < ReduxApplicationRecord
|
||||
self.type ||= self.class.name if new_record?
|
||||
end
|
||||
|
||||
after_save do
|
||||
if self.fingerprint.nil? && (blob = self.blob) &&
|
||||
(content_type = blob.content_type) &&
|
||||
(
|
||||
Domain::PostFileFingerprint::VALID_CONTENT_TYPES.any? do |type|
|
||||
content_type.match?(type)
|
||||
end
|
||||
)
|
||||
fingerprint = Domain::PostFileFingerprint.from_post_file(self)
|
||||
fingerprint&.save!
|
||||
end
|
||||
rescue => e
|
||||
logger.error("could not save fingerprint for post_file #{self.id}: #{e}")
|
||||
before_save { self.blob_sha256 ||= self.log_entry&.response&.sha256 }
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def content_type
|
||||
return nil unless log_entry = self.log_entry
|
||||
return nil unless response = log_entry.response
|
||||
response.content_type ||
|
||||
begin
|
||||
return nil unless blob = self.blob
|
||||
blob.content_type
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def sha256
|
||||
self.blob_sha256 ||
|
||||
begin
|
||||
return nil unless log_entry = self.log_entry
|
||||
return nil unless response = log_entry.response
|
||||
response.sha256
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(BlobFile)) }
|
||||
|
||||
132
app/models/domain/post_file/bit_fingerprint.rb
Normal file
132
app/models/domain/post_file/bit_fingerprint.rb
Normal file
@@ -0,0 +1,132 @@
|
||||
# typed: strict
|
||||
class Domain::PostFile::BitFingerprint < ReduxApplicationRecord
|
||||
include AttrJsonRecordAliases
|
||||
self.table_name = "domain_post_file_bit_fingerprints"
|
||||
|
||||
belongs_to :post_file,
|
||||
class_name: "::Domain::PostFile",
|
||||
inverse_of: :bit_fingerprints
|
||||
|
||||
belongs_to :thumbnail, class_name: "::Domain::PostFile::Thumbnail"
|
||||
|
||||
validates :fingerprint_value, presence: true
|
||||
|
||||
# in bytes
|
||||
HASH_SIZE_BYTES = 32
|
||||
|
||||
# Find similar images based on the fingerprint
|
||||
sig { params(fingerprint: String).returns(ActiveRecord::Relation) }
|
||||
def self.order_by_fingerprint_distance(fingerprint)
|
||||
order(
|
||||
Arel.sql(
|
||||
"(fingerprint_value <~> '#{ActiveRecord::Base.connection.quote_string(fingerprint)}') ASC",
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(post_file: Domain::PostFile).returns(
|
||||
T::Array[Domain::PostFile::BitFingerprint],
|
||||
)
|
||||
end
|
||||
def self.create_for_post_file!(post_file)
|
||||
logger.tagged("fingerprint", make_arg_tag(post_file)) do
|
||||
thumbnails = post_file.thumbnails.where(thumb_type: :size_32_32).to_a
|
||||
existing_bit_fingerprints = post_file.bit_fingerprints.to_a
|
||||
|
||||
thumbnails_missing_fingerprints =
|
||||
thumbnails.reject do |thumbnail|
|
||||
existing_bit_fingerprints.any? do |existing_bit_fingerprint|
|
||||
existing_bit_fingerprint.thumbnail_id == thumbnail.id
|
||||
end
|
||||
end
|
||||
|
||||
logger.info(
|
||||
format_tags(
|
||||
make_tag(
|
||||
"thumbnails_missing_fingerprints",
|
||||
thumbnails_missing_fingerprints.map(&:id),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
thumbnails_missing_fingerprints
|
||||
.map do |thumbnail|
|
||||
logger.tagged(make_tag("thumbnail", thumbnail.id)) do
|
||||
unless thumbnail_path = thumbnail.absolute_file_path
|
||||
logger.info(format_tags("unable to compute thumbnail path"))
|
||||
next
|
||||
end
|
||||
fingerprint_value = from_file_path(thumbnail_path)
|
||||
fingerprint =
|
||||
post_file.bit_fingerprints.build(thumbnail:, fingerprint_value:)
|
||||
logger.info(
|
||||
format_tags(
|
||||
"computed fingerprint #{fingerprint_value.to_i(2).to_s(16).upcase}",
|
||||
),
|
||||
)
|
||||
fingerprint.save!
|
||||
fingerprint
|
||||
end
|
||||
end
|
||||
.compact
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate the Hamming distance between this fingerprint and another fingerprint
|
||||
# @param other_fingerprint [Domain::PostFileFingerprint] The fingerprint to compare with
|
||||
# @return [Integer, nil] The Hamming distance (number of differing bits) or nil if either fingerprint is invalid
|
||||
sig do
|
||||
params(
|
||||
other_fingerprint: T.nilable(Domain::PostFile::BitFingerprint),
|
||||
).returns(T.nilable(Integer))
|
||||
end
|
||||
def hamming_distance_to(other_fingerprint)
|
||||
return nil unless (this_hash = self.fingerprint_value)
|
||||
return nil unless (other_hash = other_fingerprint&.fingerprint_value)
|
||||
self.class.hamming_distance(this_hash, other_hash)
|
||||
end
|
||||
|
||||
# Calculate the Hamming distance between two hash values
|
||||
# @param hash_value1 [String] The first hash value
|
||||
# @param hash_value2 [String] The second hash value
|
||||
# @return [Integer, nil] The Hamming distance (number of differing bits) or nil if either hash value is invalid
|
||||
sig do
|
||||
params(hash_value1: String, hash_value2: String).returns(T.nilable(Integer))
|
||||
end
|
||||
def self.hamming_distance(hash_value1, hash_value2)
|
||||
hash_value1.chars.zip(hash_value2.chars).count { |c1, c2| c1 != c2 }
|
||||
end
|
||||
|
||||
# Calculate the similarity percentage between this fingerprint and another fingerprint
|
||||
# @param other_fingerprint [Domain::PostFileFingerprint] The fingerprint to compare with
|
||||
# @return [Float, nil] The similarity percentage between 0 and 100 or nil if either fingerprint is invalid
|
||||
sig do
|
||||
params(
|
||||
other_fingerprint: T.nilable(Domain::PostFile::BitFingerprint),
|
||||
).returns(T.nilable(Float))
|
||||
end
|
||||
def similarity_percentage_to(other_fingerprint)
|
||||
return nil unless (distance = hamming_distance_to(other_fingerprint))
|
||||
|
||||
# Maximum possible distance for a 256-bit hash
|
||||
max_distance = HASH_SIZE_BYTES * 8
|
||||
# Calculate similarity percentage based on distance
|
||||
result = ((max_distance - distance) / max_distance.to_f * 100).round(1)
|
||||
# Ensure the return type is Float
|
||||
Float(result)
|
||||
end
|
||||
|
||||
# Create a PostFileFingerprint instance from a file path
|
||||
# @param file_path [String] Path to the image file
|
||||
# @return [Domain::PostFileFingerprint] A non-persisted fingerprint model
|
||||
sig { params(file_path: String).returns(String) }
|
||||
def self.from_file_path(file_path)
|
||||
unless File.exist?(file_path)
|
||||
raise ArgumentError, "File does not exist: #{file_path}"
|
||||
end
|
||||
|
||||
fingerprint = DHashVips::IDHash.fingerprint(file_path)
|
||||
fingerprint.to_s(2).rjust(HASH_SIZE_BYTES * 8, "0")
|
||||
end
|
||||
end
|
||||
8
app/models/domain/post_file/fingerprint_join.rb
Normal file
8
app/models/domain/post_file/fingerprint_join.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# typed: strict
|
||||
class Domain::PostFile::FingerprintJoin < ReduxApplicationRecord
|
||||
self.table_name = "domain_post_file_fingerprint_joins"
|
||||
|
||||
belongs_to :thumbnail, class_name: "::Domain::PostFile::Thumbnail"
|
||||
belongs_to :post_file, class_name: "::Domain::PostFile"
|
||||
belongs_to :fingerprint, polymorphic: true
|
||||
end
|
||||
209
app/models/domain/post_file/thumbnail.rb
Normal file
209
app/models/domain/post_file/thumbnail.rb
Normal file
@@ -0,0 +1,209 @@
|
||||
# typed: strict
|
||||
class Domain::PostFile::Thumbnail < ReduxApplicationRecord
|
||||
extend T::Sig
|
||||
self.table_name = "domain_post_file_thumbnails"
|
||||
|
||||
belongs_to :post_file,
|
||||
class_name: "::Domain::PostFile",
|
||||
inverse_of: :thumbnails
|
||||
|
||||
enum :thumb_type, { content_container: 0, size_32_32: 1 }
|
||||
validates :thumb_type, presence: true
|
||||
validates :frame, presence: true
|
||||
validate :validate_content_type!
|
||||
|
||||
THUMB_ROOT_DIR =
|
||||
T.let(File.join(BlobFile::ROOT_DIR, "domain_post_file_thumbnails"), String)
|
||||
|
||||
THUMB_FILE_PATH_PATTERN = T.let([2, 2, 1].freeze, T::Array[Integer])
|
||||
|
||||
SINGLE_FRAME_CONTENT_TYPES =
|
||||
T.let(
|
||||
[%r{image/jpeg}, %r{image/jpg}, %r{image/png}, %r{image/bmp}],
|
||||
T::Array[Regexp],
|
||||
)
|
||||
|
||||
MULTI_FRAME_CONTENT_TYPES =
|
||||
T.let(
|
||||
[
|
||||
%r{image/gif},
|
||||
%r{image/apng},
|
||||
%r{image/webp},
|
||||
%r{video/webm},
|
||||
%r{video/mp4},
|
||||
],
|
||||
T::Array[Regexp],
|
||||
)
|
||||
|
||||
THUMBABLE_CONTENT_TYPES =
|
||||
T.let(
|
||||
SINGLE_FRAME_CONTENT_TYPES + MULTI_FRAME_CONTENT_TYPES,
|
||||
T::Array[Regexp],
|
||||
)
|
||||
|
||||
class ThumbnailOptions < T::ImmutableStruct
|
||||
extend T::Sig
|
||||
const :width, Integer
|
||||
const :height, T.nilable(Integer)
|
||||
# 0..100
|
||||
const :quality, T.nilable(Integer)
|
||||
# :both, :up, :down, :force
|
||||
const :size, Symbol
|
||||
const :interlace, T::Boolean
|
||||
const :for_frames, T::Array[Numeric]
|
||||
end
|
||||
|
||||
THUMB_TYPE_TO_OPTIONS =
|
||||
T.let(
|
||||
{
|
||||
content_container:
|
||||
ThumbnailOptions.new(
|
||||
width: 768,
|
||||
height: 2048,
|
||||
quality: 95,
|
||||
size: :down,
|
||||
interlace: true,
|
||||
for_frames: [0],
|
||||
),
|
||||
size_32_32:
|
||||
ThumbnailOptions.new(
|
||||
width: 32,
|
||||
height: 32,
|
||||
quality: 95,
|
||||
size: :force,
|
||||
interlace: false,
|
||||
for_frames: [0.0, 0.1, 0.5, 0.9, 1.0],
|
||||
),
|
||||
},
|
||||
T::Hash[Symbol, ThumbnailOptions],
|
||||
)
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def absolute_file_path
|
||||
return nil unless (thumb_type = self.thumb_type)
|
||||
return nil unless (post_file = self.post_file)
|
||||
return nil unless (sha256 = post_file.sha256)
|
||||
sha256_hex = HexUtil.bin2hex(sha256)
|
||||
[
|
||||
THUMB_ROOT_DIR,
|
||||
*BlobFile.path_segments(THUMB_FILE_PATH_PATTERN, sha256_hex),
|
||||
].join("/") + ".jpg"
|
||||
end
|
||||
|
||||
sig { params(post_file: Domain::PostFile).returns(T::Boolean) }
|
||||
def self.can_thumbnail_post_file?(post_file)
|
||||
return false unless content_type = post_file.content_type
|
||||
can_thumbnail_content_type?(content_type)
|
||||
end
|
||||
|
||||
sig { params(content_type: String).returns(T::Boolean) }
|
||||
def self.can_thumbnail_content_type?(content_type)
|
||||
THUMBABLE_CONTENT_TYPES.any? { |type| content_type.match?(type) }
|
||||
end
|
||||
|
||||
sig do
|
||||
params(post_file: Domain::PostFile).returns(
|
||||
T::Array[Domain::PostFile::Thumbnail],
|
||||
)
|
||||
end
|
||||
def self.create_for_post_file!(post_file)
|
||||
logger.tagged("thumbnail", make_arg_tag(post_file)) do
|
||||
return [] unless content_type = post_file.content_type
|
||||
return [] unless can_thumbnail_content_type?(content_type)
|
||||
blob_file_path = post_file.blob&.absolute_file_path
|
||||
return [] unless blob_file_path
|
||||
|
||||
vips_image = Vips::Image.new_from_file(blob_file_path)
|
||||
num_frames = frames_for_image(content_type, vips_image)
|
||||
logger.info(format_tags(make_tag("num_frames", num_frames)))
|
||||
return [] if num_frames.zero?
|
||||
|
||||
existing_thumb_types = post_file.thumbnails.to_a.map(&:thumb_type)
|
||||
logger.info(
|
||||
format_tags(make_tag("existing_thumb_types", existing_thumb_types)),
|
||||
)
|
||||
|
||||
FileUtils.mkdir_p(BlobFile::TMP_DIR)
|
||||
thumbnails = []
|
||||
THUMB_TYPE_TO_OPTIONS.each do |thumb_type, options|
|
||||
logger.tagged(make_tag("thumb_type", thumb_type)) do
|
||||
next if existing_thumb_types.include?(thumb_type)
|
||||
logger.info(format_tags("creating thumbnail"))
|
||||
|
||||
# get the number of frames in the post file
|
||||
frame_numbers =
|
||||
options
|
||||
.for_frames
|
||||
.map { |frame_fraction| (frame_fraction * (num_frames - 1)).to_i }
|
||||
.uniq
|
||||
.sort
|
||||
|
||||
frame_numbers.each do |frame|
|
||||
logger.tagged(make_tag("frame", frame)) do
|
||||
thumbnail = post_file.thumbnails.build(thumb_type:, frame:)
|
||||
unless thumb_file_path = thumbnail.absolute_file_path
|
||||
logger.info(format_tags("unable to compute thumbnail path"))
|
||||
next
|
||||
end
|
||||
|
||||
if File.exist?(thumb_file_path)
|
||||
logger.info(format_tags("thumbnail file already exists"))
|
||||
else
|
||||
FileUtils.mkdir_p(File.dirname(thumb_file_path))
|
||||
tmp_file_path =
|
||||
File.join(
|
||||
BlobFile::TMP_DIR,
|
||||
"thumb-#{thumb_type}-#{SecureRandom.uuid}",
|
||||
)
|
||||
logger.info(format_tags("writing thumbnail file"))
|
||||
# TODO - check that this works for gifs, mp4, etc
|
||||
vips_image.thumbnail_image(
|
||||
options.width,
|
||||
height: options.height,
|
||||
size: options.size,
|
||||
).jpegsave(
|
||||
tmp_file_path,
|
||||
interlace: options.interlace,
|
||||
Q: options.quality,
|
||||
)
|
||||
FileUtils.mv(tmp_file_path, thumb_file_path)
|
||||
end
|
||||
|
||||
thumbnail.save!
|
||||
thumbnails << thumbnail
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
thumbnails
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(content_type: String, vips_image: Vips::Image).returns(Integer) }
|
||||
def self.frames_for_image(content_type, vips_image)
|
||||
case content_type
|
||||
when %r{image/gif}
|
||||
vips_image.get("n-pages")
|
||||
when %r{video/webm}, %r{video/mp4}
|
||||
vips_image.get("n-frames")
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { void }
|
||||
def validate_content_type!
|
||||
return unless (post_file = self.post_file)
|
||||
content_type = post_file.content_type
|
||||
if content_type.nil?
|
||||
errors.add(:post_file, "must have a content type")
|
||||
return
|
||||
end
|
||||
|
||||
unless self.class.can_thumbnail_content_type?(content_type)
|
||||
errors.add(:post_file, "must be a thumbnailable content type")
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/models/domain/post_file/vector_fingerprint.rb
Normal file
13
app/models/domain/post_file/vector_fingerprint.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# typed: strict
|
||||
class Domain::PostFile::VectorFingerprint < ReduxApplicationRecord
|
||||
self.table_name = "domain_post_file_vector_fingerprints"
|
||||
|
||||
has_one :fingerprint_join,
|
||||
class_name: "Domain::PostFile::FingerprintJoin",
|
||||
foreign_key: :fingerprint_id,
|
||||
inverse_of: :fingerprint
|
||||
|
||||
has_one :post_file, through: :fingerprint_join
|
||||
|
||||
belongs_to :thumbnail, class_name: "Domain::PostFile::Thumbnail"
|
||||
end
|
||||
@@ -1,155 +0,0 @@
|
||||
# typed: strict
|
||||
class Domain::PostFileFingerprint < ReduxApplicationRecord
|
||||
include AttrJsonRecordAliases
|
||||
self.table_name = "domain_post_file_fingerprints"
|
||||
|
||||
belongs_to :post_file,
|
||||
foreign_key: :blob_sha256,
|
||||
primary_key: :blob_sha256,
|
||||
class_name: "::Domain::PostFile",
|
||||
inverse_of: :fingerprint
|
||||
|
||||
validates :hash_value, presence: true
|
||||
|
||||
# in bytes
|
||||
HASH_SIZE = 32
|
||||
|
||||
VALID_CONTENT_TYPES =
|
||||
T.let(
|
||||
[
|
||||
%r{image/jpeg},
|
||||
%r{image/jpg},
|
||||
%r{image/png},
|
||||
%r{image/gif},
|
||||
%r{image/webp},
|
||||
],
|
||||
T::Array[Regexp],
|
||||
)
|
||||
|
||||
before_validation do |fingerprint|
|
||||
next false if fingerprint.hash_value.present?
|
||||
next false unless (path = post_file&.blob&.absolute_file_path)
|
||||
next false unless File.exist?(path)
|
||||
next false unless blob = post_file&.blob
|
||||
next false unless content_type = blob.content_type
|
||||
unless VALID_CONTENT_TYPES.any? { |type| content_type.match?(type) }
|
||||
next false
|
||||
end
|
||||
|
||||
fingerprint = DHashVips::IDHash.fingerprint(path)
|
||||
self.hash_value = fingerprint.to_s(2).rjust(HASH_SIZE * 8, "0")
|
||||
end
|
||||
|
||||
# Find similar images based on the fingerprint
|
||||
sig { params(fingerprint: String).returns(ActiveRecord::Relation) }
|
||||
def self.similar_to_fingerprint(fingerprint)
|
||||
includes(post_file: :post).order(
|
||||
Arel.sql(
|
||||
"(hash_value <~> '#{ActiveRecord::Base.connection.quote_string(fingerprint)}') ASC",
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
# Calculate the Hamming distance between this fingerprint and another fingerprint
|
||||
# @param other_fingerprint [Domain::PostFileFingerprint] The fingerprint to compare with
|
||||
# @return [Integer, nil] The Hamming distance (number of differing bits) or nil if either fingerprint is invalid
|
||||
sig do
|
||||
params(other_fingerprint: T.nilable(Domain::PostFileFingerprint)).returns(
|
||||
T.nilable(Integer),
|
||||
)
|
||||
end
|
||||
def hamming_distance_to(other_fingerprint)
|
||||
this_hash = hash_value
|
||||
other_hash = other_fingerprint&.hash_value
|
||||
return nil if this_hash.blank? || other_hash.blank?
|
||||
self.class.hamming_distance(this_hash, other_hash)
|
||||
end
|
||||
|
||||
# Calculate the Hamming distance between two hash values
|
||||
# @param hash_value1 [String] The first hash value
|
||||
# @param hash_value2 [String] The second hash value
|
||||
# @return [Integer, nil] The Hamming distance (number of differing bits) or nil if either hash value is invalid
|
||||
sig do
|
||||
params(hash_value1: String, hash_value2: String).returns(T.nilable(Integer))
|
||||
end
|
||||
def self.hamming_distance(hash_value1, hash_value2)
|
||||
hash_value1.chars.zip(hash_value2.chars).count { |c1, c2| c1 != c2 }
|
||||
end
|
||||
|
||||
# Calculate the similarity percentage between this fingerprint and another fingerprint
|
||||
# @param other_fingerprint [Domain::PostFileFingerprint] The fingerprint to compare with
|
||||
# @return [Float, nil] The similarity percentage between 0 and 100 or nil if either fingerprint is invalid
|
||||
sig do
|
||||
params(other_fingerprint: T.nilable(Domain::PostFileFingerprint)).returns(
|
||||
T.nilable(Float),
|
||||
)
|
||||
end
|
||||
def similarity_percentage_to(other_fingerprint)
|
||||
distance = hamming_distance_to(other_fingerprint)
|
||||
return nil unless distance
|
||||
|
||||
# Maximum possible distance for a 256-bit hash
|
||||
max_distance = HASH_SIZE * 8
|
||||
# Calculate similarity percentage based on distance
|
||||
result = ((max_distance - distance) / max_distance.to_f * 100).round(1)
|
||||
# Ensure the return type is Float
|
||||
Float(result)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(post_file: Domain::PostFile).returns(
|
||||
T.nilable(Domain::PostFileFingerprint),
|
||||
)
|
||||
end
|
||||
def self.from_post_file(post_file)
|
||||
blob_file_path = post_file.blob&.absolute_file_path
|
||||
content_type = post_file.blob&.content_type
|
||||
return nil unless blob_file_path
|
||||
return nil unless content_type
|
||||
unless VALID_CONTENT_TYPES.any? { |type| content_type.match?(type) }
|
||||
return nil
|
||||
end
|
||||
model = from_file_path(blob_file_path)
|
||||
model.post_file = post_file
|
||||
model
|
||||
end
|
||||
|
||||
# Create a PostFileFingerprint instance from a file path
|
||||
# @param file_path [String] Path to the image file
|
||||
# @return [Domain::PostFileFingerprint] A non-persisted fingerprint model
|
||||
sig { params(file_path: String).returns(Domain::PostFileFingerprint) }
|
||||
def self.from_file_path(file_path)
|
||||
unless File.exist?(file_path)
|
||||
raise ArgumentError, "File does not exist: #{file_path}"
|
||||
end
|
||||
|
||||
fingerprint = DHashVips::IDHash.fingerprint(file_path)
|
||||
from_dhash_fingerprint(fingerprint)
|
||||
end
|
||||
|
||||
# Create a PostFileFingerprint instance from a Vips::Image
|
||||
# @param vips_image [Vips::Image] Vips image object
|
||||
# @return [Domain::PostFileFingerprint] A non-persisted fingerprint model
|
||||
sig { params(vips_image: T.untyped).returns(Domain::PostFileFingerprint) }
|
||||
def self.from_vips_image(vips_image)
|
||||
# Generate fingerprint directly from the Vips::Image object
|
||||
fingerprint = DHashVips::IDHash.fingerprint(vips_image)
|
||||
from_dhash_fingerprint(fingerprint)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Create a PostFileFingerprint instance from a DHashVips fingerprint
|
||||
# @param fingerprint [Object] DHashVips fingerprint object
|
||||
# @return [Domain::PostFileFingerprint] A non-persisted fingerprint model
|
||||
sig { params(fingerprint: T.untyped).returns(Domain::PostFileFingerprint) }
|
||||
def self.from_dhash_fingerprint(fingerprint)
|
||||
# Convert the numeric fingerprint to a binary string and pad to the correct length
|
||||
# HASH_SIZE = 32 (bytes) * 8 = 256 bits
|
||||
hash_value = fingerprint.to_s(2).rjust(HASH_SIZE * 8, "0")
|
||||
|
||||
new_fingerprint = new
|
||||
new_fingerprint.hash_value = hash_value
|
||||
new_fingerprint
|
||||
end
|
||||
end
|
||||
@@ -21,7 +21,7 @@ class Domain::User::FaUser < Domain::User
|
||||
attr_json_due_timestamp :scanned_page_at, 3.months
|
||||
attr_json_due_timestamp :scanned_follows_at, 3.months
|
||||
attr_json_due_timestamp :scanned_followed_by_at, 3.months
|
||||
attr_json_due_timestamp :scanned_favs_at, 1.month
|
||||
attr_json_due_timestamp :scanned_favs_at, 3.months
|
||||
attr_json_due_timestamp :scanned_incremental_at, 1.month
|
||||
attr_json :registered_at, :datetime
|
||||
attr_json :migrated_followed_users_at, :datetime
|
||||
|
||||
@@ -45,11 +45,21 @@ class ApplicationPolicy
|
||||
user&.moderator? || false
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def is_role_at_least_moderator?
|
||||
is_role_moderator? || is_role_admin?
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def is_role_user?
|
||||
user&.user? || false
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def is_role_at_least_user?
|
||||
is_role_user? || is_role_moderator? || is_role_admin?
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def is_real_user?
|
||||
user&.is_a?(User) || false
|
||||
|
||||
@@ -14,17 +14,17 @@ class Domain::PostPolicy < ApplicationPolicy
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def visual_search?
|
||||
is_role_user?
|
||||
is_role_at_least_user?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def visual_results?
|
||||
is_role_user?
|
||||
is_role_at_least_user?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def view_file?
|
||||
is_role_admin? || is_role_moderator? || is_role_user?
|
||||
is_role_at_least_user?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
@@ -34,12 +34,12 @@ class Domain::PostPolicy < ApplicationPolicy
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def users_faving_post?
|
||||
is_role_admin? || is_role_moderator? || is_role_user?
|
||||
is_role_at_least_user?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def view_faved_by?
|
||||
is_role_admin? || is_role_moderator? || is_role_user?
|
||||
is_role_at_least_user?
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
class CreateDomainPostFileThumbnails < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :domain_post_file_fingerprints do |t|
|
||||
t.binary :blob_sha256, null: false, index: true
|
||||
t.bit :hash_value, limit: 256
|
||||
t.timestamps
|
||||
t.index :hash_value, using: :hnsw, opclass: :bit_hamming_ops
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class AddIndexOnPostFileBlobSha256 < ActiveRecord::Migration[7.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
up_only { execute "SET DEFAULT_TABLESPACE = mirai;" }
|
||||
add_index :domain_post_files, :blob_sha256, algorithm: :concurrently
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
class CreatePostFileThumbnailsFingerprints < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :domain_post_file_thumbnails do |t|
|
||||
t.references :post_file, null: false, index: false
|
||||
t.integer :thumb_type, null: false
|
||||
t.integer :frame, null: false, default: 0
|
||||
t.timestamps
|
||||
|
||||
t.index %i[post_file_id thumb_type frame], unique: true
|
||||
end
|
||||
|
||||
create_table :domain_post_file_bit_fingerprints do |t|
|
||||
t.references :post_file, null: false, index: false
|
||||
t.references :thumbnail, null: false, index: false
|
||||
t.bit :fingerprint_value, limit: 256
|
||||
t.timestamps
|
||||
|
||||
t.index %i[post_file_id thumbnail_id], unique: true
|
||||
t.index :fingerprint_value, using: :hnsw, opclass: :bit_hamming_ops
|
||||
end
|
||||
|
||||
# create_table :domain_post_file_vector_fingerprints do |t|
|
||||
# t.integer :type, null: false
|
||||
# t.references :thumbnail, null: false, index: true
|
||||
# t.vector :fingerprint_value, limit: 256
|
||||
# t.timestamps
|
||||
# t.index :fingerprint_value, using: :hnsw, opclass: :vector_cosine_ops
|
||||
# end
|
||||
end
|
||||
end
|
||||
110
db/structure.sql
110
db/structure.sql
@@ -2697,23 +2697,24 @@ ALTER SEQUENCE public.domain_inkbunny_users_id_seq OWNED BY public.domain_inkbun
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_fingerprints; Type: TABLE; Schema: public; Owner: -
|
||||
-- Name: domain_post_file_bit_fingerprints; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.domain_post_file_fingerprints (
|
||||
CREATE TABLE public.domain_post_file_bit_fingerprints (
|
||||
id bigint NOT NULL,
|
||||
blob_sha256 bytea NOT NULL,
|
||||
hash_value bit(256),
|
||||
post_file_id bigint NOT NULL,
|
||||
thumbnail_id bigint NOT NULL,
|
||||
fingerprint_value bit(256),
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_fingerprints_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
-- Name: domain_post_file_bit_fingerprints_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.domain_post_file_fingerprints_id_seq
|
||||
CREATE SEQUENCE public.domain_post_file_bit_fingerprints_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
@@ -2722,10 +2723,43 @@ CREATE SEQUENCE public.domain_post_file_fingerprints_id_seq
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_fingerprints_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
-- Name: domain_post_file_bit_fingerprints_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.domain_post_file_fingerprints_id_seq OWNED BY public.domain_post_file_fingerprints.id;
|
||||
ALTER SEQUENCE public.domain_post_file_bit_fingerprints_id_seq OWNED BY public.domain_post_file_bit_fingerprints.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_thumbnails; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.domain_post_file_thumbnails (
|
||||
id bigint NOT NULL,
|
||||
post_file_id bigint NOT NULL,
|
||||
thumb_type integer NOT NULL,
|
||||
frame integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_thumbnails_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.domain_post_file_thumbnails_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_thumbnails_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.domain_post_file_thumbnails_id_seq OWNED BY public.domain_post_file_thumbnails.id;
|
||||
|
||||
|
||||
SET default_tablespace = mirai;
|
||||
@@ -4621,10 +4655,17 @@ ALTER TABLE ONLY public.domain_inkbunny_users ALTER COLUMN id SET DEFAULT nextva
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_fingerprints id; Type: DEFAULT; Schema: public; Owner: -
|
||||
-- Name: domain_post_file_bit_fingerprints id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_file_fingerprints ALTER COLUMN id SET DEFAULT nextval('public.domain_post_file_fingerprints_id_seq'::regclass);
|
||||
ALTER TABLE ONLY public.domain_post_file_bit_fingerprints ALTER COLUMN id SET DEFAULT nextval('public.domain_post_file_bit_fingerprints_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_thumbnails id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_file_thumbnails ALTER COLUMN id SET DEFAULT nextval('public.domain_post_file_thumbnails_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
@@ -5410,11 +5451,19 @@ ALTER TABLE ONLY public.domain_inkbunny_users
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_fingerprints domain_post_file_fingerprints_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
-- Name: domain_post_file_bit_fingerprints domain_post_file_bit_fingerprints_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_file_fingerprints
|
||||
ADD CONSTRAINT domain_post_file_fingerprints_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE ONLY public.domain_post_file_bit_fingerprints
|
||||
ADD CONSTRAINT domain_post_file_bit_fingerprints_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_thumbnails domain_post_file_thumbnails_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.domain_post_file_thumbnails
|
||||
ADD CONSTRAINT domain_post_file_thumbnails_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
SET default_tablespace = mirai;
|
||||
@@ -5748,6 +5797,20 @@ SET default_tablespace = '';
|
||||
CREATE UNIQUE INDEX idx_on_good_job_execution_id_685ddb5560 ON public.good_job_execution_log_lines_collections USING btree (good_job_execution_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_on_post_file_id_thumb_type_frame_17152086d1; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX idx_on_post_file_id_thumb_type_frame_17152086d1 ON public.domain_post_file_thumbnails USING btree (post_file_id, thumb_type, frame);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_on_post_file_id_thumbnail_id_28e9a641fb; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX idx_on_post_file_id_thumbnail_id_28e9a641fb ON public.domain_post_file_bit_fingerprints USING btree (post_file_id, thumbnail_id);
|
||||
|
||||
|
||||
SET default_tablespace = mirai;
|
||||
|
||||
--
|
||||
@@ -7019,21 +7082,21 @@ CREATE INDEX index_domain_inkbunny_users_on_shallow_update_log_entry_id ON publi
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_file_fingerprints_on_blob_sha256; Type: INDEX; Schema: public; Owner: -
|
||||
-- Name: index_domain_post_file_bit_fingerprints_on_fingerprint_value; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_post_file_fingerprints_on_blob_sha256 ON public.domain_post_file_fingerprints USING btree (blob_sha256);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_file_fingerprints_on_hash_value; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_post_file_fingerprints_on_hash_value ON public.domain_post_file_fingerprints USING hnsw (hash_value public.bit_hamming_ops);
|
||||
CREATE INDEX index_domain_post_file_bit_fingerprints_on_fingerprint_value ON public.domain_post_file_bit_fingerprints USING hnsw (fingerprint_value public.bit_hamming_ops);
|
||||
|
||||
|
||||
SET default_tablespace = mirai;
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_files_on_blob_sha256; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_post_files_on_blob_sha256 ON public.domain_post_files USING btree (blob_sha256);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_files_on_log_entry_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
@@ -8846,8 +8909,9 @@ ALTER TABLE ONLY public.domain_twitter_tweets
|
||||
SET search_path TO "$user", public;
|
||||
|
||||
INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20250306021628'),
|
||||
('20250305195421'),
|
||||
('20250302074924'),
|
||||
('20250301000001'),
|
||||
('20250226003653'),
|
||||
('20250222035939'),
|
||||
('20250206224121'),
|
||||
|
||||
7
media-thumbnailer/Cargo.lock
generated
7
media-thumbnailer/Cargo.lock
generated
@@ -1,7 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "media-thumbnailer"
|
||||
version = "0.1.0"
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "media-thumbnailer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"rustc_fingerprint":2276600985026381000,"outputs":{"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/vscode/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""},"2063776225603076451":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/vscode/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
|
||||
@@ -1,3 +0,0 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
||||
@@ -1 +0,0 @@
|
||||
db843a6414af8582
|
||||
@@ -1 +0,0 @@
|
||||
{"rustc":8277423686421874925,"features":"[]","declared_features":"[]","target":9596570253498806976,"profile":17672942494452627365,"path":4942398508502643691,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/media-thumbnailer-5574003c59783d36/dep-bin-media-thumbnailer","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -1 +0,0 @@
|
||||
a43eef7362df3a3f
|
||||
@@ -1 +0,0 @@
|
||||
{"rustc":8277423686421874925,"features":"[]","declared_features":"[]","target":9596570253498806976,"profile":3316208278650011218,"path":4942398508502643691,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/media-thumbnailer-706bac28c87bc41d/dep-test-bin-media-thumbnailer","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
@@ -1,5 +0,0 @@
|
||||
/workspaces/redux-scraper/media-thumbnailer/target/debug/deps/libmedia_thumbnailer-5574003c59783d36.rmeta: src/main.rs
|
||||
|
||||
/workspaces/redux-scraper/media-thumbnailer/target/debug/deps/media_thumbnailer-5574003c59783d36.d: src/main.rs
|
||||
|
||||
src/main.rs:
|
||||
@@ -1,5 +0,0 @@
|
||||
/workspaces/redux-scraper/media-thumbnailer/target/debug/deps/libmedia_thumbnailer-706bac28c87bc41d.rmeta: src/main.rs
|
||||
|
||||
/workspaces/redux-scraper/media-thumbnailer/target/debug/deps/media_thumbnailer-706bac28c87bc41d.d: src/main.rs
|
||||
|
||||
src/main.rs:
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -99,14 +99,15 @@ namespace :fa do
|
||||
start_at =
|
||||
ENV["start_at"]&.to_i ||
|
||||
raise("need start_at (highest fa_id already present)")
|
||||
stop_at = ENV["stop_at"]&.to_i
|
||||
low_water_mark = 50
|
||||
high_water_mark = 300
|
||||
poll_duration = 10
|
||||
|
||||
enqueuer =
|
||||
Domain::Fa::PostEnqueuer.new(
|
||||
reverse_scan_holes: true,
|
||||
start_at: start_at,
|
||||
stop_at: stop_at,
|
||||
low_water_mark: low_water_mark,
|
||||
high_water_mark: high_water_mark,
|
||||
)
|
||||
|
||||
3
sorbet/rbi/dsl/blob_file.rbi
generated
3
sorbet/rbi/dsl/blob_file.rbi
generated
@@ -6,6 +6,7 @@
|
||||
|
||||
|
||||
class BlobFile
|
||||
include GeneratedAssociationMethods
|
||||
include GeneratedAttributeMethods
|
||||
include EnumMethodsModule
|
||||
extend CommonRelationMethods
|
||||
@@ -345,6 +346,8 @@ class BlobFile
|
||||
def v1?; end
|
||||
end
|
||||
|
||||
module GeneratedAssociationMethods; end
|
||||
|
||||
module GeneratedAssociationRelationMethods
|
||||
sig { returns(PrivateAssociationRelation) }
|
||||
def all; end
|
||||
|
||||
49
sorbet/rbi/dsl/domain/post_file.rbi
generated
49
sorbet/rbi/dsl/domain/post_file.rbi
generated
@@ -453,6 +453,20 @@ class Domain::PostFile
|
||||
end
|
||||
|
||||
module GeneratedAssociationMethods
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
def bit_fingerprint_ids; end
|
||||
|
||||
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
||||
def bit_fingerprint_ids=(ids); end
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::PostFile` class because it declared `has_many :bit_fingerprints`.
|
||||
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
|
||||
sig { returns(::Domain::PostFile::BitFingerprint::PrivateCollectionProxy) }
|
||||
def bit_fingerprints; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::PostFile::BitFingerprint]).void }
|
||||
def bit_fingerprints=(value); end
|
||||
|
||||
sig { returns(T.nilable(::BlobFile)) }
|
||||
def blob; end
|
||||
|
||||
@@ -468,9 +482,6 @@ class Domain::PostFile
|
||||
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(::Domain::PostFileFingerprint) }
|
||||
def build_fingerprint(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def build_log_entry(*args, &blk); end
|
||||
|
||||
@@ -483,12 +494,6 @@ class Domain::PostFile
|
||||
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(::Domain::PostFileFingerprint) }
|
||||
def create_fingerprint(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFileFingerprint) }
|
||||
def create_fingerprint!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_log_entry(*args, &blk); end
|
||||
|
||||
@@ -501,12 +506,6 @@ class Domain::PostFile
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Post) }
|
||||
def create_post!(*args, &blk); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
def fingerprint; end
|
||||
|
||||
sig { params(value: T.nilable(::Domain::PostFileFingerprint)).void }
|
||||
def fingerprint=(value); end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def log_entry; end
|
||||
|
||||
@@ -534,9 +533,6 @@ class Domain::PostFile
|
||||
sig { returns(T.nilable(::BlobFile)) }
|
||||
def reload_blob; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
def reload_fingerprint; end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def reload_log_entry; end
|
||||
|
||||
@@ -546,14 +542,25 @@ class Domain::PostFile
|
||||
sig { void }
|
||||
def reset_blob; end
|
||||
|
||||
sig { void }
|
||||
def reset_fingerprint; end
|
||||
|
||||
sig { void }
|
||||
def reset_log_entry; end
|
||||
|
||||
sig { void }
|
||||
def reset_post; end
|
||||
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
def thumbnail_ids; end
|
||||
|
||||
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
||||
def thumbnail_ids=(ids); end
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::PostFile` class because it declared `has_many :thumbnails`.
|
||||
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
|
||||
sig { returns(::Domain::PostFile::Thumbnail::PrivateCollectionProxy) }
|
||||
def thumbnails; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::PostFile::Thumbnail]).void }
|
||||
def thumbnails=(value); end
|
||||
end
|
||||
|
||||
module GeneratedAssociationRelationMethods
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `Domain::PostFileFingerprint`.
|
||||
# Please instead update this file by running `bin/tapioca dsl Domain::PostFileFingerprint`.
|
||||
# This is an autogenerated file for dynamic methods in `Domain::PostFile::BitFingerprint`.
|
||||
# Please instead update this file by running `bin/tapioca dsl Domain::PostFile::BitFingerprint`.
|
||||
|
||||
|
||||
class Domain::PostFileFingerprint
|
||||
class Domain::PostFile::BitFingerprint
|
||||
include GeneratedAssociationMethods
|
||||
include GeneratedAttributeMethods
|
||||
extend CommonRelationMethods
|
||||
@@ -47,8 +47,8 @@ class Domain::PostFileFingerprint
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def new(attributes = nil, &block); end
|
||||
end
|
||||
@@ -56,7 +56,7 @@ class Domain::PostFileFingerprint
|
||||
module CommonRelationMethods
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFileFingerprint).returns(T.untyped))
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFile::BitFingerprint).returns(T.untyped))
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def any?(&block); end
|
||||
@@ -66,20 +66,20 @@ class Domain::PostFileFingerprint
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def build(attributes = nil, &block); end
|
||||
|
||||
@@ -90,111 +90,111 @@ class Domain::PostFileFingerprint
|
||||
sig do
|
||||
params(
|
||||
column_name: NilClass,
|
||||
block: T.proc.params(object: ::Domain::PostFileFingerprint).void
|
||||
block: T.proc.params(object: ::Domain::PostFile::BitFingerprint).void
|
||||
).returns(Integer)
|
||||
end
|
||||
def count(column_name = nil, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def create(attributes = nil, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def create!(attributes = nil, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def create_or_find_by(attributes, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def create_or_find_by!(attributes, &block); end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def destroy_all; end
|
||||
|
||||
sig { params(conditions: T.untyped).returns(T::Boolean) }
|
||||
def exists?(conditions = :none); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def fifth; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def fifth!; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
args: T.any(String, Symbol, ::ActiveSupport::Multibyte::Chars, T::Boolean, BigDecimal, Numeric, ::ActiveRecord::Type::Binary::Data, ::ActiveRecord::Type::Time::Value, Date, Time, ::ActiveSupport::Duration, T::Class[T.anything])
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
args: T::Array[T.any(String, Symbol, ::ActiveSupport::Multibyte::Chars, T::Boolean, BigDecimal, Numeric, ::ActiveRecord::Type::Binary::Data, ::ActiveRecord::Type::Time::Value, Date, Time, ::ActiveSupport::Duration, T::Class[T.anything])]
|
||||
).returns(T::Enumerable[::Domain::PostFileFingerprint])
|
||||
).returns(T::Enumerable[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
args: NilClass,
|
||||
block: T.proc.params(object: ::Domain::PostFileFingerprint).void
|
||||
).returns(T.nilable(::Domain::PostFileFingerprint))
|
||||
block: T.proc.params(object: ::Domain::PostFile::BitFingerprint).void
|
||||
).returns(T.nilable(::Domain::PostFile::BitFingerprint))
|
||||
end
|
||||
def find(args = nil, &block); end
|
||||
|
||||
sig { params(args: T.untyped).returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { params(args: T.untyped).returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def find_by(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(::Domain::PostFileFingerprint) }
|
||||
sig { params(args: T.untyped).returns(::Domain::PostFile::BitFingerprint) }
|
||||
def find_by!(*args); end
|
||||
|
||||
sig do
|
||||
@@ -204,7 +204,7 @@ class Domain::PostFileFingerprint
|
||||
batch_size: Integer,
|
||||
error_on_ignore: T.untyped,
|
||||
order: Symbol,
|
||||
block: T.proc.params(object: ::Domain::PostFileFingerprint).void
|
||||
block: T.proc.params(object: ::Domain::PostFile::BitFingerprint).void
|
||||
).void
|
||||
end
|
||||
sig do
|
||||
@@ -214,7 +214,7 @@ class Domain::PostFileFingerprint
|
||||
batch_size: Integer,
|
||||
error_on_ignore: T.untyped,
|
||||
order: Symbol
|
||||
).returns(T::Enumerator[::Domain::PostFileFingerprint])
|
||||
).returns(T::Enumerator[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc, &block); end
|
||||
|
||||
@@ -225,7 +225,7 @@ class Domain::PostFileFingerprint
|
||||
batch_size: Integer,
|
||||
error_on_ignore: T.untyped,
|
||||
order: Symbol,
|
||||
block: T.proc.params(object: T::Array[::Domain::PostFileFingerprint]).void
|
||||
block: T.proc.params(object: T::Array[::Domain::PostFile::BitFingerprint]).void
|
||||
).void
|
||||
end
|
||||
sig do
|
||||
@@ -235,78 +235,78 @@ class Domain::PostFileFingerprint
|
||||
batch_size: Integer,
|
||||
error_on_ignore: T.untyped,
|
||||
order: Symbol
|
||||
).returns(T::Enumerator[T::Enumerator[::Domain::PostFileFingerprint]])
|
||||
).returns(T::Enumerator[T::Enumerator[::Domain::PostFile::BitFingerprint]])
|
||||
end
|
||||
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def find_or_create_by(attributes, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def find_or_create_by!(attributes, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def find_or_initialize_by(attributes, &block); end
|
||||
|
||||
sig { params(signed_id: T.untyped, purpose: T.untyped).returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { params(signed_id: T.untyped, purpose: T.untyped).returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def find_signed(signed_id, purpose: nil); end
|
||||
|
||||
sig { params(signed_id: T.untyped, purpose: T.untyped).returns(::Domain::PostFileFingerprint) }
|
||||
sig { params(signed_id: T.untyped, purpose: T.untyped).returns(::Domain::PostFile::BitFingerprint) }
|
||||
def find_signed!(signed_id, purpose: nil); end
|
||||
|
||||
sig { params(arg: T.untyped, args: T.untyped).returns(::Domain::PostFileFingerprint) }
|
||||
sig { params(arg: T.untyped, args: T.untyped).returns(::Domain::PostFile::BitFingerprint) }
|
||||
def find_sole_by(arg, *args); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { params(limit: Integer).returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
sig { params(limit: Integer).returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def first(limit = nil); end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def first!; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def forty_two; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def forty_two!; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def fourth; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def fourth!; end
|
||||
|
||||
sig { returns(Array) }
|
||||
@@ -340,16 +340,16 @@ class Domain::PostFileFingerprint
|
||||
sig { params(record: T.untyped).returns(T::Boolean) }
|
||||
def include?(record); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { params(limit: Integer).returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
sig { params(limit: Integer).returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def last(limit = nil); end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def last!; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFileFingerprint).returns(T.untyped))
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFile::BitFingerprint).returns(T.untyped))
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def many?(&block); end
|
||||
@@ -365,33 +365,33 @@ class Domain::PostFileFingerprint
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T::Array[T.untyped],
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
sig do
|
||||
params(
|
||||
attributes: T.untyped,
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFileFingerprint).void)
|
||||
).returns(::Domain::PostFileFingerprint)
|
||||
block: T.nilable(T.proc.params(object: ::Domain::PostFile::BitFingerprint).void)
|
||||
).returns(::Domain::PostFile::BitFingerprint)
|
||||
end
|
||||
def new(attributes = nil, &block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFileFingerprint).returns(T.untyped))
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFile::BitFingerprint).returns(T.untyped))
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def none?(&block); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFileFingerprint).returns(T.untyped))
|
||||
block: T.nilable(T.proc.params(record: ::Domain::PostFile::BitFingerprint).returns(T.untyped))
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def one?(&block); end
|
||||
@@ -402,19 +402,19 @@ class Domain::PostFileFingerprint
|
||||
sig { params(column_names: T.untyped).returns(T.untyped) }
|
||||
def pluck(*column_names); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def second; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def second!; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def second_to_last; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def second_to_last!; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def sole; end
|
||||
|
||||
sig { params(initial_value_or_column: T.untyped).returns(T.any(Integer, Float, BigDecimal)) }
|
||||
@@ -422,28 +422,28 @@ class Domain::PostFileFingerprint
|
||||
type_parameters(:U)
|
||||
.params(
|
||||
initial_value_or_column: T.nilable(T.type_parameter(:U)),
|
||||
block: T.proc.params(object: ::Domain::PostFileFingerprint).returns(T.type_parameter(:U))
|
||||
block: T.proc.params(object: ::Domain::PostFile::BitFingerprint).returns(T.type_parameter(:U))
|
||||
).returns(T.type_parameter(:U))
|
||||
end
|
||||
def sum(initial_value_or_column = nil, &block); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { params(limit: Integer).returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
sig { params(limit: Integer).returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def take(limit = nil); end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def take!; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def third; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def third!; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
sig { returns(T.nilable(::Domain::PostFile::BitFingerprint)) }
|
||||
def third_to_last; end
|
||||
|
||||
sig { returns(::Domain::PostFileFingerprint) }
|
||||
sig { returns(::Domain::PostFile::BitFingerprint) }
|
||||
def third_to_last!; end
|
||||
end
|
||||
|
||||
@@ -451,12 +451,21 @@ class Domain::PostFileFingerprint
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile) }
|
||||
def build_post_file(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile::Thumbnail) }
|
||||
def build_thumbnail(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile) }
|
||||
def create_post_file(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile) }
|
||||
def create_post_file!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile::Thumbnail) }
|
||||
def create_thumbnail(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile::Thumbnail) }
|
||||
def create_thumbnail!(*args, &blk); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFile)) }
|
||||
def post_file; end
|
||||
|
||||
@@ -472,8 +481,26 @@ class Domain::PostFileFingerprint
|
||||
sig { returns(T.nilable(::Domain::PostFile)) }
|
||||
def reload_post_file; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFile::Thumbnail)) }
|
||||
def reload_thumbnail; end
|
||||
|
||||
sig { void }
|
||||
def reset_post_file; end
|
||||
|
||||
sig { void }
|
||||
def reset_thumbnail; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFile::Thumbnail)) }
|
||||
def thumbnail; end
|
||||
|
||||
sig { params(value: T.nilable(::Domain::PostFile::Thumbnail)).void }
|
||||
def thumbnail=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def thumbnail_changed?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def thumbnail_previously_changed?; end
|
||||
end
|
||||
|
||||
module GeneratedAssociationRelationMethods
|
||||
@@ -640,51 +667,6 @@ class Domain::PostFileFingerprint
|
||||
end
|
||||
|
||||
module GeneratedAttributeMethods
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256; end
|
||||
|
||||
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
|
||||
def blob_sha256=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def blob_sha256?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def blob_sha256_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def blob_sha256_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def blob_sha256_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def blob_sha256_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def blob_sha256_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def blob_sha256_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def blob_sha256_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_was; end
|
||||
|
||||
sig { void }
|
||||
def blob_sha256_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def created_at; end
|
||||
|
||||
@@ -741,49 +723,49 @@ class Domain::PostFileFingerprint
|
||||
def created_at_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def hash_value; end
|
||||
def fingerprint_value; end
|
||||
|
||||
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
|
||||
def hash_value=(value); end
|
||||
def fingerprint_value=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def hash_value?; end
|
||||
def fingerprint_value?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def hash_value_before_last_save; end
|
||||
def fingerprint_value_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def hash_value_before_type_cast; end
|
||||
def fingerprint_value_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def hash_value_came_from_user?; end
|
||||
def fingerprint_value_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def hash_value_change; end
|
||||
def fingerprint_value_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def hash_value_change_to_be_saved; end
|
||||
def fingerprint_value_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def hash_value_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
def fingerprint_value_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def hash_value_in_database; end
|
||||
def fingerprint_value_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def hash_value_previous_change; end
|
||||
def fingerprint_value_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def hash_value_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
def fingerprint_value_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def hash_value_previously_was; end
|
||||
def fingerprint_value_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def hash_value_was; end
|
||||
def fingerprint_value_was; end
|
||||
|
||||
sig { void }
|
||||
def hash_value_will_change!; end
|
||||
def fingerprint_value_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def id; end
|
||||
@@ -875,14 +857,56 @@ class Domain::PostFileFingerprint
|
||||
sig { void }
|
||||
def id_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def post_file_id; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def post_file_id=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def post_file_id?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def post_file_id_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def post_file_id_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def post_file_id_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def post_file_id_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def post_file_id_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def post_file_id_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def post_file_id_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def post_file_id_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def post_file_id_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def post_file_id_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def post_file_id_was; end
|
||||
|
||||
sig { void }
|
||||
def restore_blob_sha256!; end
|
||||
def post_file_id_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_created_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_hash_value!; end
|
||||
def restore_fingerprint_value!; end
|
||||
|
||||
sig { void }
|
||||
def restore_id!; end
|
||||
@@ -890,15 +914,15 @@ class Domain::PostFileFingerprint
|
||||
sig { void }
|
||||
def restore_id_value!; end
|
||||
|
||||
sig { void }
|
||||
def restore_post_file_id!; end
|
||||
|
||||
sig { void }
|
||||
def restore_thumbnail_id!; end
|
||||
|
||||
sig { void }
|
||||
def restore_updated_at!; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_blob_sha256; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_blob_sha256?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_created_at; end
|
||||
|
||||
@@ -906,10 +930,10 @@ class Domain::PostFileFingerprint
|
||||
def saved_change_to_created_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_hash_value; end
|
||||
def saved_change_to_fingerprint_value; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_hash_value?; end
|
||||
def saved_change_to_fingerprint_value?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_id; end
|
||||
@@ -923,12 +947,69 @@ class Domain::PostFileFingerprint
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_id_value?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_post_file_id; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_post_file_id?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_thumbnail_id; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_thumbnail_id?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_updated_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_updated_at?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def thumbnail_id; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def thumbnail_id=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def thumbnail_id?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def thumbnail_id_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def thumbnail_id_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def thumbnail_id_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def thumbnail_id_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def thumbnail_id_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def thumbnail_id_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def thumbnail_id_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def thumbnail_id_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def thumbnail_id_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def thumbnail_id_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def thumbnail_id_was; end
|
||||
|
||||
sig { void }
|
||||
def thumbnail_id_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def updated_at; end
|
||||
|
||||
@@ -984,14 +1065,11 @@ class Domain::PostFileFingerprint
|
||||
sig { void }
|
||||
def updated_at_will_change!; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_blob_sha256?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_created_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_hash_value?; end
|
||||
def will_save_change_to_fingerprint_value?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_id?; end
|
||||
@@ -999,6 +1077,12 @@ class Domain::PostFileFingerprint
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_id_value?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_post_file_id?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_thumbnail_id?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_updated_at?; end
|
||||
end
|
||||
@@ -1168,17 +1252,17 @@ class Domain::PostFileFingerprint
|
||||
include CommonRelationMethods
|
||||
include GeneratedAssociationRelationMethods
|
||||
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def to_a; end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def to_ary; end
|
||||
end
|
||||
|
||||
class PrivateAssociationRelationGroupChain < PrivateAssociationRelation
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig { params(column_name: T.any(String, Symbol)).returns(T::Hash[T.untyped, T.any(Integer, Float, BigDecimal)]) }
|
||||
def average(column_name); end
|
||||
@@ -1213,7 +1297,7 @@ class Domain::PostFileFingerprint
|
||||
end
|
||||
|
||||
class PrivateAssociationRelationWhereChain
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig { params(args: T.untyped).returns(PrivateAssociationRelation) }
|
||||
def associated(*args); end
|
||||
@@ -1229,18 +1313,18 @@ class Domain::PostFileFingerprint
|
||||
include CommonRelationMethods
|
||||
include GeneratedAssociationRelationMethods
|
||||
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig do
|
||||
params(
|
||||
records: T.any(::Domain::PostFileFingerprint, T::Enumerable[T.any(::Domain::PostFileFingerprint, T::Enumerable[::Domain::PostFileFingerprint])])
|
||||
records: T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[::Domain::PostFile::BitFingerprint])])
|
||||
).returns(PrivateCollectionProxy)
|
||||
end
|
||||
def <<(*records); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
records: T.any(::Domain::PostFileFingerprint, T::Enumerable[T.any(::Domain::PostFileFingerprint, T::Enumerable[::Domain::PostFileFingerprint])])
|
||||
records: T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[::Domain::PostFile::BitFingerprint])])
|
||||
).returns(PrivateCollectionProxy)
|
||||
end
|
||||
def append(*records); end
|
||||
@@ -1250,45 +1334,45 @@ class Domain::PostFileFingerprint
|
||||
|
||||
sig do
|
||||
params(
|
||||
records: T.any(::Domain::PostFileFingerprint, T::Enumerable[T.any(::Domain::PostFileFingerprint, T::Enumerable[::Domain::PostFileFingerprint])])
|
||||
records: T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[::Domain::PostFile::BitFingerprint])])
|
||||
).returns(PrivateCollectionProxy)
|
||||
end
|
||||
def concat(*records); end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def load_target; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
records: T.any(::Domain::PostFileFingerprint, T::Enumerable[T.any(::Domain::PostFileFingerprint, T::Enumerable[::Domain::PostFileFingerprint])])
|
||||
records: T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[::Domain::PostFile::BitFingerprint])])
|
||||
).returns(PrivateCollectionProxy)
|
||||
end
|
||||
def prepend(*records); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
records: T.any(::Domain::PostFileFingerprint, T::Enumerable[T.any(::Domain::PostFileFingerprint, T::Enumerable[::Domain::PostFileFingerprint])])
|
||||
records: T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[::Domain::PostFile::BitFingerprint])])
|
||||
).returns(PrivateCollectionProxy)
|
||||
end
|
||||
def push(*records); end
|
||||
|
||||
sig do
|
||||
params(
|
||||
other_array: T.any(::Domain::PostFileFingerprint, T::Enumerable[T.any(::Domain::PostFileFingerprint, T::Enumerable[::Domain::PostFileFingerprint])])
|
||||
).returns(T::Array[::Domain::PostFileFingerprint])
|
||||
other_array: T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[T.any(::Domain::PostFile::BitFingerprint, T::Enumerable[::Domain::PostFile::BitFingerprint])])
|
||||
).returns(T::Array[::Domain::PostFile::BitFingerprint])
|
||||
end
|
||||
def replace(other_array); end
|
||||
|
||||
sig { returns(PrivateAssociationRelation) }
|
||||
def scope; end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def target; end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def to_a; end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def to_ary; end
|
||||
end
|
||||
|
||||
@@ -1296,17 +1380,17 @@ class Domain::PostFileFingerprint
|
||||
include CommonRelationMethods
|
||||
include GeneratedRelationMethods
|
||||
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def to_a; end
|
||||
|
||||
sig { returns(T::Array[::Domain::PostFileFingerprint]) }
|
||||
sig { returns(T::Array[::Domain::PostFile::BitFingerprint]) }
|
||||
def to_ary; end
|
||||
end
|
||||
|
||||
class PrivateRelationGroupChain < PrivateRelation
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig { params(column_name: T.any(String, Symbol)).returns(T::Hash[T.untyped, T.any(Integer, Float, BigDecimal)]) }
|
||||
def average(column_name); end
|
||||
@@ -1341,7 +1425,7 @@ class Domain::PostFileFingerprint
|
||||
end
|
||||
|
||||
class PrivateRelationWhereChain
|
||||
Elem = type_member { { fixed: ::Domain::PostFileFingerprint } }
|
||||
Elem = type_member { { fixed: ::Domain::PostFile::BitFingerprint } }
|
||||
|
||||
sig { params(args: T.untyped).returns(PrivateRelation) }
|
||||
def associated(*args); end
|
||||
1014
sorbet/rbi/dsl/domain/post_file/fingerprint_join.rbi
generated
Normal file
1014
sorbet/rbi/dsl/domain/post_file/fingerprint_join.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -454,6 +454,20 @@ class Domain::PostFile::InkbunnyPostFile
|
||||
module EnumMethodsModule; end
|
||||
|
||||
module GeneratedAssociationMethods
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
def bit_fingerprint_ids; end
|
||||
|
||||
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
||||
def bit_fingerprint_ids=(ids); end
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::PostFile` class because it declared `has_many :bit_fingerprints`.
|
||||
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
|
||||
sig { returns(::Domain::PostFile::BitFingerprint::PrivateCollectionProxy) }
|
||||
def bit_fingerprints; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::PostFile::BitFingerprint]).void }
|
||||
def bit_fingerprints=(value); end
|
||||
|
||||
sig { returns(T.nilable(::BlobFile)) }
|
||||
def blob; end
|
||||
|
||||
@@ -463,9 +477,6 @@ class Domain::PostFile::InkbunnyPostFile
|
||||
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(::Domain::PostFileFingerprint) }
|
||||
def build_fingerprint(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def build_log_entry(*args, &blk); end
|
||||
|
||||
@@ -478,12 +489,6 @@ class Domain::PostFile::InkbunnyPostFile
|
||||
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(::Domain::PostFileFingerprint) }
|
||||
def create_fingerprint(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFileFingerprint) }
|
||||
def create_fingerprint!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_log_entry(*args, &blk); end
|
||||
|
||||
@@ -496,12 +501,6 @@ class Domain::PostFile::InkbunnyPostFile
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Post) }
|
||||
def create_post!(*args, &blk); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
def fingerprint; end
|
||||
|
||||
sig { params(value: T.nilable(::Domain::PostFileFingerprint)).void }
|
||||
def fingerprint=(value); end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def log_entry; end
|
||||
|
||||
@@ -517,9 +516,6 @@ class Domain::PostFile::InkbunnyPostFile
|
||||
sig { returns(T.nilable(::BlobFile)) }
|
||||
def reload_blob; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::PostFileFingerprint)) }
|
||||
def reload_fingerprint; end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def reload_log_entry; end
|
||||
|
||||
@@ -529,14 +525,25 @@ class Domain::PostFile::InkbunnyPostFile
|
||||
sig { void }
|
||||
def reset_blob; end
|
||||
|
||||
sig { void }
|
||||
def reset_fingerprint; end
|
||||
|
||||
sig { void }
|
||||
def reset_log_entry; end
|
||||
|
||||
sig { void }
|
||||
def reset_post; end
|
||||
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
def thumbnail_ids; end
|
||||
|
||||
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
||||
def thumbnail_ids=(ids); end
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::PostFile` class because it declared `has_many :thumbnails`.
|
||||
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
|
||||
sig { returns(::Domain::PostFile::Thumbnail::PrivateCollectionProxy) }
|
||||
def thumbnails; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::PostFile::Thumbnail]).void }
|
||||
def thumbnails=(value); end
|
||||
end
|
||||
|
||||
module GeneratedAssociationRelationMethods
|
||||
|
||||
1447
sorbet/rbi/dsl/domain/post_file/thumbnail.rbi
generated
Normal file
1447
sorbet/rbi/dsl/domain/post_file/thumbnail.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
1011
sorbet/rbi/dsl/domain/post_file/vector_fingerprint.rbi
generated
Normal file
1011
sorbet/rbi/dsl/domain/post_file/vector_fingerprint.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
sorbet/rbi/dsl/domain/post_file_thumbnail_job.rbi
generated
Normal file
27
sorbet/rbi/dsl/domain/post_file_thumbnail_job.rbi
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `Domain::PostFileThumbnailJob`.
|
||||
# Please instead update this file by running `bin/tapioca dsl Domain::PostFileThumbnailJob`.
|
||||
|
||||
|
||||
class Domain::PostFileThumbnailJob
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
class << self
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
args: T::Hash[::Symbol, T.untyped],
|
||||
block: T.nilable(T.proc.params(job: Domain::PostFileThumbnailJob).void)
|
||||
).returns(T.any(Domain::PostFileThumbnailJob, FalseClass))
|
||||
end
|
||||
def perform_later(args, &block); end
|
||||
|
||||
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
|
||||
def perform_now(args); end
|
||||
end
|
||||
end
|
||||
@@ -34,10 +34,10 @@ RSpec.describe Domain::PostsController, type: :controller do
|
||||
|
||||
context "with an image URL" do
|
||||
let(:mock_hash_value) { "1010101010101010" }
|
||||
let(:mock_fingerprints) { Domain::PostFileFingerprint.none }
|
||||
let(:mock_fingerprints) { Domain::PostFile::BitFingerprint.none }
|
||||
let(:temp_file_path) { "/tmp/test_image.jpg" }
|
||||
|
||||
it "uses PostFileFingerprint model methods for fingerprinting and finding similar images" do
|
||||
it "uses Phash::Fingerprint model methods for fingerprinting and finding similar images" do
|
||||
# We need to mock the image downloading and processing since we can't do that in tests
|
||||
allow(controller).to receive(:process_image_input).and_return(
|
||||
[temp_file_path, "image/jpeg"],
|
||||
@@ -47,17 +47,12 @@ RSpec.describe Domain::PostsController, type: :controller do
|
||||
)
|
||||
|
||||
# Set up expectations for our model methods - this is what we're really testing
|
||||
expect(Domain::PostFileFingerprint).to receive(:from_file_path).with(
|
||||
temp_file_path,
|
||||
).and_return(
|
||||
instance_double(
|
||||
Domain::PostFileFingerprint,
|
||||
hash_value: mock_hash_value,
|
||||
),
|
||||
)
|
||||
expect(Domain::PostFile::BitFingerprint).to receive(
|
||||
:from_file_path,
|
||||
).with(temp_file_path).and_return(mock_hash_value)
|
||||
|
||||
expect(Domain::PostFileFingerprint).to receive(
|
||||
:similar_to_fingerprint,
|
||||
expect(Domain::PostFile::BitFingerprint).to receive(
|
||||
:order_by_fingerprint_distance,
|
||||
).with(mock_hash_value).and_return(mock_fingerprints)
|
||||
|
||||
post :visual_results,
|
||||
|
||||
@@ -7,4 +7,22 @@ FactoryBot.define do
|
||||
content_type { "text/plain" }
|
||||
sha256 { Digest::SHA256.digest(contents) }
|
||||
end
|
||||
|
||||
trait :image_blob do
|
||||
content_type { "image/jpeg" }
|
||||
transient do
|
||||
contents do
|
||||
File.read(
|
||||
Rails.root.join(
|
||||
"test/fixtures/files/images/thumb-036aaab6-low-quality.jpeg",
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
trait :text_blob do
|
||||
content_type { "text/plain" }
|
||||
transient { contents { "test content #{SecureRandom.alphanumeric(10)}" } }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,12 +12,12 @@ FactoryBot.define do
|
||||
state { "ok" }
|
||||
url_str { "https://example.com/image.jpg" }
|
||||
last_status_code { 200 }
|
||||
before(:create) do
|
||||
self.log_entry =
|
||||
before(:create) do |post_file|
|
||||
post_file.log_entry =
|
||||
create(
|
||||
:http_log_entry,
|
||||
url_str: self.url_str,
|
||||
status_code: self.last_status_code,
|
||||
url_str: post_file.url_str,
|
||||
status_code: post_file.last_status_code,
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -26,12 +26,12 @@ FactoryBot.define do
|
||||
state { "terminal_error" }
|
||||
url_str { "https://example.com/image.jpg" }
|
||||
last_status_code { 404 }
|
||||
before(:create) do
|
||||
self.log_entry =
|
||||
before(:create) do |post_file|
|
||||
post_file.log_entry =
|
||||
create(
|
||||
:http_log_entry,
|
||||
url_str: self.url_str,
|
||||
status_code: self.last_status_code,
|
||||
url_str: post_file.url_str,
|
||||
status_code: post_file.last_status_code,
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -40,36 +40,25 @@ FactoryBot.define do
|
||||
state { "retryable_error" }
|
||||
url_str { "https://example.com/image.jpg" }
|
||||
last_status_code { 500 }
|
||||
before(:create) do
|
||||
self.log_entry =
|
||||
before(:create) do |post_file|
|
||||
post_file.log_entry =
|
||||
create(
|
||||
:http_log_entry,
|
||||
url_str: self.url_str,
|
||||
status_code: self.last_status_code,
|
||||
url_str: post_file.url_str,
|
||||
status_code: post_file.last_status_code,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
trait :image_file do
|
||||
file_name { "image.jpg" }
|
||||
url_str { "https://example.com/image.jpg" }
|
||||
file_order { 1 }
|
||||
md5_initial { "d41d8cd98f00b204e9800998ecf8427e" }
|
||||
md5_full { "d41d8cd98f00b204e9800998ecf8427e" }
|
||||
before(:create) do
|
||||
self.log_entry =
|
||||
create(
|
||||
:blob_file,
|
||||
content_type: "image/jpeg",
|
||||
contents:
|
||||
File.read(
|
||||
Rails.root.join(
|
||||
"test/fixtures/files/images/thumb-036aaab6-low-quality.jpeg",
|
||||
),
|
||||
),
|
||||
)
|
||||
end
|
||||
log_entry { create(:http_log_entry, :image_entry) }
|
||||
end
|
||||
|
||||
trait :text_file do
|
||||
log_entry { create(:http_log_entry, :text_entry) }
|
||||
url_str { log_entry.uri_str }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
7
spec/factories/domain/post_file/thumbnail.rb
Normal file
7
spec/factories/domain/post_file/thumbnail.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
FactoryBot.define do
|
||||
factory :domain_post_file_thumbnail, class: "Domain::PostFile::Thumbnail" do
|
||||
post_file { create(:domain_post_file, :image_file) }
|
||||
thumb_type { :size_32_32 }
|
||||
frame { 0 }
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
# typed: false
|
||||
FactoryBot.define do
|
||||
factory :domain_post_file_thumbnail, class: "Domain::PostFileThumbnail" do
|
||||
association :post_file, factory: :domain_post_file
|
||||
thumbnail_type { Domain::ThumbnailType::Small.name }
|
||||
end
|
||||
end
|
||||
@@ -51,5 +51,15 @@ FactoryBot.define do
|
||||
trait :proxy do
|
||||
performed_by { "proxy-1" }
|
||||
end
|
||||
|
||||
trait :image_entry do
|
||||
content_type { "image/jpeg" }
|
||||
response { create(:blob_file, :image_blob) }
|
||||
end
|
||||
|
||||
trait :text_entry do
|
||||
content_type { "text/plain" }
|
||||
response { create(:blob_file, :text_blob) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -113,11 +113,9 @@ describe Domain::Fa::User do
|
||||
|
||||
expect(user.scanned_incremental_at).to eq(incremental_at)
|
||||
expect(user.due_for_incremental_scan?).to be_falsey
|
||||
expect(user.time_ago_for_incremental_scan).to eq("1 day ago")
|
||||
|
||||
expect(user.scanned_follows_at).to eq(follows_at)
|
||||
expect(user.due_for_follows_scan?).to be_falsey
|
||||
expect(user.time_ago_for_follows_scan).to eq("2 days ago")
|
||||
|
||||
# truthy if a scan has not happened in a long time
|
||||
incremental_at = Time.parse 1.year.ago.iso8601
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# typed: false
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
RSpec.describe Domain::PostFile::BitFingerprint, type: :model do
|
||||
describe ".similar_to_fingerprint" do
|
||||
let(:image_paths) do
|
||||
{
|
||||
@@ -20,38 +20,30 @@ RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
@fingerprints = []
|
||||
|
||||
image_paths.keys.each_with_index do |image_path, index|
|
||||
# Create post and attach the original image
|
||||
post = create(:domain_post_fa_post)
|
||||
|
||||
# Read the image file content
|
||||
image_content =
|
||||
File.read(
|
||||
Rails.root.join("test/fixtures/files", image_path),
|
||||
mode: "rb",
|
||||
)
|
||||
|
||||
# Create a blob file with the image content
|
||||
blob =
|
||||
log_entry =
|
||||
create(
|
||||
:blob_file,
|
||||
content_bytes: image_content,
|
||||
:http_log_entry,
|
||||
content_type: "image/jpeg",
|
||||
sha256: Digest::SHA256.digest(image_content),
|
||||
response:
|
||||
create(
|
||||
:blob_file,
|
||||
content_type: "image/jpeg",
|
||||
contents:
|
||||
File.read(
|
||||
Rails.root.join("test/fixtures/files", image_path),
|
||||
mode: "rb",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Create a post file with the blob
|
||||
post_file =
|
||||
create(
|
||||
:domain_post_file,
|
||||
post: post,
|
||||
state: "ok",
|
||||
blob_sha256: blob.sha256,
|
||||
)
|
||||
|
||||
# Create a fingerprint for the post file
|
||||
# The fingerprint should be automatically calculated in the before_validation callback
|
||||
fingerprint = Domain::PostFileFingerprint.create!(post_file: post_file)
|
||||
@fingerprints << fingerprint
|
||||
post_file = create(:domain_post_file, state: "ok", log_entry:)
|
||||
thumbs = Domain::PostFile::Thumbnail.create_for_post_file!(post_file)
|
||||
expect(thumbs.size).to eq(2)
|
||||
fingerprints = described_class.create_for_post_file!(post_file)
|
||||
expect(fingerprints.size).to eq(1)
|
||||
@fingerprints << fingerprints.first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,15 +58,15 @@ RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
|
||||
# Generate a fingerprint for the low-quality image
|
||||
fingerprint = DHashVips::IDHash.fingerprint(low_quality_image_path.to_s)
|
||||
hash_value =
|
||||
fingerprint_value =
|
||||
fingerprint.to_s(2).rjust(
|
||||
Domain::PostFileFingerprint::HASH_SIZE * 8,
|
||||
Domain::PostFile::BitFingerprint::HASH_SIZE_BYTES * 8,
|
||||
"0",
|
||||
)
|
||||
|
||||
# Find similar fingerprints
|
||||
similar_fingerprints =
|
||||
Domain::PostFileFingerprint.similar_to_fingerprint(hash_value)
|
||||
described_class.order_by_fingerprint_distance(fingerprint_value).to_a
|
||||
|
||||
# The original image's fingerprint should be in the top results
|
||||
# The PostgreSQL operator <~> (hamming distance) may produce ties
|
||||
@@ -85,16 +77,16 @@ RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
|
||||
# Get the distance for the first result to compare with our expected result
|
||||
first_result_distance =
|
||||
Domain::PostFileFingerprint.hamming_distance(
|
||||
hash_value,
|
||||
similar_fingerprints.first.hash_value,
|
||||
described_class.hamming_distance(
|
||||
fingerprint_value,
|
||||
similar_fingerprints.first.fingerprint_value,
|
||||
)
|
||||
|
||||
# Get the distance for our expected fingerprint
|
||||
expected_distance =
|
||||
Domain::PostFileFingerprint.hamming_distance(
|
||||
hash_value,
|
||||
expected_fingerprint.hash_value,
|
||||
described_class.hamming_distance(
|
||||
fingerprint_value,
|
||||
expected_fingerprint.fingerprint_value,
|
||||
)
|
||||
|
||||
# Verify our expected fingerprint is in the results and has the same or
|
||||
@@ -116,8 +108,8 @@ RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
other_fingerprint_indices = (0...@fingerprints.size).to_a - [index]
|
||||
other_fingerprint_indices.each do |other_index|
|
||||
# Generate a fingerprint from the low-quality image
|
||||
low_quality_fingerprint = Domain::PostFileFingerprint.new
|
||||
low_quality_fingerprint.hash_value = hash_value
|
||||
low_quality_fingerprint = described_class.new
|
||||
low_quality_fingerprint.fingerprint_value = fingerprint_value
|
||||
|
||||
# Compare with an original image that it should NOT match
|
||||
other_similarity =
|
||||
@@ -148,9 +140,9 @@ RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
|
||||
# Test the hamming_distance class method
|
||||
expect(
|
||||
Domain::PostFileFingerprint.hamming_distance(
|
||||
reference_fingerprint.hash_value,
|
||||
@fingerprints[1].hash_value,
|
||||
described_class.hamming_distance(
|
||||
reference_fingerprint.fingerprint_value,
|
||||
@fingerprints[1].fingerprint_value,
|
||||
),
|
||||
).to be > 0
|
||||
end
|
||||
@@ -183,79 +175,19 @@ RSpec.describe Domain::PostFileFingerprint, type: :model do
|
||||
.to_s
|
||||
|
||||
# Create a fingerprint from the file path
|
||||
fingerprint = Domain::PostFileFingerprint.from_file_path(file_path)
|
||||
fingerprint = described_class.from_file_path(file_path)
|
||||
|
||||
# Check that the fingerprint was created properly
|
||||
expect(fingerprint).to be_a(Domain::PostFileFingerprint)
|
||||
expect(fingerprint.hash_value).to be_present
|
||||
expect(fingerprint.hash_value.length).to eq(
|
||||
Domain::PostFileFingerprint::HASH_SIZE * 8,
|
||||
expect(fingerprint).to be_a(String)
|
||||
expect(fingerprint.length).to eq(
|
||||
Domain::PostFile::BitFingerprint::HASH_SIZE_BYTES * 8,
|
||||
)
|
||||
expect(fingerprint.persisted?).to be(false)
|
||||
end
|
||||
|
||||
it "raises an error for non-existent files" do
|
||||
expect {
|
||||
Domain::PostFileFingerprint.from_file_path("/non/existent/file.jpg")
|
||||
described_class.from_file_path("/non/existent/file.jpg")
|
||||
}.to raise_error(ArgumentError, /File does not exist/)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".from_vips_image" do
|
||||
it "creates a fingerprint from a Vips::Image" do
|
||||
# Load a test image as a Vips::Image
|
||||
file_path =
|
||||
Rails
|
||||
.root
|
||||
.join(
|
||||
"test/fixtures/files/images/thumb-036aaab6-content-container.jpeg",
|
||||
)
|
||||
.to_s
|
||||
vips_image = Vips::Image.new_from_file(file_path)
|
||||
|
||||
# Create a fingerprint from the Vips::Image
|
||||
fingerprint = Domain::PostFileFingerprint.from_vips_image(vips_image)
|
||||
|
||||
# Check that the fingerprint was created properly
|
||||
expect(fingerprint).to be_a(Domain::PostFileFingerprint)
|
||||
expect(fingerprint.hash_value).to be_present
|
||||
expect(fingerprint.hash_value.length).to eq(
|
||||
Domain::PostFileFingerprint::HASH_SIZE * 8,
|
||||
)
|
||||
expect(fingerprint.persisted?).to be(false)
|
||||
end
|
||||
|
||||
it "generates fingerprints with high similarity to from_file_path for the same image" do
|
||||
# This test verifies that fingerprints from vips_image and file_path are highly similar
|
||||
# for the same image, even if not exactly identical due to format considerations
|
||||
file_path =
|
||||
Rails
|
||||
.root
|
||||
.join(
|
||||
"test/fixtures/files/images/thumb-ac63d9d7-content-container.jpeg",
|
||||
)
|
||||
.to_s
|
||||
|
||||
# Create fingerprints using both methods
|
||||
vips_image = Vips::Image.new_from_file(file_path)
|
||||
from_vips_fingerprint =
|
||||
Domain::PostFileFingerprint.from_vips_image(vips_image)
|
||||
from_file_fingerprint =
|
||||
Domain::PostFileFingerprint.from_file_path(file_path)
|
||||
|
||||
# The fingerprints might not be 100% identical due to how Vips handles direct images
|
||||
# vs how it handles file loading, but they should have high similarity
|
||||
similarity =
|
||||
100 -
|
||||
(
|
||||
Domain::PostFileFingerprint.hamming_distance(
|
||||
from_vips_fingerprint.hash_value,
|
||||
from_file_fingerprint.hash_value,
|
||||
).to_f / (Domain::PostFileFingerprint::HASH_SIZE * 8) * 100
|
||||
)
|
||||
|
||||
# The similarity should be very high (above 90%)
|
||||
expect(similarity).to be > 90
|
||||
end
|
||||
end
|
||||
end
|
||||
53
spec/models/domain/post_file/thumbnail_spec.rb
Normal file
53
spec/models/domain/post_file/thumbnail_spec.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# typed: false
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Domain::PostFile::Thumbnail, type: :model do
|
||||
describe "validation" do
|
||||
it "validates thumbable content types" do
|
||||
post_file = create(:domain_post_file, :image_file)
|
||||
|
||||
# Create a PostFileThumbnail with valid params
|
||||
thumbnail =
|
||||
described_class.new(post_file:, thumb_type: :size_32_32, frame: 0)
|
||||
|
||||
expect(thumbnail).to be_valid
|
||||
expect(thumbnail.save).to be true
|
||||
end
|
||||
|
||||
it "rejects non-thumbable content types" do
|
||||
post_file = create(:domain_post_file, :text_file)
|
||||
|
||||
# Create a PostFileThumbnail with invalid content type
|
||||
thumbnail =
|
||||
described_class.new(post_file:, thumb_type: :size_32_32, frame: 0)
|
||||
|
||||
expect(thumbnail).not_to be_valid
|
||||
expect(thumbnail.errors[:post_file]).to include(
|
||||
"must be a thumbnailable content type",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "thumbnailing" do
|
||||
context "with JPEG images" do
|
||||
it "successfully creates a thumbnail from a JPEG image" do
|
||||
post_file = create(:domain_post_file, :image_file)
|
||||
|
||||
# Create a PostFileThumbnail
|
||||
thumbnail =
|
||||
described_class.new(post_file:, thumb_type: :size_32_32, frame: 0)
|
||||
expect(thumbnail.save).to be true
|
||||
|
||||
# Verify the thumbnail file was created
|
||||
expect(thumbnail.absolute_file_path).not_to be_nil
|
||||
expect(File.exist?(thumbnail.absolute_file_path)).to be true
|
||||
|
||||
# Verify the thumbnail has a reasonable size (should be smaller than original)
|
||||
original_size =
|
||||
File.size(post_file.log_entry.response.absolute_file_path)
|
||||
thumbnail_size = File.size(thumbnail.absolute_file_path)
|
||||
expect(thumbnail_size).to be < original_size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,108 +2,4 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Domain::PostFile, type: :model do
|
||||
describe "after_save callback" do
|
||||
it "creates and saves a fingerprint when saving a post file with a valid image blob" do
|
||||
# Create a post
|
||||
post = create(:domain_post_fa_post)
|
||||
|
||||
# Setup an image file
|
||||
image_path =
|
||||
Rails.root.join(
|
||||
"test/fixtures/files/images/thumb-036aaab6-content-container.jpeg",
|
||||
)
|
||||
image_content = File.read(image_path, mode: "rb")
|
||||
|
||||
# Create blob with image content
|
||||
blob =
|
||||
create(
|
||||
:blob_file,
|
||||
content_bytes: image_content,
|
||||
content_type: "image/jpeg",
|
||||
sha256: Digest::SHA256.digest(image_content),
|
||||
)
|
||||
|
||||
# Count fingerprints before creating the post file
|
||||
fingerprint_count_before = Domain::PostFileFingerprint.count
|
||||
|
||||
# Create and save the post file with the blob reference
|
||||
post_file =
|
||||
create(
|
||||
:domain_post_file,
|
||||
post: post,
|
||||
state: "ok",
|
||||
blob_sha256: blob.sha256,
|
||||
)
|
||||
|
||||
# Verify a fingerprint was automatically created and saved
|
||||
expect(Domain::PostFileFingerprint.count).to eq(
|
||||
fingerprint_count_before + 1,
|
||||
)
|
||||
expect(Domain::PostFileFingerprint.last.post_file).to eq(post_file)
|
||||
|
||||
# Verify the fingerprint has actual content
|
||||
fingerprint = Domain::PostFileFingerprint.last
|
||||
expect(fingerprint.hash_value).to be_present
|
||||
expect(fingerprint.hash_value.length).to eq(
|
||||
Domain::PostFileFingerprint::HASH_SIZE * 8,
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a fingerprint for non-image files" do
|
||||
# Create a post
|
||||
post = create(:domain_post_fa_post)
|
||||
|
||||
# Setup a text file
|
||||
text_content = "This is a text file, not an image"
|
||||
|
||||
# Create blob with text content
|
||||
blob =
|
||||
create(
|
||||
:blob_file,
|
||||
content_bytes: text_content,
|
||||
content_type: "text/plain",
|
||||
sha256: Digest::SHA256.digest(text_content),
|
||||
)
|
||||
|
||||
# Count fingerprints before creating the post file
|
||||
fingerprint_count_before = Domain::PostFileFingerprint.count
|
||||
|
||||
# Create and save the post file with the blob reference
|
||||
post_file =
|
||||
create(
|
||||
:domain_post_file,
|
||||
post: post,
|
||||
state: "ok",
|
||||
blob_sha256: blob.sha256,
|
||||
)
|
||||
|
||||
# Verify no new fingerprint was created
|
||||
expect(Domain::PostFileFingerprint.count).to eq(fingerprint_count_before)
|
||||
expect(
|
||||
Domain::PostFileFingerprint.where(post_file: post_file).count,
|
||||
).to eq(0)
|
||||
end
|
||||
|
||||
it "does not create a fingerprint for files with missing blobs" do
|
||||
# Create a post
|
||||
post = create(:domain_post_fa_post)
|
||||
|
||||
# Count fingerprints before creating the post file
|
||||
fingerprint_count_before = Domain::PostFileFingerprint.count
|
||||
|
||||
# Create post file without a blob
|
||||
post_file =
|
||||
create(
|
||||
:domain_post_file,
|
||||
post: post,
|
||||
state: "pending", # No blob associated
|
||||
)
|
||||
|
||||
# Verify no new fingerprint was created
|
||||
expect(Domain::PostFileFingerprint.count).to eq(fingerprint_count_before)
|
||||
expect(
|
||||
Domain::PostFileFingerprint.where(post_file: post_file).count,
|
||||
).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user