record user fav scans

This commit is contained in:
Dylan Knutson
2025-06-27 00:06:02 +00:00
parent 2deeb2bd78
commit c74cbfe4e0
20 changed files with 1954 additions and 64 deletions

View File

@@ -86,38 +86,16 @@ class Domain::Fa::Job::FavsJob < Domain::Fa::Job::Base
end
faved_post_ids_to_add = faved_post_ids - existing_faved_post_ids
upsert_faved_post_ids(user:, post_ids: faved_post_ids_to_add)
user.upsert_new_favs(
faved_post_ids_to_add.to_a,
log_entry: causing_log_entry,
)
ensure
user.save! if user
end
private
sig { params(user: Domain::User::FaUser, post_ids: T::Set[Integer]).void }
def upsert_faved_post_ids(user:, post_ids:)
ReduxApplicationRecord.transaction do
if post_ids.any?
post_ids.each_slice(1000) do |slice|
Domain::UserPostFav.upsert_all(
slice.map { |id| { user_id: user.id, post_id: id } },
unique_by: %i[user_id post_id],
)
end
# Use reset_counters to update the counter cache after using upsert_all
Domain::User.reset_counters(user.id, :user_post_favs)
logger.info(
format_tags(
make_tag("reset user_post_favs counter cache for user", user.id),
),
)
end
user.scanned_favs_at = Time.zone.now
end
logger.info(format_tags(make_tag("total new favs", post_ids.size)))
end
module ScanPageResult
extend T::Sig

View File

@@ -150,21 +150,17 @@ class Domain::Fa::Job::UserPageJob < Domain::Fa::Job::Base
if recent_faved_fa_ids.empty?
logger.info(format_tags("skipping favs scan, 0 favorites"))
user.scanned_favs_at = Time.current
user.upsert_new_favs([], log_entry: causing_log_entry)
elsif recent_faved_fa_ids.count < 8
logger.info(
format_tags(
"skipping favs scan, #{recent_faved_fa_ids.count} favorites < threshold",
),
)
user.user_post_favs.upsert_all(
recent_faved_posts.map(&:id).compact.map { |post_id| { post_id: } },
unique_by: %i[user_id post_id],
user.upsert_new_favs(
recent_faved_posts.map(&:id).compact,
log_entry: causing_log_entry,
)
# Use reset_counters to update the counter cache after upserting favs
Domain::User.reset_counters(user.id, :user_post_favs)
user.scanned_favs_at = Time.current
elsif user.scanned_favs_at.present?
known_faved_post_fa_ids =
user
@@ -203,22 +199,16 @@ class Domain::Fa::Job::UserPageJob < Domain::Fa::Job::Base
"unknown favs are all at beginning, adding fa_ids to favs: #{unknown_fav_fa_ids.join(", ")}",
)
if unknown_fav_fa_ids.any?
unknown_faved_posts =
unknown_fav_fa_ids
.map do |fa_id|
recent_faved_posts.find { |post| post.fa_id == fa_id }
end
.compact
user.user_post_favs.upsert_all(
unknown_faved_posts.map(&:id).compact.map { |post_id| { post_id: } },
unique_by: %i[user_id post_id],
)
Domain::User.reset_counters(user.id, :user_post_favs)
end
user.scanned_favs_at = Time.current
user.upsert_new_favs(
unknown_fav_fa_ids
.map do |fa_id|
recent_faved_posts.find { |post| post.fa_id == fa_id }
end
.compact
.map(&:id)
.compact,
log_entry: causing_log_entry,
)
end
end

View File

@@ -79,6 +79,7 @@ class Domain::User < ReduxApplicationRecord
end
attr_json :migrated_user_favs_at, :datetime
attr_json_due_timestamp :scanned_favs_at, 3.months
has_many :user_search_names,
class_name: "::Domain::UserSearchName",
@@ -136,6 +137,31 @@ class Domain::User < ReduxApplicationRecord
class_name: klass.name
end
has_many :favs_scans,
-> { order(created_at: :desc) },
inverse_of: :user,
class_name: Domain::UserJobEvent::FavsScan.name
sig do
params(post_ids: T::Array[Integer], log_entry: T.nilable(HttpLogEntry)).void
end
def upsert_new_favs(post_ids, log_entry: nil)
self.class.transaction do
if post_ids.any?
post_ids.each_slice(1000) do |slice|
self.user_post_favs.upsert_all(
slice.map { |post_id| { post_id: } },
unique_by: %i[user_id post_id],
)
end
end
self.favs_scans.create!(num_favs_added: post_ids.count, log_entry:)
Domain::User.reset_counters(id, :user_post_favs)
self.scanned_favs_at = Time.current
logger.info(format_tags(make_tag("upsert new favs", post_ids.size)))
end
end
sig { params(klass: T.class_of(Domain::User)).void }
def self.has_followed_users!(klass)
self.class_has_followed_users = klass

View File

@@ -5,7 +5,6 @@ class Domain::User::E621User < Domain::User
attr_json :favs_are_hidden, :boolean
attr_json :num_other_favs_cached, :integer
attr_json :scanned_favs_status, :string
attr_json_due_timestamp :scanned_favs_at, 1.month
attr_json :registered_at, :datetime
enum :scanned_favs_status, { ok: "ok", error: "error" }, prefix: :scanned_favs

View File

@@ -21,7 +21,6 @@ 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, 3.months
attr_json_due_timestamp :scanned_incremental_at, 1.month
attr_json :registered_at, :datetime
attr_json :migrated_followed_users_at, :datetime

View File

@@ -0,0 +1,10 @@
# typed: strict
class Domain::UserJobEvent < ReduxApplicationRecord
extend T::Sig
extend T::Helpers
include AttrJsonRecordAliases
abstract!
self.abstract_class = true
belongs_to :user, class_name: Domain::User.name
end

View File

@@ -0,0 +1,6 @@
# typed: strict
class Domain::UserJobEvent::FavsScan < Domain::UserJobEvent
self.table_name = "domain_user_job_event_fav_scans"
attr_json :num_favs_added, :integer
belongs_to :log_entry, class_name: "HttpLogEntry"
end

View File

@@ -17,4 +17,15 @@ class ActiveRecord::Migration
**options,
)
end
sig { params(name: String, values: T::Array[String]).void }
def create_enum(name, values)
reversible do |dir|
dir.up do
execute "CREATE TYPE #{name} AS ENUM (#{values.map { |t| "'#{t}'" }.join(",")})"
end
dir.down { execute "DROP TYPE #{name}" }
end
end
end

View File

@@ -1,5 +1,4 @@
# typed: strict
#
class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
extend T::Sig
@@ -28,17 +27,6 @@ class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
GROUP_TYPES = %w[Domain::PostGroup::InkbunnyPool Domain::PostGroup::E621Pool]
sig { params(name: String, values: T::Array[String]).void }
def create_enum(name, values)
reversible do |dir|
dir.up do
execute "CREATE TYPE #{name} AS ENUM (#{values.map { |t| "'#{t}'" }.join(",")})"
end
dir.down { execute "DROP TYPE #{name}" }
end
end
sig { void }
def change
up_only { execute "SET DEFAULT_TABLESPACE = mirai" }

View File

@@ -0,0 +1,12 @@
class CreateJobEventsTable < ActiveRecord::Migration[7.2]
def change
up_only { execute "SET DEFAULT_TABLESPACE = mirai" }
create_table :domain_user_job_event_fav_scans do |t|
t.references :user, null: false, foreign_key: { to_table: :domain_users }
t.references :log_entry, foreign_key: { to_table: :http_log_entries }
t.jsonb :json_attributes, default: {}
t.timestamps
end
end
end

View File

@@ -3040,6 +3040,39 @@ CREATE SEQUENCE public.domain_user_avatars_id_seq
ALTER SEQUENCE public.domain_user_avatars_id_seq OWNED BY public.domain_user_avatars.id;
--
-- Name: domain_user_job_event_fav_scans; Type: TABLE; Schema: public; Owner: -; Tablespace: mirai
--
CREATE TABLE public.domain_user_job_event_fav_scans (
id bigint NOT NULL,
user_id bigint NOT NULL,
log_entry_id bigint,
json_attributes jsonb DEFAULT '{}'::jsonb,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
--
-- Name: domain_user_job_event_fav_scans_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.domain_user_job_event_fav_scans_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: domain_user_job_event_fav_scans_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.domain_user_job_event_fav_scans_id_seq OWNED BY public.domain_user_job_event_fav_scans.id;
--
-- Name: domain_user_post_creations; Type: TABLE; Schema: public; Owner: -; Tablespace: mirai
--
@@ -4722,6 +4755,13 @@ ALTER TABLE ONLY public.domain_twitter_users ALTER COLUMN id SET DEFAULT nextval
ALTER TABLE ONLY public.domain_user_avatars ALTER COLUMN id SET DEFAULT nextval('public.domain_user_avatars_id_seq'::regclass);
--
-- Name: domain_user_job_event_fav_scans id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.domain_user_job_event_fav_scans ALTER COLUMN id SET DEFAULT nextval('public.domain_user_job_event_fav_scans_id_seq'::regclass);
--
-- Name: domain_user_search_names id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -5529,6 +5569,14 @@ ALTER TABLE ONLY public.domain_user_avatars
ADD CONSTRAINT domain_user_avatars_pkey PRIMARY KEY (id);
--
-- Name: domain_user_job_event_fav_scans domain_user_job_event_fav_scans_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: mirai
--
ALTER TABLE ONLY public.domain_user_job_event_fav_scans
ADD CONSTRAINT domain_user_job_event_fav_scans_pkey PRIMARY KEY (id);
--
-- Name: domain_user_search_names domain_user_search_names_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: mirai
--
@@ -7228,6 +7276,20 @@ CREATE UNIQUE INDEX index_domain_twitter_users_on_tw_id ON public.domain_twitter
CREATE INDEX index_domain_user_avatars_on_user_id ON public.domain_user_avatars USING btree (user_id);
--
-- Name: index_domain_user_job_event_fav_scans_on_log_entry_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
--
CREATE INDEX index_domain_user_job_event_fav_scans_on_log_entry_id ON public.domain_user_job_event_fav_scans USING btree (log_entry_id);
--
-- Name: index_domain_user_job_event_fav_scans_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
--
CREATE INDEX index_domain_user_job_event_fav_scans_on_user_id ON public.domain_user_job_event_fav_scans USING btree (user_id);
--
-- Name: index_domain_user_post_creations_on_post_id_and_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
--
@@ -8532,6 +8594,14 @@ ALTER INDEX public.index_blob_files_on_sha256 ATTACH PARTITION public.index_blob
ALTER INDEX public.index_blob_files_on_sha256 ATTACH PARTITION public.index_blob_files_63_on_sha256;
--
-- Name: domain_user_job_event_fav_scans fk_rails_0041c99646; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.domain_user_job_event_fav_scans
ADD CONSTRAINT fk_rails_0041c99646 FOREIGN KEY (log_entry_id) REFERENCES public.http_log_entries(id);
--
-- Name: domain_e621_favs fk_rails_0b7ec98aa2; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -8740,6 +8810,14 @@ ALTER TABLE ONLY public.domain_fa_follows
ADD CONSTRAINT fk_rails_87bfff6dba FOREIGN KEY (follower_id) REFERENCES public.domain_fa_users(id);
--
-- Name: domain_user_job_event_fav_scans fk_rails_8d6cca4835; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.domain_user_job_event_fav_scans
ADD CONSTRAINT fk_rails_8d6cca4835 FOREIGN KEY (user_id) REFERENCES public.domain_users(id);
--
-- Name: domain_inkbunny_posts fk_rails_91015c8e4f; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -8923,6 +9001,7 @@ ALTER TABLE ONLY public.domain_twitter_tweets
SET search_path TO "$user", public;
INSERT INTO "schema_migrations" (version) VALUES
('20250626191434'),
('20250619233027'),
('20250321050628'),
('20250310001341'),

View File

@@ -0,0 +1,16 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Domain::Fa::EnqueueUnscannedOkPosts`.
# Please instead update this file by running `bin/tapioca dsl Domain::Fa::EnqueueUnscannedOkPosts`.
class Domain::Fa::EnqueueUnscannedOkPosts
sig { returns(ColorLogger) }
def logger; end
class << self
sig { returns(ColorLogger) }
def logger; end
end
end

View File

@@ -11,6 +11,9 @@ class Domain::User
extend CommonRelationMethods
extend GeneratedRelationMethods
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
def favs_scan; end
sig { returns(ColorLogger) }
def logger; end
@@ -457,6 +460,20 @@ class Domain::User
sig { params(value: T::Enumerable[::Domain::Post]).void }
def faved_posts=(value); end
sig { returns(T::Array[T.untyped]) }
def favs_scan_ids; end
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
def favs_scan_ids=(ids); end
# This method is created by ActiveRecord on the `Domain::User` class because it declared `has_many :favs_scans`.
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
sig { returns(::Domain::UserJobEvent::FavsScan::PrivateCollectionProxy) }
def favs_scans; end
sig { params(value: T::Enumerable[::Domain::UserJobEvent::FavsScan]).void }
def favs_scans=(value); end
sig { returns(T::Array[T.untyped]) }
def followed_by_user_ids; end
@@ -1000,6 +1017,9 @@ class Domain::User
sig { void }
def restore_migrated_user_favs_at!; end
sig { void }
def restore_scanned_favs_at!; end
sig { void }
def restore_type!; end
@@ -1048,6 +1068,12 @@ class Domain::User
sig { returns(T::Boolean) }
def saved_change_to_migrated_user_favs_at?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def saved_change_to_scanned_favs_at; end
sig { returns(T::Boolean) }
def saved_change_to_scanned_favs_at?; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def saved_change_to_type; end
@@ -1084,6 +1110,61 @@ class Domain::User
sig { returns(T::Boolean) }
def saved_change_to_user_user_follows_to_count?; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at; end
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at=(value); end
sig { returns(T::Boolean) }
def scanned_favs_at?; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_before_last_save; end
sig { returns(T.untyped) }
def scanned_favs_at_before_type_cast; end
sig { returns(T::Boolean) }
def scanned_favs_at_came_from_user?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def scanned_favs_at_change; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def scanned_favs_at_change_to_be_saved; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def scanned_favs_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_in_database; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def scanned_favs_at_previous_change; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def scanned_favs_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_previously_was; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_was; end
sig { void }
def scanned_favs_at_will_change!; end
sig { returns(T.untyped) }
def type; end
@@ -1379,6 +1460,9 @@ class Domain::User
sig { returns(T::Boolean) }
def will_save_change_to_migrated_user_favs_at?; end
sig { returns(T::Boolean) }
def will_save_change_to_scanned_favs_at?; end
sig { returns(T::Boolean) }
def will_save_change_to_type?; end

View File

@@ -510,6 +510,20 @@ class Domain::User::E621User
sig { params(value: T::Enumerable[::Domain::Post::E621Post]).void }
def faved_posts=(value); end
sig { returns(T::Array[T.untyped]) }
def favs_scan_ids; end
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
def favs_scan_ids=(ids); end
# This method is created by ActiveRecord on the `Domain::User` class because it declared `has_many :favs_scans`.
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
sig { returns(::Domain::UserJobEvent::FavsScan::PrivateCollectionProxy) }
def favs_scans; end
sig { params(value: T::Enumerable[::Domain::UserJobEvent::FavsScan]).void }
def favs_scans=(value); end
sig { returns(T::Array[T.untyped]) }
def followed_by_user_ids; end

View File

@@ -549,6 +549,20 @@ class Domain::User::FaUser
sig { params(value: T::Enumerable[::Domain::Post::FaPost]).void }
def faved_posts=(value); end
sig { returns(T::Array[T.untyped]) }
def favs_scan_ids; end
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
def favs_scan_ids=(ids); end
# This method is created by ActiveRecord on the `Domain::User` class because it declared `has_many :favs_scans`.
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
sig { returns(::Domain::UserJobEvent::FavsScan::PrivateCollectionProxy) }
def favs_scans; end
sig { params(value: T::Enumerable[::Domain::UserJobEvent::FavsScan]).void }
def favs_scans=(value); end
sig { returns(T::Array[T.untyped]) }
def followed_by_user_ids; end

View File

@@ -11,6 +11,9 @@ class Domain::User::InkbunnyUser
extend CommonRelationMethods
extend GeneratedRelationMethods
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
def favs_scan; end
sig { returns(ColorLogger) }
def logger; end
@@ -524,6 +527,20 @@ class Domain::User::InkbunnyUser
sig { params(value: T::Enumerable[::Domain::Post::InkbunnyPost]).void }
def faved_posts=(value); end
sig { returns(T::Array[T.untyped]) }
def favs_scan_ids; end
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
def favs_scan_ids=(ids); end
# This method is created by ActiveRecord on the `Domain::User` class because it declared `has_many :favs_scans`.
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
sig { returns(::Domain::UserJobEvent::FavsScan::PrivateCollectionProxy) }
def favs_scans; end
sig { params(value: T::Enumerable[::Domain::UserJobEvent::FavsScan]).void }
def favs_scans=(value); end
sig { returns(T::Array[T.untyped]) }
def followed_by_user_ids; end
@@ -1283,6 +1300,9 @@ class Domain::User::InkbunnyUser
sig { void }
def restore_name!; end
sig { void }
def restore_scanned_favs_at!; end
sig { void }
def restore_scanned_gallery_at!; end
@@ -1364,6 +1384,12 @@ class Domain::User::InkbunnyUser
sig { returns(T::Boolean) }
def saved_change_to_name?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def saved_change_to_scanned_favs_at; end
sig { returns(T::Boolean) }
def saved_change_to_scanned_favs_at?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def saved_change_to_scanned_gallery_at; end
@@ -1418,6 +1444,61 @@ class Domain::User::InkbunnyUser
sig { returns(T::Boolean) }
def saved_change_to_user_user_follows_to_count?; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at; end
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at=(value); end
sig { returns(T::Boolean) }
def scanned_favs_at?; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_before_last_save; end
sig { returns(T.untyped) }
def scanned_favs_at_before_type_cast; end
sig { returns(T::Boolean) }
def scanned_favs_at_came_from_user?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def scanned_favs_at_change; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def scanned_favs_at_change_to_be_saved; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def scanned_favs_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_in_database; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def scanned_favs_at_previous_change; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def scanned_favs_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_previously_was; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_favs_at_was; end
sig { void }
def scanned_favs_at_will_change!; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def scanned_gallery_at; end
@@ -1870,6 +1951,9 @@ class Domain::User::InkbunnyUser
sig { returns(T::Boolean) }
def will_save_change_to_name?; end
sig { returns(T::Boolean) }
def will_save_change_to_scanned_favs_at?; end
sig { returns(T::Boolean) }
def will_save_change_to_scanned_gallery_at?; end

37
sorbet/rbi/dsl/domain/user_job_event.rbi generated Normal file
View File

@@ -0,0 +1,37 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Domain::UserJobEvent`.
# Please instead update this file by running `bin/tapioca dsl Domain::UserJobEvent`.
class Domain::UserJobEvent
sig { returns(ColorLogger) }
def logger; end
class << self
sig do
params(
name: Symbol,
type: T.any(Symbol, ActiveModel::Type::Value),
options: T.nilable(T::Hash[Symbol, T.untyped])
).void
end
def attr_json(name, type, options = nil); end
sig do
params(
default_container_attribute: T.nilable(Symbol),
bad_cast: T.nilable(Symbol),
unknown_key: T.nilable(Symbol)
).void
end
def attr_json_config(default_container_attribute: nil, bad_cast: nil, unknown_key: nil); end
sig { returns(T::Array[Symbol]) }
def attr_json_registry; end
sig { returns(ColorLogger) }
def logger; end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -203,6 +203,18 @@ describe Domain::Fa::Job::FavsJob do
expect(user.faved_posts).to match_array(posts)
end
it "creates a favs scan job event" do
expect do perform_now(args) end.to change(
Domain::UserJobEvent::FavsScan,
:count,
).by(1)
favs_scan = Domain::UserJobEvent::FavsScan.last
expect(favs_scan.user).to eq(user)
expect(favs_scan.num_favs_added).to eq(5)
expect(favs_scan.log_entry).to eq(log_entries[0])
end
it "creates missing users" do
expect(Domain::User::FaUser.find_by(url_name: "sepulte")).to be_nil
expect do perform_now(args) end.to change(
@@ -400,6 +412,20 @@ describe Domain::Fa::Job::FavsJob do
# Should have updated scanned_favs_at
expect(user.scanned_favs_at).to be_within(1.second).of(Time.current)
end
it "creates a favs scan job event" do
expect do perform_now(args) end.to change(
Domain::UserJobEvent::FavsScan,
:count,
).by(1)
end
it "records the number of favs added" do
perform_now(args)
favs_scan = Domain::UserJobEvent::FavsScan.last
expect(favs_scan.log_entry).to eq(log_entries[0])
expect(favs_scan.num_favs_added).to eq(2)
end
end
end

View File

@@ -752,6 +752,14 @@ describe Domain::Fa::Job::UserPageJob do
perform_now({ url_name: "angu" })
expect(user.scanned_favs_at).to be_within(3.seconds).of(Time.current)
end
it "records a favs scan" do
perform_now({ url_name: "angu" })
expect(user.favs_scans.count).to eq(1)
favs_scan = user.favs_scans.first
expect(favs_scan.log_entry).to eq(@log_entries[0])
expect(favs_scan.num_favs_added).to eq(0)
end
end
context "all favorites fit in the recently faved section" do
@@ -795,6 +803,14 @@ describe Domain::Fa::Job::UserPageJob do
expect(user.faved_posts.count).to eq(1)
expect(user.faved_posts.map(&:fa_id)).to eq([51_355_154])
end
it "records a favs scan" do
perform_now({ url_name: "lleaued" })
expect(user.favs_scans.count).to eq(1)
favs_scan = user.favs_scans.first
expect(favs_scan.log_entry).to eq(@log_entries[0])
expect(favs_scan.num_favs_added).to eq(1)
end
end
context "more favorites than fits in the recent faved section" do
@@ -822,6 +838,12 @@ describe Domain::Fa::Job::UserPageJob do
[hash_including(user:, caused_by_entry: @log_entries[0])],
)
end
it "does not record a favs scan" do
perform_now({ url_name: "dilgear" })
user = Domain::User::FaUser.find_by(url_name: "dilgear")
expect(user.favs_scans.count).to eq(0)
end
end
context "the user has had a favs scan in the past" do
@@ -873,6 +895,11 @@ describe Domain::Fa::Job::UserPageJob do
[hash_including(user:, caused_by_entry: @log_entries[0])],
)
end
it "does not record a favs scan" do
perform_now({ user: })
expect(user.favs_scans.count).to eq(0)
end
end
shared_examples "marks scanned_favs_at as recent" do