Migrate create_post_file_fingerprints rake task to typed class

- Extract rake task logic into Tasks::CreatePostFileFingerprintsTask class
- Add full Sorbet strict typing with comprehensive method signatures
- Implement Mode enum with proper string serialization for execution modes
- Inherit from InterruptableTask for signal handling (SIGINT/SIGTERM)
- Add comprehensive test coverage with 24 examples covering all modes
- Update Rakefile to instantiate and use the new task class
- Support all existing execution modes: post_file_descending, posts_descending, user, users_descending
- Maintain backward compatibility with existing environment variable interface
This commit is contained in:
Dylan Knutson
2025-07-08 05:10:43 +00:00
parent be36c74bbd
commit 4f8a5cfcff
3 changed files with 657 additions and 111 deletions

124
Rakefile
View File

@@ -293,120 +293,22 @@ task run_fa_user_avatar_jobs: :environment do
end
task create_post_file_fingerprints: %i[environment set_logger_stdout] do
PB_FORMAT = "%B %c/%C (%r/sec) %J%% %a %E"
task = Tasks::CreatePostFileFingerprintsTask.new
def migrate_posts_for_user(user)
puts "migrating posts for #{user.to_param}"
posts = user.posts.includes(files: %i[blob thumbnails bit_fingerprints])
pb =
ProgressBar.create(
total: posts.count,
progress_mark: " ",
remainder_mark: " ",
format: PB_FORMAT,
)
posts.find_in_batches(batch_size: 64) do |batch|
ReduxApplicationRecord.transaction do
batch.each do |post|
migrate_post(post)
pb.progress = [pb.progress + 1, pb.total].min
end
end
mode =
if ENV["post_file_descending"].present?
Tasks::CreatePostFileFingerprintsTask::Mode::PostFileDescending
elsif ENV["posts_descending"].present?
Tasks::CreatePostFileFingerprintsTask::Mode::PostsDescending
elsif ENV["user"].present?
Tasks::CreatePostFileFingerprintsTask::Mode::User
elsif ENV["users_descending"].present?
Tasks::CreatePostFileFingerprintsTask::Mode::UsersDescending
else
raise "need one of: post_file_descending, posts_descending, user, users_descending"
end
end
def migrate_post(post)
puts "#{post.creator&.url_name} (#{post.creator&.user_user_follows_to_count}) :: #{post.to_param} / '#{post.title_for_view}'"
ColorLogger.quiet do
post.files.each do |file|
migrate_post_file(file)
rescue StandardError => e
puts "error: #{e.message}"
end
end
end
def migrate_post_file(post_file)
ColorLogger.quiet do
Domain::PostFileThumbnailJob.new.perform({ post_file: })
rescue => e
puts "error: #{e.message}"
end
end
if ENV["post_file_descending"].present?
total = 49_783_962 # cache this value
pb =
ProgressBar.create(
total:,
progress_mark: " ",
remainder_mark: " ",
format: PB_FORMAT,
)
i = 0
Domain::PostFile
.where(state: "ok")
.includes(:blob)
.find_each(
order: :desc,
batch_size: 32,
start: ENV["start_at"],
) do |post_file|
i += 1
if i % 100 == 0
post = post_file.post
creator_str =
(post.class.has_creators? ? post.creator.to_param : "(none)")
post_desc =
"#{creator_str&.rjust(20)} / #{post_file.post&.to_param}".ljust(40)
puts "post_file = #{post_file.id} :: #{post_desc} - #{post_file.post.title_for_view}"
end
migrate_post_file(post_file)
pb.progress = [pb.progress + 1, pb.total].min
end
elsif ENV["posts_descending"].present?
# total = Domain::Post.count
total = 66_431_808 # cache this value
pb =
ProgressBar.create(
total:,
progress_mark: " ",
remainder_mark: " ",
format: PB_FORMAT,
)
Domain::Post.find_each(order: :desc) do |post|
migrate_post(post) unless post.is_a?(Domain::Post::InkbunnyPost)
pb.progress = [pb.progress + 1, pb.total].min
end
elsif 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
migrated_file = File.open("migrated_files.txt", "a+")
migrated_file.seek(0)
migrated_users = migrated_file.readlines.map(&:strip)
users =
Domain::User::FaUser.order(
Arel.sql("user_user_follows_to_count DESC NULLS LAST"),
).pluck(:id)
users.each do |user_id|
user = Domain::User::FaUser.find(user_id)
next if migrated_users.include?(user.to_param)
puts "migrating posts for #{user.to_param} (#{user.num_watched_by} watched by)"
migrate_posts_for_user(user)
migrated_file.write("#{user.to_param}\n")
migrated_file.flush
end
migrated_file.close
else
raise "need 'user' or 'users_descending'"
end
task.run(mode: mode, user_param: ENV["user"], start_at: ENV["start_at"])
end
task enqueue_pending_post_files: :environment do