Don't forget to lycansubscribe

separated importing likes from fetching posts

+24 -43
app/importer.rb
··· 6 6 require_relative 'models/user' 7 7 8 8 class Importer 9 + attr_accessor :post_queue 10 + 9 11 def initialize(user_did) 10 12 @did = DID.new(user_did) 11 - @did.get_document 12 - 13 13 @user = User.find_or_create_by!(did: user_did) 14 14 15 - @pds_cache = { user_did => @did.document.pds_host } 16 15 @uid_cache = { user_did => @user.id } 17 16 end 18 17 ··· 41 40 42 41 def process_likes(likes) 43 42 likes.each do |record| 44 - like_rkey = record['uri'].split('/').last 45 - next if @user.likes.where(rkey: like_rkey).exists? 43 + begin 44 + like_rkey = record['uri'].split('/').last 45 + next if @user.likes.where(rkey: like_rkey).exists? 46 46 47 - like_time = Time.parse(record['value']['createdAt']) 47 + like_time = Time.parse(record['value']['createdAt']) 48 48 49 - post_uri = record['value']['subject']['uri'] 50 - parts = post_uri.split('/') 51 - next if parts[3] != 'app.bsky.feed.post' 49 + post_uri = record['value']['subject']['uri'] 50 + parts = post_uri.split('/') 51 + next if parts[3] != 'app.bsky.feed.post' 52 52 53 - post_did, _, post_rkey = parts[2..4] 53 + post_did, _, post_rkey = parts[2..4] 54 54 55 - if @uid_cache[post_did].nil? 56 - post_author = User.find_or_create_by!(did: post_did) 57 - @uid_cache[post_did] = post_author.id 58 - end 55 + if @uid_cache[post_did].nil? 56 + post_author = User.find_or_create_by!(did: post_did) 57 + @uid_cache[post_did] = post_author.id 58 + end 59 59 60 - author_id = @uid_cache[post_did] 61 - post = Post.find_by(user_id: author_id, rkey: post_rkey) 60 + post = Post.find_by(user_id: @uid_cache[post_did], rkey: post_rkey) 62 61 63 - if post.nil? 64 - begin 65 - post_record = fetch_post_record(post_did, post_rkey) 66 - json = post_record['value'] 67 - text = json.delete('text') 68 - created = json.delete('createdAt') 62 + if post 63 + @user.likes.create!(rkey: like_rkey, time: like_time, post: post) 64 + else 65 + like_stub = @user.likes.create!(rkey: like_rkey, time: like_time, post_uri: post_uri) 69 66 70 - post = Post.create!( 71 - user_id: author_id, 72 - rkey: post_rkey, 73 - time: Time.parse(created), 74 - text: text, 75 - data: JSON.generate(json) 76 - ) 77 - rescue StandardError => e 78 - # skip 79 - next 67 + if @post_queue 68 + @post_queue.push(like_stub) 69 + end 80 70 end 71 + rescue StandardError => e 72 + puts "Error in Importer#process_likes: #{record['uri']}: #{e}" 81 73 end 82 - 83 - @user.likes.create!(rkey: like_rkey, post: post, time: like_time) 84 74 end 85 - end 86 - 87 - def fetch_post_record(did, rkey) 88 - @pds_cache[did] ||= DID.new(did).document.pds_host 89 - 90 - pds = @pds_cache[did] 91 - sky = Minisky.new(pds, nil) 92 - 93 - sky.get_request('com.atproto.repo.getRecord', { repo: did, collection: 'app.bsky.feed.post', rkey: rkey }) 94 75 end 95 76 end
+3 -1
app/models/like.rb
··· 7 7 validates_presence_of :time, :rkey 8 8 validates_length_of :rkey, maximum: 13 9 9 10 + validates_presence_of :post_uri, if: -> { post_id.nil? } 11 + 10 12 belongs_to :user, foreign_key: 'actor_id' 11 - belongs_to :post 13 + belongs_to :post, optional: true 12 14 end
+52
app/post_downloader.rb
··· 1 + require 'didkit' 2 + require 'minisky' 3 + 4 + require_relative 'models/post' 5 + require_relative 'models/user' 6 + 7 + class PostDownloader 8 + def initialize 9 + @pds_cache = {} 10 + end 11 + 12 + def import_from_queue(queue) 13 + loop do 14 + like = queue.pop 15 + 16 + begin 17 + post = import_post(like.post_uri) 18 + like.update!(post: post, post_uri: nil) 19 + rescue StandardError => e 20 + puts "Error in PostDownloader: #{like.post_uri}: #{e}" 21 + end 22 + end 23 + end 24 + 25 + def import_post(post_uri) 26 + did, _, rkey = post_uri.split('/')[2..4] 27 + post_record = fetch_post_record(did, rkey) 28 + 29 + json = post_record['value'] 30 + text = json.delete('text') 31 + created = json.delete('createdAt') 32 + 33 + author = User.find_or_create_by!(did: did) 34 + 35 + Post.create!( 36 + user: author, 37 + rkey: rkey, 38 + time: Time.parse(created), 39 + text: text, 40 + data: JSON.generate(json) 41 + ) 42 + end 43 + 44 + def fetch_post_record(did, rkey) 45 + @pds_cache[did] ||= DID.new(did).document.pds_host 46 + 47 + pds = @pds_cache[did] 48 + sky = Minisky.new(pds, nil) 49 + 50 + sky.get_request('com.atproto.repo.getRecord', { repo: did, collection: 'app.bsky.feed.post', rkey: rkey }) 51 + end 52 + end
+6
db/migrate/20250830210139_add_uri_to_likes.rb
··· 1 + class AddUriToLikes < ActiveRecord::Migration[7.2] 2 + def change 3 + add_column :likes, :post_uri, :string 4 + change_column_null :likes, :post_id, true 5 + end 6 + end
+3 -2
db/schema.rb
··· 10 10 # 11 11 # It's strongly recommended that you check this file into your version control system. 12 12 13 - ActiveRecord::Schema[7.2].define(version: 2025_08_29_000022) do 13 + ActiveRecord::Schema[7.2].define(version: 2025_08_30_210139) do 14 14 # These are extensions that must be enabled in order to support this database 15 15 enable_extension "plpgsql" 16 16 ··· 18 18 t.integer "actor_id", null: false 19 19 t.string "rkey", limit: 13, null: false 20 20 t.datetime "time", null: false 21 - t.bigint "post_id", null: false 21 + t.bigint "post_id" 22 + t.string "post_uri" 22 23 t.index ["actor_id", "rkey"], name: "index_likes_on_actor_id_and_rkey", unique: true 23 24 t.index ["actor_id", "time", "id"], name: "index_likes_on_actor_id_and_time_and_id", order: { time: :desc, id: :desc } 24 25 end