diff --git a/backend/app/models/post_version.rb b/backend/app/models/post_version.rb index a11ee89..c933813 100644 --- a/backend/app/models/post_version.rb +++ b/backend/app/models/post_version.rb @@ -11,9 +11,12 @@ class PostVersion < ApplicationRecord belongs_to :parent, class_name: 'Post', optional: true belongs_to :created_by_user, class_name: 'User', optional: true - enum :event_type, create: 'create', update: 'update', discard: 'discard', restore: 'restore' + enum :event_type, { create: 'create', + update: 'update', + discard: 'discard', + restore: 'restore' }, prefix: true, validate: true - validates :version_no, presence: true, numerically: { only_integer: true, greater_than: 0 } + validates :version_no, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :event_type, presence: true, inclusion: { in: event_types.keys } validates :url, presence: true diff --git a/backend/app/services/post_version_recorder.rb b/backend/app/services/post_version_recorder.rb index 20f5c1e..052bacd 100644 --- a/backend/app/services/post_version_recorder.rb +++ b/backend/app/services/post_version_recorder.rb @@ -3,7 +3,7 @@ class PostVersionRecorder new(post:, event_type:, created_by_user:).record! end - def initialize post:, event_type:, created_user: + def initialize post:, event_type:, created_by_user: @post = post @event_type = event_type @created_by_user = created_by_user @@ -23,7 +23,7 @@ class PostVersionRecorder title: attrs[:title], url: attrs[:url], thumbnail_base: attrs[:thumbnail_base], - tags: attrs[:url], + tags: attrs[:tags], parent: attrs[:parent], original_created_from: attrs[:original_created_from], original_created_before: attrs[:original_created_before], diff --git a/backend/db/migrate/20260409123700_create_post_versions.rb b/backend/db/migrate/20260409123700_create_post_versions.rb index f1c884e..b7a38d8 100644 --- a/backend/db/migrate/20260409123700_create_post_versions.rb +++ b/backend/db/migrate/20260409123700_create_post_versions.rb @@ -1,5 +1,17 @@ class CreatePostVersions < ActiveRecord::Migration[8.0] - def change + class Post < ApplicationRecord + self.table_name = 'posts' + end + + class PostTag < ApplicationRecord + self.table_name = 'post_tags' + end + + class PostVersion < ApplicationRecord + self.table_name = 'post_versions' + end + + def up create_table :post_versions do |t| t.references :post, null: false, foreign_key: true t.integer :version_no, null: false @@ -19,62 +31,45 @@ class CreatePostVersions < ActiveRecord::Migration[8.0] t.check_constraint "event_type IN ('create', 'update', 'discard', 'restore')" end - reversible do |dir| - dir.up do - execute <<~SQL - INSERT INTO - post_versions( - post_id - , version_no - , event_type - , title - , url - , thumbnail_base - , tags - , parent_id - , original_created_from - , original_created_before - , created_at - , created_by_user_id) - SELECT - posts.id - , 1 - , 'create' - , posts.title - , posts.url - , posts.thumbnail_base - , COALESCE(tag_snapshots.tags, '') - , posts.parent_id - , posts.original_created_from - , posts.original_created_before - , posts.created_at - , posts.uploaded_user_id - FROM - posts - LEFT JOIN - ( - SELECT - post_tags.post_id - , GROUP_CONCAT(tag_names.name ORDER BY tag_names.name SEPARATOR ' ') AS tags - FROM - post_tags - INNER JOIN - tags - ON - tags.id = post_tags.tag_id - INNER JOIN - tag_names - ON - tag_names.id = tags.tag_name_id - WHERE - post_tags.discarded_at IS NULL - GROUP BY - post_tags.post_id - ) tag_snapshots - ON - tag_snapshots.post_id = posts.id - SQL + say_with_time 'Backfilling post_versions' do + Post.find_in_batches(batch_size: 500) do |posts| + post_ids = posts.map(&:id) + + tag_names_by_post_id = + PostTag + .joins('INNER JOIN tags ON tags.id = post_tags.tag_id') + .joins('INNER JOIN tag_names ON tag_names.id = tags.tag_name_id') + .where(post_id: post_ids) + .where('post_tags.discarded_at IS NULL') + .where('tags.discarded_at IS NULL') + .where('tag_names.discarded_at IS NULL') + .pluck('post_tags.post_id', 'tag_names.name') + .each_with_object(Hash.new { |h, k| h[k] = [] }) do |(post_id, tag_name), h| + h[post_id] << tag_name + end + + rows = posts.map do |post| + tags = tag_names_by_post_id[post.id].uniq.sort.join(' ') + { post_id: post.id, + version_no: 1, + event_type: 'create', + title: post.title, + url: post.url, + thumbnail_base: post.thumbnail_base, + tags:, + parent_id: post.parent_id, + original_created_from: post.original_created_from, + original_created_before: post.original_created_before, + created_at: post.created_at, + created_by_user_id: post.uploaded_user_id } + end + + PostVersion.insert_all!(rows) if rows.present? end end end + + def down + drop_table :post_versions + end end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index 22541e2..34a6930 100644 --- a/backend/db/schema.rb +++ b/backend/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_03_29_034700) do +ActiveRecord::Schema[8.0].define(version: 2026_04_09_123700) do create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -132,6 +132,27 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_29_034700) do t.index ["tag_id"], name: "index_post_tags_on_tag_id" end + create_table "post_versions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "post_id", null: false + t.integer "version_no", null: false + t.string "event_type", null: false + t.string "title" + t.string "url", limit: 768, null: false + t.string "thumbnail_base", limit: 2000 + t.text "tags", null: false + t.bigint "parent_id" + t.datetime "original_created_from" + t.datetime "original_created_before" + t.datetime "created_at", null: false + t.bigint "created_by_user_id" + t.index ["created_by_user_id"], name: "index_post_versions_on_created_by_user_id" + t.index ["parent_id"], name: "index_post_versions_on_parent_id" + t.index ["post_id", "version_no"], name: "index_post_versions_on_post_id_and_version_no", unique: true + t.index ["post_id"], name: "index_post_versions_on_post_id" + t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')" + t.check_constraint "`version_no` > 0" + end + create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "title" t.string "url", limit: 768, null: false @@ -362,6 +383,9 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_29_034700) do add_foreign_key "post_tags", "tags" add_foreign_key "post_tags", "users", column: "created_user_id" add_foreign_key "post_tags", "users", column: "deleted_user_id" + add_foreign_key "post_versions", "posts" + add_foreign_key "post_versions", "posts", column: "parent_id" + add_foreign_key "post_versions", "users", column: "created_by_user_id" add_foreign_key "posts", "posts", column: "parent_id" add_foreign_key "posts", "users", column: "uploaded_user_id" add_foreign_key "settings", "users"