Update Dockerfile and Ruby files for improved dependency management and type safety
- Upgraded bundler version from 2.4.5 to 2.5.6 in Dockerfile for better compatibility. - Updated gem versions for faiss and rails_live_reload to their latest versions. - Modified Ruby files to enforce strict typing with Sorbet, enhancing type safety across various models and jobs. - Added type signatures to methods in Inkbunny and FA domain models, ensuring better type checking and documentation. - Improved job argument handling in Inkbunny jobs, including priority settings for deferred jobs. - Refactored initialization logic in several models to ensure default states are set correctly. These changes aim to enhance the overall stability and maintainability of the codebase.
This commit is contained in:
14
Dockerfile
14
Dockerfile
@@ -9,13 +9,13 @@ RUN \
|
||||
cmake
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
RUN gem install bundler -v '2.4.5'
|
||||
RUN gem install bundler -v '2.5.6'
|
||||
COPY gems gems
|
||||
WORKDIR /usr/src/app/gems/xdiff-rb
|
||||
RUN bundle install
|
||||
RUN bundle _2.5.6_ install
|
||||
RUN rake compile
|
||||
WORKDIR /usr/src/app/gems/rb-bsdiff
|
||||
RUN bundle install
|
||||
RUN bundle _2.5.6_ install
|
||||
RUN rake compile
|
||||
|
||||
# Primary image
|
||||
@@ -34,9 +34,9 @@ RUN \
|
||||
libblas-dev liblapack-dev
|
||||
|
||||
# preinstall gems that take a long time to install
|
||||
RUN MAKE="make -j12" gem install bundler -v '2.4.6' --verbose
|
||||
RUN MAKE="make -j12" gem install faiss -v '0.2.5' --verbose
|
||||
RUN MAKE="make -j12" gem install rails_live_reload -v '0.3.4' --verbose
|
||||
RUN MAKE="make -j12" gem install bundler -v '2.5.6' --verbose
|
||||
RUN MAKE="make -j12" gem install faiss -v '0.3.2' --verbose
|
||||
RUN MAKE="make -j12" gem install rails_live_reload -v '0.3.6' --verbose
|
||||
RUN bundle config --global frozen 1
|
||||
|
||||
# set up nodejs 18.x deb repo
|
||||
@@ -58,7 +58,7 @@ COPY --from=native-gems /usr/src/app/gems/rb-bsdiff /gems/rb-bsdiff
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
RUN bundle install
|
||||
RUN bundle _2.5.6_ install
|
||||
|
||||
# install js dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: true
|
||||
# typed: strict
|
||||
|
||||
# Processes a page of submissions from the API search endpoint.
|
||||
#
|
||||
@@ -10,6 +10,8 @@ class Domain::Inkbunny::Job::ApiSearchPageProcessor
|
||||
|
||||
SUBMISSIONS_PER_PAGE = 100
|
||||
MAX_LOOP_COUNT = 50
|
||||
|
||||
sig { returns(T::Array[Domain::Inkbunny::Post]) }
|
||||
attr_reader :changed_posts
|
||||
|
||||
sig { void }
|
||||
|
||||
@@ -57,6 +57,7 @@ module Domain::Inkbunny::Job
|
||||
defer_job(
|
||||
Domain::Inkbunny::Job::UserGalleryJob,
|
||||
{ user: user, caused_by_entry: first_log_entry },
|
||||
{ priority: 2 },
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -110,6 +110,7 @@ module Domain::Inkbunny::Job
|
||||
defer_job(
|
||||
Domain::Inkbunny::Job::FileJob,
|
||||
{ file: file, caused_by_entry: caused_by_entry },
|
||||
{ priority: 1 },
|
||||
)
|
||||
end
|
||||
post.save!
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
# typed: strict
|
||||
|
||||
module HasIndexedPost
|
||||
extend ActiveSupport::Concern
|
||||
extend T::Helpers
|
||||
extend T::Sig
|
||||
abstract!
|
||||
|
||||
# Abstract methods that must be implemented by including class
|
||||
sig { abstract.returns(T.nilable(IndexedPost)) }
|
||||
def indexed_post
|
||||
end
|
||||
|
||||
sig { abstract.params(post: T.nilable(IndexedPost)).void }
|
||||
def indexed_post=(post)
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def created_at
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def posted_at
|
||||
end
|
||||
|
||||
sig { abstract.returns(T::Boolean) }
|
||||
def posted_at_changed?
|
||||
end
|
||||
|
||||
included do
|
||||
T.bind(
|
||||
self,
|
||||
T.all(T.class_of(ReduxApplicationRecord), T::Class[HasIndexedPost]),
|
||||
)
|
||||
has_one :indexed_post,
|
||||
as: :postable,
|
||||
dependent: :destroy,
|
||||
@@ -9,16 +39,18 @@ module HasIndexedPost
|
||||
autosave: true
|
||||
|
||||
before_create :ensure_indexed_post!
|
||||
sig { returns(IndexedPost) }
|
||||
def ensure_indexed_post!
|
||||
self.indexed_post ||=
|
||||
IndexedPost.new(created_at: self.created_at, postable: self)
|
||||
end
|
||||
|
||||
before_save :ensure_indexed_post_posted_at!
|
||||
sig { returns(T.nilable(IndexedPost)) }
|
||||
def ensure_indexed_post_posted_at!
|
||||
if self.posted_at_changed?
|
||||
self.ensure_indexed_post!
|
||||
self.indexed_post.posted_at = self.posted_at
|
||||
T.must(self.indexed_post).tap { |ip| ip.posted_at = self.posted_at }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::E621::Post < ReduxApplicationRecord
|
||||
self.table_name = "domain_e621_posts"
|
||||
|
||||
@@ -12,7 +14,7 @@ class Domain::E621::Post < ReduxApplicationRecord
|
||||
|
||||
validates_presence_of(:e621_id, :state)
|
||||
after_initialize do
|
||||
self.state ||= :ok
|
||||
self.state = :ok unless self.state.present?
|
||||
self.state_detail ||= {}
|
||||
self.flags_array ||= []
|
||||
self.pools_array ||= []
|
||||
@@ -35,30 +37,37 @@ class Domain::E621::Post < ReduxApplicationRecord
|
||||
foreign_key: :e621_id,
|
||||
optional: true
|
||||
|
||||
sig { returns(String) }
|
||||
def to_param
|
||||
self.e621_id.to_s
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
||||
def tags_array
|
||||
ta = super
|
||||
return nil if ta.nil?
|
||||
ta.is_a?(Hash) ? ta : { "general" => ta }
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(HttpLogEntry)) }
|
||||
def index_page_http_log_entry
|
||||
if state_detail["last_index_page_id"].present?
|
||||
HttpLogEntry.find_by(id: state_detail["last_index_page_id"])
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Addressable::URI)) }
|
||||
def file_uri
|
||||
Addressable::URI.parse(self.file_url_str) if self.file_url_str.present?
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Time)) }
|
||||
def e621_updated_at
|
||||
str = state_detail["e621_updated_at"]
|
||||
Time.parse(str) if str
|
||||
end
|
||||
|
||||
sig { params(time: T.any(Time, String)).void }
|
||||
def e621_updated_at=(time)
|
||||
time = Time.parse(time) if time.is_a?(String)
|
||||
state_detail["e621_updated_at"] = time.iso8601
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::E621::Tag < ReduxApplicationRecord
|
||||
self.table_name = "domain_e621_tags"
|
||||
has_many :taggings, class_name: "Domain::E621::Tagging", inverse_of: :tag
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::E621::Tagging < ReduxApplicationRecord
|
||||
self.table_name = "domain_e621_taggings"
|
||||
self.primary_key = %i[post_id tag_id]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Fa::Fav < ReduxApplicationRecord
|
||||
self.table_name = "domain_fa_favs"
|
||||
self.primary_key = %i[user_id post_id]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Fa::Follow < ReduxApplicationRecord
|
||||
self.table_name = "domain_fa_follows"
|
||||
self.primary_key = %i[follower_id followed_id]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Fa::Post < ReduxApplicationRecord
|
||||
self.table_name = "domain_fa_posts"
|
||||
|
||||
@@ -45,14 +47,17 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
foreign_key: :post_id,
|
||||
dependent: :destroy
|
||||
|
||||
sig { returns(String) }
|
||||
def to_param
|
||||
self.fa_id.to_s
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Addressable::URI)) }
|
||||
def file_uri
|
||||
Addressable::URI.parse(self.file_url_str) if self.file_url_str
|
||||
end
|
||||
|
||||
sig { params(uri: T.nilable(T.any(String, Addressable::URI))).void }
|
||||
def file_uri=(uri)
|
||||
if uri
|
||||
uri = Addressable::URI.parse(uri)
|
||||
@@ -63,6 +68,7 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Addressable::URI)) }
|
||||
def thumbnail_uri
|
||||
if self.state_detail["thumbnail_url_str"]
|
||||
Addressable::URI.parse(self.state_detail["thumbnail_url_str"])
|
||||
@@ -71,6 +77,7 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(uri: T.nilable(T.any(String, Addressable::URI))).void }
|
||||
def thumbnail_uri=(uri)
|
||||
if uri
|
||||
uri = Addressable::URI.parse(uri)
|
||||
@@ -81,10 +88,12 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def scanned?
|
||||
self.file_url_str.present?
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Time)) }
|
||||
def scanned_at
|
||||
# at some point, `scanned_at` was populated to avoid having to look up the
|
||||
# post's `last_submission_page` log entry, but we fall back to that
|
||||
@@ -96,36 +105,35 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(time: T.nilable(Time)).void }
|
||||
def scanned_at=(time)
|
||||
unless time.nil?
|
||||
unless time.is_a?(Time)
|
||||
raise ArgumentError.new("time must be Time, was #{time.class}")
|
||||
end
|
||||
end
|
||||
self.state_detail["scanned_at"] = time&.to_i
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def posted_at
|
||||
pa = super
|
||||
return pa if pa
|
||||
@posted_at ||=
|
||||
begin
|
||||
contents = guess_last_submission_page&.response&.contents
|
||||
if contents
|
||||
parser = Domain::Fa::Parser::Page.new(contents)
|
||||
parser.submission.posted_date if parser.probably_submission?
|
||||
end
|
||||
begin
|
||||
contents = guess_last_submission_page&.response&.contents
|
||||
if contents
|
||||
parser = Domain::Fa::Parser::Page.new(contents)
|
||||
parser.submission.posted_date if parser.probably_submission?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(log_entry: T.nilable(HttpLogEntry)).void }
|
||||
def last_submission_page=(log_entry)
|
||||
self.log_entry_detail["last_submission_page_id"] = log_entry.id
|
||||
self.log_entry_detail["last_submission_page_id"] = log_entry&.id
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(HttpLogEntry)) }
|
||||
def last_submission_page
|
||||
HttpLogEntry.find_by_id(self.log_entry_detail["last_submission_page_id"])
|
||||
HttpLogEntry.find_by(id: self.log_entry_detail["last_submission_page_id"])
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(HttpLogEntry)) }
|
||||
def guess_last_submission_page
|
||||
last_submission_page ||
|
||||
begin
|
||||
@@ -141,6 +149,7 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def description
|
||||
content = super
|
||||
return nil if content.nil? || content.blank?
|
||||
@@ -149,30 +158,33 @@ class Domain::Fa::Post < ReduxApplicationRecord
|
||||
# always empty and a <br><br>
|
||||
lines = content.lines.map(&:strip).map(&:chomp)
|
||||
if lines.length > 3
|
||||
if lines[0] == "" && lines[1].start_with?("<a href=") &&
|
||||
if lines[0] == "" && lines[1]&.start_with?("<a href=") &&
|
||||
lines[2] == "<br><br>"
|
||||
return lines[3..].join("\n")
|
||||
return (lines[3..] || []).join("\n")
|
||||
end
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def have_file?
|
||||
self.file_id.present?
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
submission: T.untyped,
|
||||
first_seen_log_entry: T.nilable(HttpLogEntry),
|
||||
).returns(T::Hash[String, T.untyped])
|
||||
end
|
||||
def self.hash_from_submission_parser_helper(
|
||||
submission,
|
||||
first_seen_log_entry: nil
|
||||
)
|
||||
creator =
|
||||
begin
|
||||
Domain::Fa::User.find_or_create_by!(
|
||||
{ url_name: submission.artist_url_name },
|
||||
) { |user| user.name = submission.artist }
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
binding.pry
|
||||
end
|
||||
Domain::Fa::User.find_or_create_by!(
|
||||
{ url_name: submission.artist_url_name },
|
||||
) { |user| user.name = submission.artist }
|
||||
|
||||
{
|
||||
fa_id: submission.id,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Fa::PostFactor < ReduxApplicationRecord
|
||||
self.table_name = "domain_fa_post_factors"
|
||||
self.primary_key = "post_id"
|
||||
|
||||
@@ -24,8 +24,6 @@ class Domain::Fa::UserAvatar < ReduxApplicationRecord
|
||||
@file_model ||= BlobEntry.ensure(file_sha256) if file_sha256
|
||||
end
|
||||
|
||||
before_validation { file_uri = Addressable::URI.parse(file_url_str) }
|
||||
|
||||
def file_uri
|
||||
Addressable::URI.parse(file_url_str) unless file_url_str.blank?
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::Fav < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_favs"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::File < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_files"
|
||||
|
||||
@@ -12,7 +14,7 @@ class Domain::Inkbunny::File < ReduxApplicationRecord
|
||||
|
||||
enum :state, %i[ok error]
|
||||
after_initialize do
|
||||
self.state ||= :ok
|
||||
self.state = :ok unless self.state.present?
|
||||
self.state_detail ||= {}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::Follow < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_follows"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::Pool < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_pools"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::PoolJoin < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_pool_joins"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::Post < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_posts"
|
||||
include HasIndexedPost
|
||||
@@ -38,11 +40,12 @@ class Domain::Inkbunny::Post < ReduxApplicationRecord
|
||||
]
|
||||
|
||||
after_initialize do
|
||||
self.state ||= :ok
|
||||
self.state = :ok unless self.state.present?
|
||||
self.state_detail ||= {}
|
||||
self.ib_detail_raw ||= {}
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def to_param
|
||||
ib_post_id.to_s
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::Tag < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_tags"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::Tagging < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_taggings"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
class Domain::Inkbunny::User < ReduxApplicationRecord
|
||||
self.table_name = "domain_inkbunny_users"
|
||||
|
||||
@@ -30,15 +32,18 @@ class Domain::Inkbunny::User < ReduxApplicationRecord
|
||||
enum :avatar_state, %i[ok not_found error], prefix: :avatar
|
||||
|
||||
after_initialize do
|
||||
self.state ||= :ok
|
||||
self.state = :ok unless self.state.present?
|
||||
self.state_detail ||= {}
|
||||
self.avatar_state_detail ||= {}
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due_for_gallery_scan?
|
||||
scanned_gallery_at.blank? || scanned_gallery_at < 1.month.ago
|
||||
s = scanned_gallery_at
|
||||
s.blank? || s < 1.month.ago
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def to_param
|
||||
name
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# typed: true
|
||||
|
||||
class HttpLogEntry < ReduxApplicationRecord
|
||||
include ImmutableModel
|
||||
before_destroy { raise ActiveRecord::ReadOnlyRecord }
|
||||
@@ -46,7 +48,7 @@ class HttpLogEntry < ReduxApplicationRecord
|
||||
|
||||
def response_size
|
||||
if association(:response).loaded?
|
||||
response.size
|
||||
T.must(self.response).size
|
||||
else
|
||||
BlobEntry.where(sha256: response_sha256).pick(:size)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class ReduxApplicationRecord < ActiveRecord::Base
|
||||
extend T::Sig
|
||||
|
||||
self.abstract_class = true
|
||||
logger.level = Logger::ERROR
|
||||
end
|
||||
|
||||
3
justfile
3
justfile
@@ -21,3 +21,6 @@ test:
|
||||
|
||||
tc *args:
|
||||
bundle exec srb tc {{args}}
|
||||
|
||||
tapioca *args:
|
||||
bundle _2.5.6_ exec tapioca {{args}}
|
||||
|
||||
@@ -54,11 +54,6 @@ RSpec.describe Domain::Fa::Post do
|
||||
post.scanned_at = time
|
||||
expect(post.state_detail["scanned_at"]).to eq(time.to_i)
|
||||
end
|
||||
|
||||
it "raises ArgumentError for non-Time values" do
|
||||
expect { post.scanned_at = "2024-03-20" }.to raise_error(ArgumentError)
|
||||
expect { post.scanned_at = Date.today }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#scanned_at" do
|
||||
@@ -69,7 +64,7 @@ RSpec.describe Domain::Fa::Post do
|
||||
end
|
||||
|
||||
it "returns Time object from stored timestamp" do
|
||||
time = Time.current
|
||||
time = Time.now
|
||||
post.scanned_at = time
|
||||
expect(post.scanned_at).to be_within(1.second).of(time)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user