bsky user registerd at scanning
This commit is contained in:
@@ -31,7 +31,7 @@ module Domain::UsersHelper
|
||||
end
|
||||
def domain_user_registered_at_ts_for_view(user)
|
||||
case user
|
||||
when Domain::User::FaUser, Domain::User::E621User
|
||||
when Domain::User::FaUser, Domain::User::E621User, Domain::User::BlueskyUser
|
||||
user.registered_at
|
||||
else
|
||||
nil
|
||||
|
||||
@@ -29,6 +29,29 @@ class Domain::Bluesky::Job::ScanUserJob < Domain::Bluesky::Job::Base
|
||||
|
||||
private
|
||||
|
||||
sig { params(user: Domain::User::BlueskyUser).void }
|
||||
def set_user_registration_time(user)
|
||||
audit_log = http_client.get("https://plc.directory/#{user.did}/log/audit")
|
||||
if audit_log.status_code != 200
|
||||
fatal_error(
|
||||
format_tags(
|
||||
"failed to get user registration time",
|
||||
make_tags(status_code: audit_log.status_code),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
audit_log_data =
|
||||
T.cast(JSON.parse(audit_log.body), T::Array[T::Hash[String, T.untyped]])
|
||||
if (data = audit_log_data.first)
|
||||
registered_at = Time.parse(data["createdAt"]).in_time_zone("UTC")
|
||||
user.registered_at = registered_at
|
||||
logger.info(
|
||||
format_tags("set user registration time", make_tags(registered_at:)),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User::BlueskyUser).void }
|
||||
def scan_user_profile(user)
|
||||
logger.info(format_tags("scanning user profile"))
|
||||
@@ -51,30 +74,6 @@ class Domain::Bluesky::Job::ScanUserJob < Domain::Bluesky::Job::Base
|
||||
|
||||
begin
|
||||
profile_data = JSON.parse(response.body)
|
||||
|
||||
if profile_data["error"]
|
||||
fatal_error(
|
||||
format_tags(
|
||||
"profile API error",
|
||||
make_tags(error: profile_data["error"]),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
record = profile_data["value"]
|
||||
if record
|
||||
# Update user profile information
|
||||
user.description = record["description"]
|
||||
user.display_name = record["displayName"]
|
||||
user.profile_raw = record
|
||||
|
||||
# Process avatar if present
|
||||
if record["avatar"] && record["avatar"]["ref"]
|
||||
process_user_avatar(user, record["avatar"])
|
||||
end
|
||||
end
|
||||
|
||||
user.scanned_profile_at = Time.zone.now
|
||||
rescue JSON::ParserError => e
|
||||
fatal_error(
|
||||
format_tags(
|
||||
@@ -83,6 +82,31 @@ class Domain::Bluesky::Job::ScanUserJob < Domain::Bluesky::Job::Base
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
if profile_data["error"]
|
||||
fatal_error(
|
||||
format_tags(
|
||||
"profile API error",
|
||||
make_tags(error: profile_data["error"]),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
record = profile_data["value"]
|
||||
if record
|
||||
# Update user profile information
|
||||
user.description = record["description"]
|
||||
user.display_name = record["displayName"]
|
||||
user.profile_raw = record
|
||||
|
||||
# Process avatar if present
|
||||
if record["avatar"] && record["avatar"]["ref"]
|
||||
process_user_avatar(user, record["avatar"])
|
||||
end
|
||||
end
|
||||
|
||||
set_user_registration_time(user)
|
||||
user.scanned_profile_at = Time.zone.now
|
||||
end
|
||||
|
||||
sig do
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<section class="sky-section animated-shadow-sky divide-y">
|
||||
<h2 class="section-header">User Stats</h2>
|
||||
<% stat_rows_for_user(user).each do |stat_row| %>
|
||||
<% label = stat_row.name %>
|
||||
<% value = stat_row.value %>
|
||||
<% fa_icon_class = stat_row.fa_icon_class %>
|
||||
<div class="flex items-center px-4 py-2 gap-2">
|
||||
<span class="grow text-slate-900 truncate"><%= label %></span>
|
||||
<span class="text-slate-500 relative group">
|
||||
<% value_str = case value %>
|
||||
<% when Integer %>
|
||||
<% number_with_delimiter(value, delimiter: ",") %>
|
||||
<% when HasTimestampsWithDueAt::TimestampScanInfo %>
|
||||
<% value.ago_in_words %>
|
||||
<% else %>
|
||||
<% value %>
|
||||
<% end %>
|
||||
<% if stat_row.link_to %>
|
||||
<%= link_to value_str, stat_row.link_to, class: "blue-link" %>
|
||||
<% else %>
|
||||
<%= value_str %>
|
||||
<% end %>
|
||||
<% if fa_icon_class %>
|
||||
<i class="fa-solid <%= fa_icon_class %>"></i>
|
||||
<% end %>
|
||||
<% if stat_row.hover_title %>
|
||||
<div class="absolute hidden group-hover:block bg-slate-800 text-white text-sm rounded px-2 py-1 top-1/2 -translate-y-1/2 right-full mr-2 whitespace-nowrap">
|
||||
<%= stat_row.hover_title %>
|
||||
</div>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
@@ -0,0 +1,7 @@
|
||||
class CreateBskyUserRegisteredAt < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
change_table :domain_users_bluesky_aux do |t|
|
||||
t.datetime :registered_at
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1957,7 +1957,8 @@ CREATE TABLE public.domain_users_bluesky_aux (
|
||||
scanned_posts_at timestamp(6) without time zone,
|
||||
profile_raw jsonb DEFAULT '{}'::jsonb,
|
||||
last_scan_log_entry_id bigint,
|
||||
last_posts_scan_log_entry_id bigint
|
||||
last_posts_scan_log_entry_id bigint,
|
||||
registered_at timestamp(6) without time zone
|
||||
);
|
||||
|
||||
|
||||
@@ -5884,6 +5885,7 @@ ALTER TABLE ONLY public.domain_twitter_tweets
|
||||
SET search_path TO "$user", public;
|
||||
|
||||
INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20250812215907'),
|
||||
('20250812214902'),
|
||||
('20250812214415'),
|
||||
('20250812211640'),
|
||||
|
||||
67
sorbet/rbi/dsl/domain/user/bluesky_user.rbi
generated
67
sorbet/rbi/dsl/domain/user/bluesky_user.rbi
generated
@@ -1606,6 +1606,61 @@ class Domain::User::BlueskyUser
|
||||
sig { void }
|
||||
def profile_raw_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def registered_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def registered_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def registered_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def registered_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def registered_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_was; end
|
||||
|
||||
sig { void }
|
||||
def registered_at_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_created_at!; end
|
||||
|
||||
@@ -1651,6 +1706,9 @@ class Domain::User::BlueskyUser
|
||||
sig { void }
|
||||
def restore_profile_raw!; end
|
||||
|
||||
sig { void }
|
||||
def restore_registered_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_scanned_favs_at!; end
|
||||
|
||||
@@ -1771,6 +1829,12 @@ class Domain::User::BlueskyUser
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_profile_raw?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_registered_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_registered_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Time), T.nilable(::Time)])) }
|
||||
def saved_change_to_scanned_favs_at; end
|
||||
|
||||
@@ -2356,6 +2420,9 @@ class Domain::User::BlueskyUser
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_profile_raw?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_registered_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_scanned_favs_at?; end
|
||||
|
||||
|
||||
67
sorbet/rbi/dsl/domain_users_bluesky_aux.rbi
generated
67
sorbet/rbi/dsl/domain_users_bluesky_aux.rbi
generated
@@ -1195,6 +1195,61 @@ class DomainUsersBlueskyAux
|
||||
sig { void }
|
||||
def profile_raw_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def registered_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def registered_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def registered_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def registered_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def registered_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_was; end
|
||||
|
||||
sig { void }
|
||||
def registered_at_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_base_table_id!; end
|
||||
|
||||
@@ -1231,6 +1286,9 @@ class DomainUsersBlueskyAux
|
||||
sig { void }
|
||||
def restore_profile_raw!; end
|
||||
|
||||
sig { void }
|
||||
def restore_registered_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_scanned_posts_at!; end
|
||||
|
||||
@@ -1312,6 +1370,12 @@ class DomainUsersBlueskyAux
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_profile_raw?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_registered_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_registered_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_scanned_posts_at; end
|
||||
|
||||
@@ -1531,6 +1595,9 @@ class DomainUsersBlueskyAux
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_profile_raw?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_registered_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_scanned_posts_at?; end
|
||||
|
||||
|
||||
@@ -45,6 +45,36 @@ RSpec.describe Domain::Bluesky::Job::ScanUserJob do
|
||||
content_type: "application/json",
|
||||
contents: profile_response_body,
|
||||
},
|
||||
{
|
||||
uri: "https://plc.directory/#{user.did}/log/audit",
|
||||
status_code: 200,
|
||||
content_type: "application/json",
|
||||
caused_by_entry_idx: 0,
|
||||
contents: [
|
||||
{
|
||||
did: "did:plc:le66o7kn5k4iqkxbih7gi4w2",
|
||||
operation: {
|
||||
sig: "signature",
|
||||
prev: nil,
|
||||
type: "plc_operation",
|
||||
services: {
|
||||
atproto_pds: {
|
||||
type: "AtprotoPersonalDataServer",
|
||||
endpoint: "https://bsky.social",
|
||||
},
|
||||
},
|
||||
alsoKnownAs: ["at://handle.bsky.social"],
|
||||
rotationKeys: ["rotation_key"],
|
||||
verificationMethods: {
|
||||
atproto: "verification_method",
|
||||
},
|
||||
},
|
||||
cid: "cid",
|
||||
nullified: false,
|
||||
createdAt: "2023-07-03T05:08:27.780Z",
|
||||
},
|
||||
].to_json,
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
@@ -61,6 +91,9 @@ RSpec.describe Domain::Bluesky::Job::ScanUserJob do
|
||||
expect(user.scanned_profile_at).to be_present
|
||||
expect(user.state).to eq("ok")
|
||||
expect(user.last_scan_log_entry).to eq(@log_entries.first)
|
||||
expect(user.registered_at).to eq(
|
||||
Time.parse("2023-07-03T05:08:27.780Z").in_time_zone("UTC"),
|
||||
)
|
||||
end
|
||||
|
||||
it "sets last_scan_log_entry to the HTTP response log entry" do
|
||||
@@ -138,6 +171,36 @@ RSpec.describe Domain::Bluesky::Job::ScanUserJob do
|
||||
content_type: "application/json",
|
||||
contents: profile_response_body,
|
||||
},
|
||||
{
|
||||
uri: "https://plc.directory/#{user.did}/log/audit",
|
||||
status_code: 200,
|
||||
content_type: "application/json",
|
||||
caused_by_entry_idx: 0,
|
||||
contents: [
|
||||
{
|
||||
did: "did:plc:le66o7kn5k4iqkxbih7gi4w2",
|
||||
operation: {
|
||||
sig: "signature",
|
||||
prev: nil,
|
||||
type: "plc_operation",
|
||||
services: {
|
||||
atproto_pds: {
|
||||
type: "AtprotoPersonalDataServer",
|
||||
endpoint: "https://bsky.social",
|
||||
},
|
||||
},
|
||||
alsoKnownAs: ["at://handle.bsky.social"],
|
||||
rotationKeys: ["rotation_key"],
|
||||
verificationMethods: {
|
||||
atproto: "verification_method",
|
||||
},
|
||||
},
|
||||
cid: "cid",
|
||||
nullified: false,
|
||||
createdAt: "2023-07-03T05:08:27.780Z",
|
||||
},
|
||||
].to_json,
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user