fix how account state is shown, link to last page scan hle

This commit is contained in:
Dylan Knutson
2025-02-19 23:22:24 +00:00
parent 784682bb44
commit 93b0de6073
10 changed files with 702 additions and 58 deletions

View File

@@ -102,11 +102,25 @@ module Domain::UsersHelper
end
can_view_timestamps = policy(user).view_page_scanned_at_timestamps?
can_view_log_entries = policy(user).view_log_entries?
if user.is_a?(Domain::User::FaUser) && can_view_timestamps
rows << ["Favorites", time_ago_or_never(user.scanned_favs_at)]
rows << ["Gallery scanned", time_ago_or_never(user.scanned_gallery_at)]
rows << ["Page scanned", time_ago_or_never(user.scanned_page_at)]
if can_view_log_entries && hle = user.guess_last_user_page_log_entry
rows << [
"Page scanned",
raw(
link_to(
time_ago_or_never(user.scanned_page_at),
log_entry_path(hle),
class: "text-blue-500 underline",
),
),
]
else
rows << ["Page scanned", time_ago_or_never(user.scanned_page_at)]
end
elsif user.is_a?(Domain::User::E621User) && can_view_timestamps
if user.favs_are_hidden
rows << ["Favorites hidden", "yes"]

View File

@@ -19,6 +19,10 @@ module HelpersInterface
def blob_path(sha256, format:, thumb: nil)
end
sig { abstract.params(hle: T.nilable(HttpLogEntry)).returns(String) }
def log_entry_path(hle)
end
sig { abstract.params(path: String).returns(String) }
def asset_path(path)
end

View File

@@ -137,6 +137,7 @@ class Domain::Fa::Job::FavsJob < Domain::Fa::Job::Base
response,
)
logger.error(format_tags("account disabled / not found", "aborting"))
user.scanned_favs_at = Time.zone.now
return ScanPageResult::Stop.new
end

View File

@@ -120,15 +120,42 @@ class Domain::User::FaUser < Domain::User
# TODO - write a test for this
sig { override.returns(String) }
def account_status_for_view
account_status ||
begin
if (hle = last_user_page_log_entry) && (response = hle.response) &&
(contents = response.contents)
parser =
Domain::Fa::Parser::Page.new(contents, require_logged_in: false)
parser.user_page.account_status.to_s if parser.probably_user_page?
end
end || "unknown"
as =
account_status ||
begin
if (hle = guess_last_user_page_log_entry) &&
(response = hle.response) && (contents = response.contents)
parser =
Domain::Fa::Parser::Page.new(contents, require_logged_in: false)
parser.user_page.account_status.to_s if parser.probably_user_page?
end
end || "unknown"
as.titlecase
end
sig { returns(String) }
def account_state_for_view
case state
when "ok"
"Ok"
when "account_disabled"
"Disabled"
when "error"
"Error"
else
"Unknown"
end
end
sig { returns(T.nilable(HttpLogEntry)) }
def guess_last_user_page_log_entry
last_user_page_log_entry ||
HttpLogEntry.find_by_uri_host_path(
"https://www.furaffinity.net/user/#{url_name}/",
) ||
HttpLogEntry.find_by_uri_host_path(
"https://www.furaffinity.net/user/#{url_name}",
)
end
sig { override.returns(T.nilable(String)) }

View File

@@ -47,7 +47,9 @@ class HttpLogEntry < ReduxApplicationRecord
end
def self.find_by_uri_host_path(uri)
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
find_by(uri_host: uri.host, uri_path: uri.path)
where(uri_host: uri.host, uri_path: uri.path).order(
requested_at: :desc,
).first
end
sig do

View File

@@ -31,4 +31,9 @@ class Domain::UserPolicy < ApplicationPolicy
def view_page_scanned_at_timestamps?
user&.admin? || user&.moderator? || false
end
sig { returns(T::Boolean) }
def view_log_entries?
user&.admin? || false
end
end

View File

@@ -1,6 +1,7 @@
<section class="animated-shadow-sky sky-section flex divide-none p-3">
<div class="flex grow items-center gap-4">
<img
<% cache [user, "name_icon_and_status"] do %>
<section class="animated-shadow-sky sky-section flex divide-none p-3">
<div class="flex grow items-center gap-4">
<img
src="<%= domain_user_avatar_img_src_path(user.avatar) %>"
class="h-12 w-12 rounded-lg"
/>
@@ -8,35 +9,36 @@
<div class="text-lg font-bold text-slate-900">
<%= user.name_for_view %>
</div>
<div class="flex gap-6 text-sm text-slate-400">
<% if policy(user).view_page_scanned_at_timestamps? %>
<div class="flex gap-6 text-sm text-slate-400">
<% if policy(user).view_page_scanned_at_timestamps? %>
<div class="flex flex-col">
<span class="font-medium italic text-slate-500">Status</span>
<span class=""><%= user.account_status_for_view %></span>
</div>
<% end %>
<%= render_for_model user, "overview_details", as: :user %>
<div class="flex flex-col">
<span class="font-medium italic text-slate-500">Status</span>
<span class=""><%= user.account_status_for_view %></span>
<span class="font-medium italic text-slate-500">Registered</span>
<span class="">
<% if registered_at = user.registered_at_for_view %>
<%= time_ago_in_words(registered_at) %>
ago
<% else %>
unknown
<% end %>
</span>
</div>
<% end %>
<%= render_for_model user, "overview_details", as: :user %>
<div class="flex flex-col">
<span class="font-medium italic text-slate-500">Registered</span>
<span class="">
<% if registered_at = user.registered_at_for_view %>
<%= time_ago_in_words(registered_at) %>
ago
<% else %>
unknown
<% end %>
</span>
</div>
</div>
</div>
</div>
<a
<a
href="<%= user.external_url_for_view %>"
target="_blank"
rel="noopener noreferrer"
class="sky-link flex items-center gap-2"
>
<span class="font-bold"><%= site_name_for_user(user) %></span>
<img src="<%= site_icon_path_for_user(user) %>" class="h-5 w-5" />
</a>
</section>
<span class="font-bold"><%= site_name_for_user(user) %></span>
<img src="<%= site_icon_path_for_user(user) %>" class="h-5 w-5" />
</a>
</section>
<% end %>

View File

@@ -1,4 +1,6 @@
<div class="flex flex-col">
<span class="font-medium italic text-slate-500">State</span>
<span class=""><%= user.state %></span>
</div>
<% cache [user, "overview_details"] do %>
<div class="flex flex-col">
<span class="font-medium italic text-slate-500">State</span>
<span class=""><%= user.account_state_for_view %></span>
</div>
<% end %>

View File

@@ -12,10 +12,12 @@ describe Domain::Fa::Job::FavsJob do
client_mock_config,
)
end
let(:user_url_name) { "zzreg" }
let(:args) { { url_name: user_url_name } }
shared_context "user exists" do
let!(:user) do
Domain::User::FaUser.create!(name: "zzreg", url_name: "zzreg")
create(:domain_user_fa_user, name: user_url_name, url_name: user_url_name)
end
end
@@ -33,16 +35,33 @@ describe Domain::Fa::Job::FavsJob do
end
end
shared_context "user is disabled" do
let(:user_url_name) { "caffeinatedarson" }
let(:client_mock_config) do
[
{
uri: "https://www.furaffinity.net/favorites/caffeinatedarson/",
status_code: 200,
content_type: "text/html",
contents:
SpecUtil.read_fixture_file(
"domain/fa/favorites/favs_account_not_found_user_caffeinatedarson.html",
),
},
]
end
end
context "the user does not yet exist" do
include_context "user has no favs"
it "creates the user" do
perform_now({ url_name: "zzreg" })
perform_now(args)
expect(Domain::User::FaUser.find_by(url_name: "zzreg")).not_to be_nil
end
it "enqueues a page scan job" do
perform_now({ url_name: "zzreg" })
perform_now(args)
user = Domain::User::FaUser.find_by(url_name: "zzreg")
expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::UserPageJob)).to match(
[including({ user:, caused_by_entry: log_entries[0] })],
@@ -50,7 +69,7 @@ describe Domain::Fa::Job::FavsJob do
end
it "does not create any new posts" do
expect do perform_now({ url_name: "zzreg" }) end.not_to change(
expect do perform_now(args) end.not_to change(
Domain::Post::FaPost,
:count,
)
@@ -62,7 +81,7 @@ describe Domain::Fa::Job::FavsJob do
include_context "user has no favs"
it "records no favs for the user" do
perform_now({ url_name: "zzreg" })
perform_now(args)
expect(user.faved_posts).to eq([])
end
end
@@ -72,11 +91,31 @@ describe Domain::Fa::Job::FavsJob do
it "does not re-scan the user" do
user.scanned_favs_at = old_scanned_at = 1.day.ago
user.save!
perform_now({ url_name: "zzreg" })
perform_now(args)
expect(user.scanned_favs_at).to be_within(1.second).of(old_scanned_at)
end
end
context "the user has been disabled" do
include_context "user exists"
include_context "user is disabled"
it "marks the user as scanned" do
expect do
perform_now(args)
user.reload
end.to change(user, :scanned_favs_at).from(nil).to(
be_within(1.second).of(Time.now),
)
end
it "changes user account state to account_disabled" do
expect do
perform_now(args)
user.reload
end.to change(user, :state).from("ok").to("account_disabled")
end
end
context "site indicates favs" do
include_context "user exists"
let(:fa_ids) do
@@ -118,7 +157,7 @@ describe Domain::Fa::Job::FavsJob do
end
it "records favs for the user" do
expect do perform_now({ url_name: "zzreg" }) end.to change(
expect do perform_now(args) end.to change(
Domain::Post::FaPost,
:count,
).by(5)
@@ -129,7 +168,7 @@ describe Domain::Fa::Job::FavsJob do
it "creates missing users" do
expect(Domain::User::FaUser.find_by(url_name: "sepulte")).to be_nil
expect do perform_now({ url_name: "zzreg" }) end.to change(
expect do perform_now(args) end.to change(
Domain::User::FaUser,
:count,
).by(5)
@@ -140,7 +179,7 @@ describe Domain::Fa::Job::FavsJob do
it "creates missing posts" do
expect(Domain::Post::FaPost.find_by(fa_id: 52_106_426)).to be_nil
expect do perform_now({ url_name: "zzreg" }) end.to change(
expect do perform_now(args) end.to change(
Domain::Post::FaPost,
:count,
).by(5)
@@ -149,13 +188,13 @@ describe Domain::Fa::Job::FavsJob do
end
it "updates scanned_favs_at" do
perform_now({ url_name: "zzreg" })
perform_now(args)
user.reload
expect(user.scanned_favs_at).to be_within(1.second).of(Time.now)
end
it "enqueues post scans" do
expect do perform_now({ url_name: "zzreg" }) end.to change(
expect do perform_now(args) end.to change(
Domain::Post::FaPost,
:count,
).by(5)
@@ -187,14 +226,14 @@ describe Domain::Fa::Job::FavsJob do
context "the user model already has favs recorded" do
it "adds favs newly present" do
perform_now({ url_name: "zzreg" })
perform_now(args)
posts = Domain::Post::FaPost.where(fa_id: fa_ids)
expect(user.faved_posts).to match_array(posts)
end
it "creates new FA post models and enqueues scans" do
p1 = Domain::Post::FaPost.create!(fa_id: fa_ids[0], creator: user)
expect do perform_now({ url_name: "zzreg" }) end.to change(
expect do perform_now(args) end.to change(
Domain::Post::FaPost,
:count,
).by(4)
@@ -213,7 +252,7 @@ describe Domain::Fa::Job::FavsJob do
include_context "user exists"
before do
perform_now({ url_name: "zzreg" })
perform_now(args)
user.update_attribute(:scanned_favs_at, 100.days.ago)
end
@@ -222,7 +261,7 @@ describe Domain::Fa::Job::FavsJob do
http_client_mock,
client_mock_config[0...1],
)
perform_now({ url_name: "zzreg" })
perform_now(args)
user.reload
expect(user.faved_posts.count).to eq(5)
expect(user.scanned_favs_at).to be_within(1.second).of(Time.now)
@@ -233,7 +272,7 @@ describe Domain::Fa::Job::FavsJob do
http_client_mock,
client_mock_config,
)
perform_now({ url_name: "zzreg", full_scan: true })
perform_now(args.merge(full_scan: true))
user.reload
expect(user.faved_posts.count).to eq(5)
expect(user.scanned_favs_at).to be_within(1.second).of(Time.now)
@@ -294,14 +333,14 @@ describe Domain::Fa::Job::FavsJob do
it "does not remove existing favs from the user when the post is not on the page" do
post = create(:domain_post_fa_post)
user.faved_posts << post
perform_now({ user: })
perform_now(args)
user.reload
expect(user.faved_posts).to include(post)
end
it "stops scanning when no new favs are found and adds posts from scanned pages" do
# Should only create posts from page 1 since those are the only new ones
expect do perform_now({ url_name: "zzreg" }) end.to change(
expect do perform_now(args) end.to change(
Domain::Post::FaPost,
:count,
).by(2)

File diff suppressed because one or more lines are too long