From 11eedc242d455338c29bcc4ed3fa2cdccfcda99f Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 4 Oct 2026 13:45:23 +0900 Subject: [PATCH] #422 --- .../tag_descriptions_controller.rb | 0 .../app/controllers/wiki_pages_controller.rb | 120 ++++++++++++++++-- backend/app/models/tag_description.rb | 3 + backend/app/models/tag_description_version.rb | 3 + backend/app/models/wiki_page.rb | 11 +- backend/config/routes.rb | 2 +- .../20261003000000_create_tag_descriptions.rb | 63 +++++++++ ...igrate_tag_descriptions_from_wiki_pages.rb | 104 +++++++++++++++ backend/db/schema.rb | 67 +++++++--- 9 files changed, 343 insertions(+), 30 deletions(-) create mode 100644 backend/app/controllers/tag_descriptions_controller.rb create mode 100644 backend/app/models/tag_description.rb create mode 100644 backend/app/models/tag_description_version.rb create mode 100644 backend/db/migrate/20261003000000_create_tag_descriptions.rb create mode 100644 backend/db/migrate/20261003010000_migrate_tag_descriptions_from_wiki_pages.rb diff --git a/backend/app/controllers/tag_descriptions_controller.rb b/backend/app/controllers/tag_descriptions_controller.rb new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/controllers/wiki_pages_controller.rb b/backend/app/controllers/wiki_pages_controller.rb index b0d0b8c..9a0c310 100644 --- a/backend/app/controllers/wiki_pages_controller.rb +++ b/backend/app/controllers/wiki_pages_controller.rb @@ -2,21 +2,129 @@ class WikiPagesController < ApplicationController rescue_from Wiki::Commit::Conflict, with: :render_wiki_conflict def index + # 互換性のための化石リソース + # TODO: ヘルプおよびタグ・グループ追加後に, + # TODO: タグ説明とヘルプ、タグ・グループから取得する作りに変更する. + title = params[:title].to_s.strip if title.blank? - return render json: WikiPageRepr.base( - WikiPage.joins(:tag_name).includes(tag_name: :tag)) + tag_descriptions = TagDescription.where(language_code: :ja).map do |td| + { id: td.id, + tag_name_id: td.tag.tag_name(:ja).id, + body: td.body, + created_user_id: td.created_by_user_id, + updated_user_id: td.updated_by_user_id, + created_at: td.created_at, + updated_at: td.updated_at, + discarded_at: nil, + version_no: td.version_no, + title: td.tag.name(:ja), + deprecated_at: td.tag.deprecated_at } + end + # TODO: ヘルプ移行したらここに追記 + # TODO: タグ・グループ追加したらここに追記 + + # TODO: ヘルプ、タグ・グループ移行したらそれらもタイトルから除外に含める. + # てかこれ何順? + wiki_pages = + WikiPageRepr.base(WikiPage.joins(:tag_name).includes(tag_name: :tag)) + .reject { |wp| tag_descriptions.any? { |td| td.title == wp.title } } + + json = tag_descriptions + wiki_pages + + return render json: end - q = WikiPage.joins(:tag_name).includes(tag_name: :tag) + q = + TagDescription.joins(tag: :tag_names) + .where(language_code: :ja) + .where(tag: { tag_names: { language_code: :ja, primary_flg: true } }) + .where('tag_names.name LIKE ?', "%#{ ApplicationRecord.sanitize_sql_like(title) }%") + tag_descriptions = q.limit(20).map do |td| + { id: td.id, + tag_name_id: td.tag.tag_name(:ja).id, + body: td.body, + created_user_id: td.created_by_user_id, + updated_user_id: td.updated_by_user_id, + created_at: td.created_at, + updated_at: td.updated_at, + discarded_at: nil, + version_no: td.version_no, + title: td.tag.name(:ja), + deprecated_at: td.tag.deprecated_at } + end + + q = + WikiPage.joins(:tag_name).includes(tag_name: :tag) .where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%") - render json: WikiPageRepr.base(q.limit(20)) + # TODO: ヘルプ、タグ・グループ移行したらそれらもタイトルから除外に含める. + wiki_pages = + WikiPageRepr.base(q.limit(20)) + .reject { |wp| tag_descriptions.any? { |td| td.title == wp.title } } + + json = (tag_descriptions + wiki_pages)[...20] + + render json: end def show + tag_description = TagDescription.find_by(id: params[:id]) + + if tag_description + rev = + if params[:version].present? + TagDescriptionVersion.find_by(id: params[:version]) + else + TagDescriptionVersion.find_by(tag_id: tag_description.tag_id, + language_code: :ja, + version_no: tag_description.version_no) + end + + if rev + body = + if rev.version_no == tag_description.version_no + tag_description.body + else + rev.body + end + revision_id = rev.id + pred = TagDescriptionVersion.find_by(tag_id: tag_description.tag_id, + language_code: :ja, + version_no: tag_description.version_no - 1)&.id + succ = TagDescriptionVersion.find_by(tag_id: tag_description.tag_id, + language_code: :ja, + version_no: tag_description.version_no + 1)&.id + updated_at = + if rev.version_no == tag_description.version_no + tag_description.updated_at + else + rev.created_at + end + + json = + { id: tag_description.id, + tag_name_id: tag_description.tag.tag_name(:ja).id, + body:, + created_user_id: tag_description.created_by_user_id, + updated_user_id: tag_description.updated_by_user_id, + created_at: tag_description.created_at, + updated_at:, + discarded_at: nil, + version_no: tag_description.version_no, + title: tag_description.tag.name(:ja), + deprecated_at: tag_description.tag.deprecated_at, + revision_id:, + pred:, + succ: } + + return render json: + end + end + page = WikiPage.joins(:tag_name) .includes(tag_name: :tag) .find_by(id: params[:id]) + render_wiki_page_or_404 page end @@ -156,10 +264,6 @@ class WikiPagesController < ApplicationController head :ok end - def search - index - end - def changes id = params[:id].presence q = WikiRevision.joins(wiki_page: :tag_name) diff --git a/backend/app/models/tag_description.rb b/backend/app/models/tag_description.rb new file mode 100644 index 0000000..2c5e226 --- /dev/null +++ b/backend/app/models/tag_description.rb @@ -0,0 +1,3 @@ +class TagDescription < ApplicationRecord + belongs_to :tag +end diff --git a/backend/app/models/tag_description_version.rb b/backend/app/models/tag_description_version.rb new file mode 100644 index 0000000..69f31ac --- /dev/null +++ b/backend/app/models/tag_description_version.rb @@ -0,0 +1,3 @@ +class TagDescriptionVersion < ApplicationRecord + ; +end diff --git a/backend/app/models/wiki_page.rb b/backend/app/models/wiki_page.rb index 68928f1..caee630 100644 --- a/backend/app/models/wiki_page.rb +++ b/backend/app/models/wiki_page.rb @@ -48,8 +48,11 @@ class WikiPage < ApplicationRecord page end - def pred_revision_id(revision_id) = - wiki_revisions.where('id < ?', revision_id).order(id: :desc).limit(1).pick(:id) - def succ_revision_id(revision_id) = - wiki_revisions.where('id > ?', revision_id).order(id: :asc).limit(1).pick(:id) + def pred_revision_id revision_id + wiki_revisions.where('id < ?', revision_id).order(id: :desc).limit(1).pick(:id) + end + + def succ_revision_id revision_id + wiki_revisions.where('id > ?', revision_id).order(id: :asc).limit(1).pick(:id) + end end diff --git a/backend/config/routes.rb b/backend/config/routes.rb index 0f721d1..e44f5b5 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -35,7 +35,7 @@ Rails.application.routes.draw do resources :wiki_pages, path: 'wiki', only: [:index, :show, :create, :update] do collection do - get :search + get :search, to: redirect('/') get :changes scope :title do diff --git a/backend/db/migrate/20261003000000_create_tag_descriptions.rb b/backend/db/migrate/20261003000000_create_tag_descriptions.rb new file mode 100644 index 0000000..961a4ad --- /dev/null +++ b/backend/db/migrate/20261003000000_create_tag_descriptions.rb @@ -0,0 +1,63 @@ +class CreateTagDescriptions < ActiveRecord::Migration[8.0] + def change + create_table :tag_description_versions do |t| + t.references :tag, null: false, foreign_key: false + t.string :language_code, limit: 16, null: false + t.integer :version_no, null: false + t.string :event_type, null: false + t.text :body, null: false + t.string :source_language_code, limit: 16 + t.integer :source_version_no + t.references :created_by_user + t.datetime :created_at, null: false + + t.index [:tag_id, :language_code, :version_no], unique: true + t.foreign_key :tag_description_versions, + column: [:tag_id, :source_language_code, :source_version_no], + primary_key: [:tag_id, :language_code, :version_no] + + t.check_constraint 'version_no > 0', + name: 'chk_tag_description_versions_version_no_positive' + + t.check_constraint "event_type IN ('create', 'update', 'discard', 'restore')", + name: 'tag_description_versions_event_type_valid' + + t.check_constraint ('(source_language_code IS NULL AND source_version_no IS NULL) OR ' + + '(source_language_code IS NOT NULL AND source_version_no IS NOT NULL)'), + name: 'chk_tag_description_versions_source_pair' + + t.check_constraint 'source_language_code IS NULL OR source_language_code <> language_code', + name: 'chk_tag_description_versions_source_language_code_different' + end + + create_table :tag_descriptions do |t| + t.references :tag, null: false, foreign_key: true + t.string :language_code, limit: 16, null: false, index: true + t.integer :version_no, null: false + t.text :body, null: false + t.string :source_language_code, limit: 16 + t.integer :source_version_no + t.references :created_by_user, foreign_key: { to_table: :users } + t.references :updated_by_user, foreign_key: { to_table: :users } + t.timestamps + + t.foreign_key :languages, column: :language_code, primary_key: :code + t.foreign_key :tag_description_versions, + column: [:tag_id, :source_language_code, :source_version_no], + primary_key: [:tag_id, :language_code, :version_no] + + t.index [:tag_id, :language_code], unique: true + t.index [:source_language_code, :source_version_no] + + t.check_constraint 'version_no > 0', + name: 'chk_tag_descriptions_version_no_positive' + + t.check_constraint ('(source_language_code IS NULL AND source_version_no IS NULL) OR ' + + '(source_language_code IS NOT NULL AND source_version_no IS NOT NULL)'), + name: 'chk_tag_descriptions_source_pair' + + t.check_constraint 'source_language_code IS NULL OR source_language_code <> language_code', + name: 'chk_tag_descriptions_source_language_code_different' + end + end +end diff --git a/backend/db/migrate/20261003010000_migrate_tag_descriptions_from_wiki_pages.rb b/backend/db/migrate/20261003010000_migrate_tag_descriptions_from_wiki_pages.rb new file mode 100644 index 0000000..4d51db9 --- /dev/null +++ b/backend/db/migrate/20261003010000_migrate_tag_descriptions_from_wiki_pages.rb @@ -0,0 +1,104 @@ +class MigrateTagDescriptionsFromWikiPages < ActiveRecord::Migration[8.0] + class WikiPage < ActiveRecord::Base + self.table_name = 'wiki_pages' + end + + class WikiRevision < ActiveRecord::Base + self.table_name = 'wiki_revisions' + end + + class WikiRevisionLine < ActiveRecord::Base + self.table_name = 'wiki_revision_lines' + end + + class TagName < ActiveRecord::Base + self.table_name = 'tag_names' + end + + class TagDescription < ActiveRecord::Base + self.table_name = 'tag_descriptions' + end + + class TagDescriptionVersion < ActiveRecord::Base + self.table_name = 'tag_description_versions' + end + + def up + WikiPage.find_each do |page| + tag_name = TagName.find_by(id: page.tag_name_id, language_code: 'ja') + next unless tag_name&.tag_id + + revisions = WikiRevision.where(wiki_page_id: page.id).order(:id).to_a + + revisions.each_with_index do |revision, index| + version_no = index + 1 + + tag_id = tag_name[:tag_id] + + unless tag_id + raise ( + 'tag_id is nil: ' + + "wiki_page_id=#{ page.id }, " + + "tag_name_id=#{ tag_name.id }, " + + "tag_name=#{ tag_name.attributes.inspect}") + end + + revision_id = revision.id + + say ( + "wiki_page_id=#{ page.id }, " + + "tag_name_id=#{ tag_name.id }, " + + "tag_id=#{ tag_id }, " + + "revision_id=#{ revision_id }") + + version = TagDescriptionVersion.new( + id: revision_id, + tag_id:, + language_code: 'ja', + version_no:, + event_type: version_no == 1 ? 'create' : 'update', + body: revision_body(revision_id), + source_language_code: nil, + source_version_no: nil, + created_by_user_id: revision.created_user_id, + created_at: revision.created_at) + + say version.attributes.inspect + + version.save! + end + + next if page.discarded_at.present? + next if revisions.empty? + + TagDescription.create!( + id: page.id, + tag_id: tag_name.tag_id, + language_code: 'ja', + version_no: revisions.length, + body: page.body, + source_language_code: nil, + source_version_no: nil, + created_by_user_id: page.created_user_id, + updated_by_user_id: page.updated_user_id, + created_at: page.created_at, + updated_at: page.updated_at) + end + end + + def down + raise ActiveRecord::IrreversibleMigration, '戻せません.' + end + + private + + def revision_body revision_id + WikiRevisionLine + .joins('INNER JOIN wiki_lines ' + + 'ON wiki_lines.id = wiki_revision_lines.wiki_line_id') + .where(wiki_revision_id: revision_id) + .order(:position) + .pluck('wiki_lines.body') + .join("\n") + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index eee4568..914edcb 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_09_22_040000) do +ActiveRecord::Schema[8.0].define(version: 2026_10_03_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 @@ -440,6 +440,49 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_040000) do t.index ["user_id"], name: "index_settings_on_user_id", unique: true end + create_table "tag_description_versions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "tag_id", null: false + t.string "language_code", limit: 16, null: false + t.integer "version_no", null: false + t.string "event_type", null: false + t.text "body", null: false + t.string "source_language_code", limit: 16 + t.integer "source_version_no" + 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 ["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" + t.check_constraint "((`source_language_code` is null) and (`source_version_no` is null)) or ((`source_language_code` is not null) and (`source_version_no` is not null))", name: "chk_tag_description_versions_source_pair" + t.check_constraint "(`source_language_code` is null) or (`source_language_code` <> `language_code`)", name: "chk_tag_description_versions_source_language_code_different" + t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')", name: "tag_description_versions_event_type_valid" + t.check_constraint "`version_no` > 0", name: "chk_tag_description_versions_version_no_positive" + end + + create_table "tag_descriptions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "tag_id", null: false + t.string "language_code", limit: 16, null: false + t.integer "version_no", null: false + t.text "body", null: false + t.string "source_language_code", limit: 16 + t.integer "source_version_no" + t.bigint "created_by_user_id" + t.bigint "updated_by_user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["created_by_user_id"], name: "index_tag_descriptions_on_created_by_user_id" + t.index ["language_code"], name: "index_tag_descriptions_on_language_code" + t.index ["source_language_code", "source_version_no"], name: "idx_on_source_language_code_source_version_no_00117fe02a" + t.index ["tag_id", "language_code"], name: "index_tag_descriptions_on_tag_id_and_language_code", unique: true + t.index ["tag_id", "source_language_code", "source_version_no"], name: "fk_rails_d1be0a7696" + t.index ["tag_id"], name: "index_tag_descriptions_on_tag_id" + t.index ["updated_by_user_id"], name: "index_tag_descriptions_on_updated_by_user_id" + t.check_constraint "((`source_language_code` is null) and (`source_version_no` is null)) or ((`source_language_code` is not null) and (`source_version_no` is not null))", name: "chk_tag_descriptions_source_pair" + t.check_constraint "(`source_language_code` is null) or (`source_language_code` <> `language_code`)", name: "chk_tag_descriptions_source_language_code_different" + t.check_constraint "`version_no` > 0", name: "chk_tag_descriptions_version_no_positive" + end + create_table "tag_implications", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "tag_id", null: false t.bigint "parent_tag_id", null: false @@ -656,19 +699,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_040000) do t.index ["banned_at"], name: "index_users_on_banned_at" end - create_table "wiki_assets", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| - t.bigint "wiki_page_id", null: false - t.integer "no", null: false - t.string "alt_text" - t.binary "sha256", limit: 32, null: false - t.bigint "created_by_user_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["created_by_user_id"], name: "index_wiki_assets_on_created_by_user_id" - t.index ["wiki_page_id", "no"], name: "index_wiki_assets_on_wiki_page_id_and_no", unique: true - t.index ["wiki_page_id", "sha256"], name: "index_wiki_assets_on_wiki_page_id_and_sha256", unique: true - end - create_table "wiki_lines", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "sha256", limit: 64, null: false t.text "body", null: false @@ -685,7 +715,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_040000) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.datetime "discarded_at" - t.integer "next_asset_no", default: 1, null: false t.integer "version_no", null: false t.index ["created_user_id"], name: "index_wiki_pages_on_created_user_id" t.index ["discarded_at"], name: "index_wiki_pages_on_discarded_at" @@ -788,6 +817,12 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_040000) do 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", "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"] + add_foreign_key "tag_descriptions", "tags" + add_foreign_key "tag_descriptions", "users", column: "created_by_user_id" + add_foreign_key "tag_descriptions", "users", column: "updated_by_user_id" add_foreign_key "tag_implications", "tags" add_foreign_key "tag_implications", "tags", column: "parent_tag_id" add_foreign_key "tag_names", "languages", column: "language_code", primary_key: "code" @@ -822,8 +857,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_22_040000) do add_foreign_key "user_post_views", "posts" add_foreign_key "user_post_views", "users" add_foreign_key "user_theme_slots", "users" - add_foreign_key "wiki_assets", "users", column: "created_by_user_id" - add_foreign_key "wiki_assets", "wiki_pages" add_foreign_key "wiki_pages", "tag_names" add_foreign_key "wiki_pages", "users", column: "created_user_id" add_foreign_key "wiki_pages", "users", column: "updated_user_id"