diff --git a/backend/db/migrate/20261102000000_create_events.rb b/backend/db/migrate/20261102000000_create_events.rb new file mode 100644 index 0000000..38c35c4 --- /dev/null +++ b/backend/db/migrate/20261102000000_create_events.rb @@ -0,0 +1,19 @@ +class CreateEvents < ActiveRecord::Migration[8.0] + def change + create_table :events do |t| + t.string :event_type, null: false, index: true + t.text :reason + t.references :reverted_from_event, foreign_key: { to_table: :events } + t.json :metadata + t.binary :ip_address, limit: 16, index: true + t.references :created_by_user, index: true, foreign_key: false + t.datetime :created_at, null: false, index: true + end + + add_reference :post_versions, :event, foreign_key: true, after: :original_created_before + add_reference :tag_versions, :event, foreign_key: true, after: :deprecated_at + add_reference :material_versions, :event, foreign_key: true + add_reference :tag_description_versions, :event, foreign_key: true, after: :source_version_no + add_reference :tag_group_versions, :event, foreign_key: true, after: :placements + end +end diff --git a/backend/db/migrate/20261102010000_backfill_events_from_existing_versions.rb b/backend/db/migrate/20261102010000_backfill_events_from_existing_versions.rb new file mode 100644 index 0000000..fa18024 --- /dev/null +++ b/backend/db/migrate/20261102010000_backfill_events_from_existing_versions.rb @@ -0,0 +1,207 @@ +class BackfillEventsFromExistingVersions < ActiveRecord::Migration[8.0] + class MigrationEvent < ActiveRecord::Base + self.table_name = 'events' + end + + class MigrationPostVersion < ActiveRecord::Base + self.table_name = 'post_versions' + end + + class MigrationTagVersion < ActiveRecord::Base + self.table_name = 'tag_versions' + end + + class MigrationMaterialVersion < ActiveRecord::Base + self.table_name = 'material_versions' + end + + class MigrationTagDescriptionVersion < ActiveRecord::Base + self.table_name = 'tag_description_versions' + end + + class MigrationTagGroupVersion < ActiveRecord::Base + self.table_name = 'tag_group_versions' + end + + class MigrationLocale < ActiveRecord::Base + self.table_name = 'locales' + end + + def up + # create → posts.create + # update → posts.update + raise if MigrationPostVersion.where.not(event_type: ['create', 'update']).exists? + + # create → materials.create + # update → materials.update + if MigrationMaterialVersion.where.not(event_type: ['create', 'update']).exists? + raise + end + + # create → posts.create / posts.update / materials.create / materials.update / unknown + # update → tags.update / locales.create + # discard → unknown + if MigrationTagVersion.where.not(event_type: ['create', 'update', 'discard']).exists? + raise + end + + # create → tag_descriptions.create + # update → tag_descriptions.update + if MigrationTagDescriptionVersion.where.not(event_type: ['create', 'update']).exists? + raise + end + + # create → tag_groups.create + # update → tag_groups.update + if MigrationTagGroupVersion.where.not(event_type: ['create', 'update']).exists? + raise + end + + # post_versions の Backfill + MigrationPostVersion.find_each do |pv| + ev = MigrationEvent.create!(event_type: "posts.#{ pv.event_type }", + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: pv.created_by_user_id, + created_at: pv.created_at) + pv.update_columns(event_id: ev.id) + end + + # material_versions の Backfill + MigrationMaterialVersion.find_each do |m| + ev = MigrationEvent.create!(event_type: "materials.#{ m.event_type }", + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: m.created_by_user_id, + created_at: m.created_at) + m.update_columns(event_id: ev.id) + end + + # ロカール追加に伴ふ該当語言のタグ名称追加異動のみ分離 + ids_cause_of_locale_creation = [] + MigrationLocale.where('created_at > ?', MigrationLocale.minimum(:created_at)).find_each do |l| + ev = MigrationEvent.create!(event_type: 'locales.create', + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: nil, + created_at: l.created_at) + + MigrationTagVersion + .where('created_at > ?', l.created_at) + .where(event_type: 'update', event_id: nil) + .find_each do |t| + next unless t.names.key?(l.language_code) + + prev = MigrationTagVersion.find_by(tag_id: t.tag_id, version_no: t.version_no - 1) + next if prev.nil? || prev.names.key?(l.language_code) + + t.update_columns(event_id: ev.id) + + ids_cause_of_locale_creation << t.id + end + + ev.destroy! unless MigrationTagVersion.where(event_id: ev.id).exists? + end + + # tag_versions の Backfill + MigrationTagVersion.where.not(id: ids_cause_of_locale_creation).find_each do |t| + ev = + case t.event_type + when 'create' + post = + MigrationPostVersion + .where('JSON_CONTAINS(tags_json, ?) <> 0', { tag_id: t.tag_id }.to_json) + .find_by(created_at: MigrationPostVersion + .where('JSON_CONTAINS(tags_json, ?) <> 0', + { tag_id: t.tag_id }.to_json) + .minimum(:created_at)) + material = + MigrationMaterialVersion + .where(tag_id: t.tag_id) + .find_by(created_at: MigrationMaterialVersion + .where(tag_id: t.tag_id) + .minimum(:created_at)) + if post + if material + if post.created_at < material.created_at + MigrationEvent.find(post.event_id) + else + MigrationEvent.find(material.event_id) + end + else + MigrationEvent.find(post.event_id) + end + else + if material + MigrationEvent.find(material.event_id) + else + MigrationEvent.create!(event_type: 'unknown', + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: t.created_by_user_id, + created_at: t.created_at) + end + end + when 'update' + MigrationEvent.create!(event_type: 'tags.update', + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: t.created_by_user_id, + created_at: t.created_at) + when 'discard' + MigrationEvent.create!(event_type: 'unknown', + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: t.created_by_user_id, + created_at: t.created_at) + end + t.update_columns(event_id: ev.id) + end + + # tag_description_versions の Backfill + MigrationTagDescriptionVersion.find_each do |td| + ev = MigrationEvent.create!(event_type: "tag_descriptions.#{ td.event_type }", + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: td.created_by_user_id, + created_at: td.created_at) + td.update_columns(event_id: ev.id) + end + + # tag_group_versions の Backfill + MigrationTagGroupVersion.find_each do |tg| + ev = MigrationEvent.create!(event_type: "tag_groups.#{ tg.event_type }", + reason: nil, + reverted_from_event_id: nil, + metadata: nil, + ip_address: nil, + created_by_user_id: tg.created_by_user_id, + created_at: tg.created_at) + tg.update_columns(event_id: ev.id) + end + + change_column_null :post_versions, :event_id, false + change_column_null :tag_versions, :event_id, false + change_column_null :material_versions, :event_id, false + change_column_null :tag_description_versions, :event_id, false + change_column_null :tag_group_versions, :event_id, false + end + + def down + raise ActiveRecord::IrreversibleMigration, '戻せません.' + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index bf72236..6d307ea 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_10_15_010000) do +ActiveRecord::Schema[8.0].define(version: 2026_11_02_010000) 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 @@ -48,6 +48,21 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do t.index ["tag_id"], name: "index_deerjikists_on_tag_id" end + create_table "events", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.string "event_type", null: false + t.text "reason" + t.bigint "reverted_from_event_id" + t.json "metadata" + t.binary "ip_address", limit: 16 + t.bigint "created_by_user_id" + t.datetime "created_at", null: false + t.index ["created_at"], name: "index_events_on_created_at" + t.index ["created_by_user_id"], name: "index_events_on_created_by_user_id" + t.index ["event_type"], name: "index_events_on_event_type" + t.index ["ip_address"], name: "index_events_on_ip_address" + t.index ["reverted_from_event_id"], name: "index_events_on_reverted_from_event_id" + end + create_table "external_tags", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "platform", limit: 16, null: false t.string "name", null: false @@ -250,8 +265,10 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do t.string "source_path" t.string "source_file_id" t.string "normalized_source_key" + t.bigint "event_id", null: false t.index ["created_by_user_id"], name: "index_material_versions_on_created_by_user_id" t.index ["discarded_at"], name: "index_material_versions_on_discarded_at" + t.index ["event_id"], name: "index_material_versions_on_event_id" t.index ["file_blob_id"], name: "index_material_versions_on_file_blob_id" t.index ["material_id", "version_no"], name: "index_material_versions_on_material_id_and_version_no", unique: true t.index ["material_id"], name: "index_material_versions_on_material_id" @@ -380,10 +397,12 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do t.text "parent_post_ids", null: false t.datetime "original_created_from" t.datetime "original_created_before" + t.bigint "event_id", null: false t.datetime "created_at", null: false t.bigint "created_by_user_id" t.integer "video_ms" t.index ["created_by_user_id"], name: "index_post_versions_on_created_by_user_id" + t.index ["event_id"], name: "index_post_versions_on_event_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.index ["video_ms", "post_id"], name: "idx_post_versions_video_ms_post_id" @@ -448,9 +467,11 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do t.text "body", null: false t.string "source_language_code", limit: 16 t.integer "source_version_no" + t.bigint "event_id", null: false t.bigint "created_by_user_id" t.datetime "created_at", null: false t.index ["created_by_user_id"], name: "index_tag_description_versions_on_created_by_user_id" + t.index ["event_id"], name: "index_tag_description_versions_on_event_id" t.index ["tag_id", "language_code", "version_no"], name: "idx_on_tag_id_language_code_version_no_ced77e57e6", unique: true t.index ["tag_id", "source_language_code", "source_version_no"], name: "fk_rails_4f13094dbc" t.index ["tag_id"], name: "index_tag_description_versions_on_tag_id" @@ -533,9 +554,11 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do t.text "description" t.json "members", null: false t.json "placements", null: false + t.bigint "event_id", null: false t.bigint "created_by_user_id" t.datetime "created_at", null: false t.index ["created_by_user_id"], name: "index_tag_group_versions_on_created_by_user_id" + t.index ["event_id"], name: "index_tag_group_versions_on_event_id" t.index ["tag_group_id", "version_no"], name: "index_tag_group_versions_on_tag_group_id_and_version_no", unique: true t.index ["tag_group_id"], name: "index_tag_group_versions_on_tag_group_id" t.check_constraint "`version_no` > 0", name: "chk_tag_group_versions_version_no_positive" @@ -611,11 +634,13 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do t.text "aliases", null: false t.text "parent_tag_ids", null: false t.datetime "deprecated_at" + t.bigint "event_id", null: false t.datetime "created_at", null: false t.bigint "created_by_user_id" t.json "names", null: false t.index ["created_at"], name: "index_tag_versions_on_created_at" t.index ["created_by_user_id", "created_at"], name: "index_tag_versions_on_created_by_user_id_and_created_at", order: { created_at: :desc } + t.index ["event_id"], name: "index_tag_versions_on_event_id" t.index ["tag_id", "created_at"], name: "index_tag_versions_on_tag_id_and_created_at", order: { created_at: :desc } t.index ["tag_id", "version_no"], name: "index_tag_versions_on_tag_id_and_version_no", unique: true t.check_constraint "`version_no` > 0", name: "tag_versions_version_no_positive" @@ -771,6 +796,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "events", "events", column: "reverted_from_event_id" add_foreign_key "gekanator_ai_runs", "gekanator_question_suggestions" add_foreign_key "gekanator_games", "posts", column: "correct_post_id" add_foreign_key "gekanator_games", "posts", column: "guessed_post_id" @@ -792,6 +818,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do add_foreign_key "material_sync_sources", "users", column: "created_by_user_id" add_foreign_key "material_sync_sources", "users", column: "updated_by_user_id" add_foreign_key "material_sync_suppressions", "users", column: "created_by_user_id" + add_foreign_key "material_versions", "events" add_foreign_key "material_versions", "materials" add_foreign_key "material_versions", "materials", column: "parent_id" add_foreign_key "material_versions", "users", column: "created_by_user_id" @@ -813,10 +840,12 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do add_foreign_key "post_tags", "posts" add_foreign_key "post_tags", "tags" add_foreign_key "post_tags", "users", column: "created_user_id" + add_foreign_key "post_versions", "events" add_foreign_key "post_versions", "posts" add_foreign_key "post_versions", "users", column: "created_by_user_id" add_foreign_key "posts", "users", column: "uploaded_user_id" add_foreign_key "settings", "users" + add_foreign_key "tag_description_versions", "events" add_foreign_key "tag_description_versions", "tag_description_versions", column: ["tag_id", "source_language_code", "source_version_no"], primary_key: ["tag_id", "language_code", "version_no"] add_foreign_key "tag_descriptions", "languages", column: "language_code", primary_key: "code" add_foreign_key "tag_descriptions", "tag_description_versions", column: ["tag_id", "source_language_code", "source_version_no"], primary_key: ["tag_id", "language_code", "version_no"] @@ -835,6 +864,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do add_foreign_key "tag_group_tags", "tag_groups" add_foreign_key "tag_group_tags", "tags" add_foreign_key "tag_group_tags", "users", column: "created_by_user_id" + add_foreign_key "tag_group_versions", "events" add_foreign_key "tag_groups", "users", column: "created_by_user_id" add_foreign_key "tag_groups", "users", column: "updated_by_user_id" add_foreign_key "tag_implications", "tags" @@ -844,6 +874,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_10_15_010000) do add_foreign_key "tag_names", "tags" add_foreign_key "tag_similarities", "tags" add_foreign_key "tag_similarities", "tags", column: "target_tag_id" + add_foreign_key "tag_versions", "events" add_foreign_key "tag_versions", "users", column: "created_by_user_id" add_foreign_key "theatre_comments", "theatres" add_foreign_key "theatre_comments", "users"