Merge branch 'main' into feature/142

このコミットが含まれているのは:
2025-12-11 22:15:40 +09:00
コミット be43164598
15個のファイルの変更238行の追加49行の削除
+55 -6
ファイルの表示
@@ -52,9 +52,12 @@ class PostsController < ApplicationController
viewed = current_user&.viewed?(post) || false
render json: (post
.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } })
.merge(related: post.related(limit: 20), viewed:))
json = post.as_json
json['tags'] = build_tag_tree_for(post.tags)
json['related'] = post.related(limit: 20)
json['viewed'] = viewed
render json:
end
# POST /posts
@@ -78,6 +81,7 @@ class PostsController < ApplicationController
if post.save
post.resized_thumbnail!
post.tags = Tag.normalise_tags(tag_names)
post.tags = Tag.expand_parent_tags(post.tags)
render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }),
status: :created
else
@@ -110,10 +114,13 @@ class PostsController < ApplicationController
original_created_before = params[:original_created_before]
post = Post.find(params[:id].to_i)
tags = post.tags.where(category: 'nico').to_a + Tag.normalise_tags(tag_names)
tags = post.tags.where(category: 'nico').to_a +
Tag.normalise_tags(tag_names, with_tagme: false)
tags = Tag.expand_parent_tags(tags)
if post.update(title:, tags:, original_created_from:, original_created_before:)
render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }),
status: :ok
json = post.as_json
json['tags'] = build_tag_tree_for(post.tags)
render json:, status: :ok
else
render json: post.errors, status: :unprocessable_entity
end
@@ -142,4 +149,46 @@ class PostsController < ApplicationController
end
posts.distinct
end
def build_tag_tree_for tags
tags = tags.to_a
tag_ids = tags.map(&:id)
implications = TagImplication.where(parent_tag_id: tag_ids, tag_id: tag_ids)
children_ids_by_parent = Hash.new { |h, k| h[k] = [] }
implications.each do |imp|
children_ids_by_parent[imp.parent_tag_id] << imp.tag_id
end
child_ids = children_ids_by_parent.values.flatten.uniq
root_ids = tag_ids - child_ids
tags_by_id = tags.index_by(&:id)
memo = { }
build_node = -> tag_id, path do
tag = tags_by_id[tag_id]
return nil unless tag
if path.include?(tag_id)
return tag.as_json(only: [:id, :name, :category, :post_count]).merge(children: [])
end
if memo.key?(tag_id)
return memo[tag_id]
end
new_path = path + [tag_id]
child_ids = children_ids_by_parent[tag_id] || []
children = child_ids.filter_map { |cid| build_node.(cid, new_path) }
memo[tag_id] = tag.as_json(only: [:id, :name, :category, :post_count]).merge(children:)
end
root_ids.filter_map { |id| build_node.call(id, []) }
end
end
+8 -5
ファイルの表示
@@ -6,12 +6,15 @@ class UsersController < ApplicationController
end
def verify
ip_bin = IPAddr.new(request.remote_ip).hton
ip_address = IpAddress.find_or_create_by!(ip_address: ip_bin)
user = User.find_by(inheritance_code: params[:code])
render json: if user
{ valid: true, user: user.slice(:id, :name, :inheritance_code, :role) }
else
{ valid: false }
end
return render json: { valid: false } unless user
UserIp.find_or_create_by!(user:, ip_address:)
render json: { valid: true, user: user.slice(:id, :name, :inheritance_code, :role) }
end
def renew
+32 -1
ファイルの表示
@@ -11,6 +11,14 @@ class Tag < ApplicationRecord
dependent: :destroy
has_many :linked_nico_tags, through: :reversed_nico_tag_relations, source: :nico_tag
has_many :tag_implications, foreign_key: :parent_tag_id, dependent: :destroy
has_many :children, through: :tag_implications, source: :tag
has_many :reversed_tag_implications, class_name: 'TagImplication',
foreign_key: :tag_id,
dependent: :destroy
has_many :parents, through: :reversed_tag_implications, source: :parent_tag
enum :category, { deerjikist: 'deerjikist',
meme: 'meme',
character: 'character',
@@ -57,10 +65,33 @@ class Tag < ApplicationRecord
end
end
end
tags << Tag.tagme if with_tagme && tags.size < 20 && tags.none?(Tag.tagme)
tags << Tag.tagme if with_tagme && tags.size < 10 && tags.none?(Tag.tagme)
tags.uniq
end
def self.expand_parent_tags tags
return [] if tags.blank?
seen = Set.new
result = []
stack = tags.compact.dup
until stack.empty?
tag = stack.pop
next unless tag
tag.parents.each do |parent|
next if seen.include?(parent.id)
seen << parent.id
result << parent
stack << parent
end
end
(result + tags).uniq { |t| t.id }
end
private
def nico_tag_name_must_start_with_nico
+17
ファイルの表示
@@ -0,0 +1,17 @@
class TagImplication < ApplicationRecord
belongs_to :tag, class_name: 'Tag'
belongs_to :parent_tag, class_name: 'Tag'
validates :tag_id, presence: true, uniqueness: { scope: :parent_tag_id }
validates :parent_tag_id, presence: true
validate :parent_tag_mustnt_be_itself
private
def parent_tag_mustnt_be_itself
if parent_tag == tag
errors.add :parent_tag_id, '親タグは子タグと同一であってはなりません.'
end
end
end
-1
ファイルの表示
@@ -8,7 +8,6 @@ class User < ApplicationRecord
has_many :posts
has_many :settings
has_many :ip_addresses
has_many :user_ips, dependent: :destroy
has_many :ip_addresses, through: :user_ips
has_many :user_post_views, dependent: :destroy
+9
ファイルの表示
@@ -0,0 +1,9 @@
class CreateTagImplications < ActiveRecord::Migration[8.0]
def change
create_table :tag_implications do |t|
t.references :tag, null: false, foreign_key: { to_table: :tags }
t.references :parent_tag, null: false, foreign_key: { to_table: :tags }
t.timestamps
end
end
end
+5
ファイルの表示
@@ -0,0 +1,5 @@
class RenameIpAdressColumnToIpAddresses < ActiveRecord::Migration[8.0]
def change
rename_column :ip_addresses, :ip_adress, :ip_address
end
end
+27
ファイルの表示
@@ -0,0 +1,27 @@
class AddUniqueIndexToTagImplications < ActiveRecord::Migration[8.0]
def up
execute <<~SQL
DELETE
ti1
FROM
tag_implications ti1
INNER JOIN
tag_implications ti2
ON
ti1.tag_id = ti2.tag_id
AND ti1.parent_tag_id = ti2.parent_tag_id
AND ti1.id > ti2.id
;
SQL
add_index :tag_implications, [:tag_id, :parent_tag_id],
unique: true,
name: 'index_tag_implications_on_tag_id_and_parent_tag_id'
end
def down
# NOTE: 重複削除は復元されなぃ.
remove_index :tag_implications,
name: 'index_tag_implications_on_tag_id_and_parent_tag_id'
end
end
生成ファイル
+21 -2
ファイルの表示
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do
ActiveRecord::Schema[8.0].define(version: 2025_12_10_123200) 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
@@ -40,7 +40,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do
end
create_table "ip_addresses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.binary "ip_adress", limit: 16, null: false
t.binary "ip_address", limit: 16, null: false
t.boolean "banned", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@@ -70,9 +70,16 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do
t.bigint "deleted_user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "discarded_at"
t.virtual "is_active", type: :boolean, as: "(`discarded_at` is null)", stored: true
t.virtual "active_unique_key", type: :string, as: "(case when (`discarded_at` is null) then concat(`post_id`,_utf8mb4':',`tag_id`) else NULL end)", stored: true
t.index ["active_unique_key"], name: "idx_post_tags_active_unique", unique: true
t.index ["created_user_id"], name: "index_post_tags_on_created_user_id"
t.index ["deleted_user_id"], name: "index_post_tags_on_deleted_user_id"
t.index ["discarded_at"], name: "index_post_tags_on_discarded_at"
t.index ["post_id", "discarded_at"], name: "index_post_tags_on_post_id_and_discarded_at"
t.index ["post_id"], name: "index_post_tags_on_post_id"
t.index ["tag_id", "discarded_at"], name: "index_post_tags_on_tag_id_and_discarded_at"
t.index ["tag_id"], name: "index_post_tags_on_tag_id"
end
@@ -107,6 +114,16 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do
t.index ["tag_id"], name: "index_tag_aliases_on_tag_id"
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
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["parent_tag_id"], name: "index_tag_implications_on_parent_tag_id"
t.index ["tag_id", "parent_tag_id"], name: "index_tag_implications_on_tag_id_and_parent_tag_id", unique: true
t.index ["tag_id"], name: "index_tag_implications_on_tag_id"
end
create_table "tag_similarities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "tag_id", null: false
t.bigint "target_tag_id", null: false
@@ -176,6 +193,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do
add_foreign_key "posts", "users", column: "uploaded_user_id"
add_foreign_key "settings", "users"
add_foreign_key "tag_aliases", "tags"
add_foreign_key "tag_implications", "tags"
add_foreign_key "tag_implications", "tags", column: "parent_tag_id"
add_foreign_key "tag_similarities", "tags"
add_foreign_key "tag_similarities", "tags", column: "target_tag_id"
add_foreign_key "user_ips", "ip_addresses"
-12
ファイルの表示
@@ -1,12 +0,0 @@
namespace :nico do
desc 'ニコタグ連携'
task link: :environment do
Post.find_each do |post|
tags = post.tags.where(category: 'nico')
tags.each do |tag|
post.tags.concat(tag.linked_tags) if tag.linked_tags.present?
end
post.tags = post.tags.to_a.uniq
end
end
end
+1 -1
ファイルの表示
@@ -51,7 +51,7 @@ namespace :nico do
end
tags_to_add.concat([tag] + tag.linked_tags)
end
tags_to_add << Tag.tagme if post.tags.size < 20
tags_to_add << Tag.tagme if post.tags.size < 10
tags_to_add << Tag.bot
post.tags = (post.tags + tags_to_add).uniq
end