Files
redux-scraper/app/models/domain/post/bluesky_post.rb
2025-08-05 19:21:38 +00:00

150 lines
3.4 KiB
Ruby

# typed: strict
class Domain::Post::BlueskyPost < Domain::Post
aux_table :bluesky, allow_redefining: :title
belongs_to :first_seen_entry, class_name: "::HttpLogEntry", optional: true
has_multiple_files! Domain::PostFile::BlueskyPostFile
has_single_creator! Domain::User::BlueskyUser
has_faving_users! Domain::User::BlueskyUser
after_initialize { self.state ||= "ok" if new_record? }
enum :state,
{
ok: "ok",
removed: "removed",
scan_error: "scan_error",
file_error: "file_error",
},
prefix: "state"
validates :state, presence: true
validates :bluesky_id, presence: true
sig { override.returns([String, Symbol]) }
def self.param_prefix_and_attribute
["bsky", :bluesky_id]
end
sig { override.returns(String) }
def self.view_prefix
"bsky"
end
sig { override.returns(Symbol) }
def self.post_order_attribute
:bluesky_id
end
sig { override.returns(Domain::DomainType) }
def self.domain_type
Domain::DomainType::Bluesky
end
sig { override.returns(T.nilable(Domain::User)) }
def primary_creator_for_view
self.creator
end
sig { override.returns(T.nilable(T.any(String, Integer))) }
def domain_id_for_view
self.bluesky_id
end
sig { override.returns(T.nilable(Addressable::URI)) }
def external_url_for_view
handle = self.creator&.handle
if bluesky_id.present? && handle.present?
Addressable::URI.parse(
"https://bsky.app/profile/#{handle}/post/#{bluesky_id}",
)
end
end
sig { override.returns(T.nilable(Domain::PostFile)) }
def primary_file_for_view
self.files.first
end
sig { override.returns(T::Boolean) }
def pending_scan?
scanned_at.nil?
end
sig { override.returns(T.nilable(String)) }
def description_html_for_view
text
end
sig { override.returns(String) }
def description_html_base_domain
"bsky.app"
end
sig { override.returns(T.nilable(String)) }
def description_for_view
self.text
end
sig { override.returns(T.nilable(Integer)) }
def num_favorites_for_view
like_count
end
sig { override.returns(T.nilable(String)) }
def title
# Use first 50 characters of text as title, or a fallback
current_text = text
current_text.present? ? current_text.truncate(50) : "Bluesky Post"
end
sig { returns(T.nilable(String)) }
def status_for_view
case self.state
when "ok"
"OK"
when "removed"
"Removed"
when "scan_error"
"Scan error"
when "file_error"
"File error"
end
end
sig do
params(
at_uri: String,
creator: Domain::User::BlueskyUser,
first_seen_log_entry: T.nilable(HttpLogEntry),
).returns(Domain::Post::BlueskyPost)
end
def self.find_or_initialize_by_at_uri(
at_uri,
creator:,
first_seen_log_entry: nil
)
# Extract the record key (post ID) from AT URI
# e.g., "at://did:plc:abc123/app.bsky.feed.post/xyz789" -> "xyz789"
bluesky_id = at_uri.split("/").last
post =
Domain::Post::BlueskyPost.find_or_initialize_by(
bluesky_id: bluesky_id,
) do |post|
post.first_seen_entry = first_seen_log_entry
post.at_uri = at_uri
end
post.creator ||= creator
post
end
sig { override.returns(T.nilable(T::Array[TagForView])) }
def tags_for_view
return nil unless hashtags.present?
hashtags.map { |tag| TagForView.new(category: :general, value: tag) }
end
end