コミットを比較
7
コミット
| 作成者 | SHA1 | 日付 | |
|---|---|---|---|
|
|
115a14205b | ||
|
|
1b1130ccd9 | ||
|
|
f6d3dd1c10 | ||
|
|
b0df11c500 | ||
|
|
886128a7a8 | ||
|
|
6d1a8f0444 | ||
|
|
6f0442d633 |
@@ -92,4 +92,6 @@ class ApplicationController < ActionController::API
|
|||||||
|
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def resolve_locale! = Locale.find_by(code: params[:locale]) || Locale.nipponese
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class MaterialsController < ApplicationController
|
|||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.gte_member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
|
locale = resolve_locale!
|
||||||
tag_name_raw = params[:tag].to_s.strip
|
tag_name_raw = params[:tag].to_s.strip
|
||||||
file = params[:file]
|
file = params[:file]
|
||||||
file_sha256 = MaterialFileSha256.from_upload(file)
|
file_sha256 = MaterialFileSha256.from_upload(file)
|
||||||
@@ -89,7 +90,7 @@ class MaterialsController < ApplicationController
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
Material.transaction do
|
Material.transaction do
|
||||||
tag = resolve_material_tag!(tag_name_raw)
|
tag = resolve_material_tag!(locale, tag_name_raw)
|
||||||
material = Material.new(tag:, url:,
|
material = Material.new(tag:, url:,
|
||||||
created_by_user: current_user,
|
created_by_user: current_user,
|
||||||
updated_by_user: current_user)
|
updated_by_user: current_user)
|
||||||
@@ -236,10 +237,8 @@ class MaterialsController < ApplicationController
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_material_tag! tag_name_raw
|
def resolve_material_tag! locale, tag_name_raw
|
||||||
tag_name = TagName.find_or_create_by!(name: tag_name_raw)
|
Tag.find_or_create_by_tag_name!(locale, tag_name_raw, category: :material)
|
||||||
tag = tag_name.tag
|
|
||||||
tag || Tag.create!(tag_name:, category: :material)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def material_index_needs_tag_name? filters
|
def material_index_needs_tag_name? filters
|
||||||
|
|||||||
@@ -15,22 +15,23 @@ class NicoTagsController < ApplicationController
|
|||||||
limit = 1 if limit < 1
|
limit = 1 if limit < 1
|
||||||
|
|
||||||
post_tag_max_sql =
|
post_tag_max_sql =
|
||||||
PostExternalTag
|
PostTag
|
||||||
.select('external_tag_id, MAX(created_at) AS max_created_at')
|
.select('tag_id, MAX(created_at) AS max_created_at')
|
||||||
.group('external_tag_id')
|
.group('tag_id')
|
||||||
.to_sql
|
.to_sql
|
||||||
|
|
||||||
q =
|
q = Tag.nico_tags
|
||||||
ExternalTag
|
.joins(:tag_name)
|
||||||
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
||||||
'ON post_tag_max.external_tag_id = external_tags.id')
|
'ON post_tag_max.tag_id = tags.id')
|
||||||
q = q.where('external_tags.name LIKE ?', "%#{ name }%") if name
|
.includes(:tag_name, tag_name: :wiki_page, linked_tags: { tag_name: :wiki_page })
|
||||||
|
q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name
|
||||||
if linked_tag
|
if linked_tag
|
||||||
linked_tag_ids =
|
linked_tag_ids =
|
||||||
Tag
|
Tag
|
||||||
.joins(:tag_name)
|
.joins(:tag_name)
|
||||||
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
||||||
.pluck(:id)
|
.pluck(:id)
|
||||||
linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
|
linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
|
||||||
q = q.where(id: linked_nico_tag_ids)
|
q = q.where(id: linked_nico_tag_ids)
|
||||||
end
|
end
|
||||||
@@ -69,16 +70,23 @@ class NicoTagsController < ApplicationController
|
|||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.gte_member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
|
locale = resolve_locale!
|
||||||
|
|
||||||
id = params[:id].to_i
|
id = params[:id].to_i
|
||||||
|
|
||||||
tag = ExternalTag.find(id)
|
tag = Tag.find(id)
|
||||||
|
return render_bad_request('ニコニコ・タグを指定してください.') unless tag.nico?
|
||||||
|
|
||||||
linked_tag_names = params[:tags].to_s.split
|
linked_tag_names = params[:tags].to_s.split
|
||||||
linked_tags = nil
|
linked_tags = nil
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
linked_tags = Tag.normalise_tags!(linked_tag_names, with_tagme: false,
|
linked_tags = Tag.normalise_tags!(locale, linked_tag_names,
|
||||||
with_no_deerjikist: false)
|
with_tagme: false,
|
||||||
|
with_no_deerjikist: false)
|
||||||
|
if linked_tags.any? { |t| t.nico? }
|
||||||
|
raise Tag::NicoTagNormalisationError
|
||||||
|
end
|
||||||
|
|
||||||
TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)
|
TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class PostVersionsController < ApplicationController
|
|||||||
'prev.original_created_before AS prev_original_created_before')
|
'prev.original_created_before AS prev_original_created_before')
|
||||||
q = q.where('post_versions.post_id = ?', post_id) if post_id
|
q = q.where('post_versions.post_id = ?', post_id) if post_id
|
||||||
if tag_id
|
if tag_id
|
||||||
q = q.where("JSON_CONTAINS(post_versions.tags_json, JSON_OBJECT('tag_id', #{ tag_id })) " +
|
q = q.where("JSON_CONTAINS(post_versions.tags_json, JSON_OBJECT('id', #{ tag_id })) " +
|
||||||
"OR JSON_CONTAINS(prev.tags_json, JSON_OBJECT('tag_id', #{ tag_id }))")
|
"OR JSON_CONTAINS(prev.tags_json, JSON_OBJECT('id', #{ tag_id }))")
|
||||||
end
|
end
|
||||||
|
|
||||||
count = q.except(:select, :order, :limit, :offset).count
|
count = q.except(:select, :order, :limit, :offset).count
|
||||||
@@ -113,18 +113,21 @@ class PostVersionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_version_tags cur_tags, prev_tags
|
def build_version_tags(cur_tags, prev_tags)
|
||||||
(cur_tags | prev_tags).map do |name|
|
(cur_tags | prev_tags).map do |name|
|
||||||
type =
|
type =
|
||||||
if cur_tags.include?(name) && prev_tags.include?(name)
|
if cur_tags.include?(name) && prev_tags.include?(name)
|
||||||
'context'
|
'context'
|
||||||
elsif cur_tags.include?(name)
|
elsif cur_tags.include?(name)
|
||||||
'added'
|
'added'
|
||||||
else
|
else
|
||||||
'removed'
|
'removed'
|
||||||
end
|
end
|
||||||
|
|
||||||
{ name:, type: }
|
{
|
||||||
|
name:,
|
||||||
|
type:
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ class PostsController < ApplicationController
|
|||||||
base_version_no = parse_base_version_no
|
base_version_no = parse_base_version_no
|
||||||
return render_bad_request('base_version_no は必須です.') if !(force) && !(base_version_no)
|
return render_bad_request('base_version_no は必須です.') if !(force) && !(base_version_no)
|
||||||
|
|
||||||
|
locale = Locale.find_by(code: params[:locale].presence) || Locale.nipponese
|
||||||
title = params[:title].presence
|
title = params[:title].presence
|
||||||
tag_names = params[:tags].to_s.split
|
tag_names = params[:tags].to_s.split
|
||||||
original_created_from = params[:original_created_from]
|
original_created_from = params[:original_created_from]
|
||||||
@@ -329,7 +330,8 @@ class PostsController < ApplicationController
|
|||||||
base_snapshot = post_snapshot_from_version(base_version)
|
base_snapshot = post_snapshot_from_version(base_version)
|
||||||
current_snapshot = post_snapshot_from_record(post)
|
current_snapshot = post_snapshot_from_record(post)
|
||||||
end
|
end
|
||||||
incoming_snapshot = post_incoming_snapshot(title:,
|
incoming_snapshot = post_incoming_snapshot(locale:,
|
||||||
|
title:,
|
||||||
original_created_from:,
|
original_created_from:,
|
||||||
original_created_before:,
|
original_created_before:,
|
||||||
tag_names:,
|
tag_names:,
|
||||||
@@ -358,7 +360,7 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
apply_post_snapshot!(post, snapshot_to_apply)
|
apply_post_snapshot!(locale, post, snapshot_to_apply)
|
||||||
end
|
end
|
||||||
|
|
||||||
return render json: conflict_json, status: :conflict if conflict_json
|
return render json: conflict_json, status: :conflict if conflict_json
|
||||||
@@ -671,6 +673,7 @@ class PostsController < ApplicationController
|
|||||||
post
|
post
|
||||||
.post_tags
|
.post_tags
|
||||||
.joins(tag: :tag_name)
|
.joins(tag: :tag_name)
|
||||||
|
.merge(Tag.not_nico)
|
||||||
.merge(Tag.where(deprecated_at: nil))
|
.merge(Tag.where(deprecated_at: nil))
|
||||||
.includes(:sections, tag: :tag_name)
|
.includes(:sections, tag: :tag_name)
|
||||||
.order('tag_names.name')
|
.order('tag_names.name')
|
||||||
@@ -684,11 +687,13 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_incoming_snapshot title:, original_created_from:, original_created_before:,
|
def post_incoming_snapshot locale:, title:, original_created_from:, original_created_before:,
|
||||||
tag_names:, video_ms_param:, duration_param:, parent_post_ids:
|
tag_names:, video_ms_param:, duration_param:, parent_post_ids:
|
||||||
validate_original_created_values!(original_created_from, original_created_before)
|
validate_original_created_values!(original_created_from, original_created_before)
|
||||||
Tag.normalise_tags!(tag_names, with_tagme: false, deny_deprecated: true,
|
Tag.normalise_tags!(locale, tag_names,
|
||||||
with_sections: true) => { tags:, sections: }
|
with_tagme: false,
|
||||||
|
deny_deprecated: true,
|
||||||
|
with_sections: true) => { tags:, sections: }
|
||||||
|
|
||||||
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
||||||
video_ms = normalise_video_ms(tags, video_ms_param:, duration_param:)
|
video_ms = normalise_video_ms(tags, video_ms_param:, duration_param:)
|
||||||
@@ -823,7 +828,7 @@ class PostsController < ApplicationController
|
|||||||
(added_by_current & removed_by_me).present? || (removed_by_current & added_by_me).present?
|
(added_by_current & removed_by_me).present? || (removed_by_current & added_by_me).present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_post_snapshot! post, snapshot
|
def apply_post_snapshot! locale, post, snapshot
|
||||||
PostVersionRecorder.ensure_snapshot!(post, created_by_user: current_user)
|
PostVersionRecorder.ensure_snapshot!(post, created_by_user: current_user)
|
||||||
|
|
||||||
post.update!(title: snapshot[:title],
|
post.update!(title: snapshot[:title],
|
||||||
@@ -831,12 +836,15 @@ class PostsController < ApplicationController
|
|||||||
original_created_from: snapshot[:original_created_from],
|
original_created_from: snapshot[:original_created_from],
|
||||||
original_created_before: snapshot[:original_created_before])
|
original_created_before: snapshot[:original_created_before])
|
||||||
|
|
||||||
Tag.normalise_tags!(snapshot[:tag_names],
|
Tag.normalise_tags!(locale, snapshot[:tag_names],
|
||||||
with_tagme: false,
|
with_tagme: false,
|
||||||
deny_deprecated: true,
|
deny_deprecated: true,
|
||||||
with_sections: true) => { tags:, sections: }
|
with_sections: true) => { tags: editable_tags, sections: }
|
||||||
TagVersioning.record_tag_snapshots!(tags, created_by_user: current_user)
|
TagVersioning.record_tag_snapshots!(editable_tags, created_by_user: current_user)
|
||||||
|
|
||||||
|
readonly_tags = post.tags.nico.to_a
|
||||||
|
|
||||||
|
tags = readonly_tags + editable_tags
|
||||||
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
||||||
|
|
||||||
post.video_ms = tags.any? { _1.id == Tag.video.id } ? snapshot[:video_ms] : nil
|
post.video_ms = tags.any? { _1.id == Tag.video.id } ? snapshot[:video_ms] : nil
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class TagChildrenController < ApplicationController
|
|||||||
|
|
||||||
parent = Tag.find(parent_id)
|
parent = Tag.find(parent_id)
|
||||||
child = Tag.find(child_id)
|
child = Tag.find(child_id)
|
||||||
|
return render_bad_request('ニコニコ・タグの階層は変更できません.') if parent.nico? || child.nico?
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
||||||
@@ -32,6 +33,7 @@ class TagChildrenController < ApplicationController
|
|||||||
|
|
||||||
parent = Tag.find(parent_id)
|
parent = Tag.find(parent_id)
|
||||||
child = Tag.find(child_id)
|
child = Tag.find(child_id)
|
||||||
|
return render_bad_request('ニコニコ・タグの階層は変更できません.') if parent.nico? || child.nico?
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
||||||
|
|||||||
@@ -100,14 +100,13 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
def autocomplete
|
def autocomplete
|
||||||
q = params[:q].to_s.strip.sub(/\Anot:/i, '')
|
q = params[:q].to_s.strip.sub(/\Anot:/i, '')
|
||||||
prefix = "#{ ActiveRecord::Base.sanitize_sql_like(q) }%"
|
|
||||||
|
|
||||||
with_nico = bool?(:nico, default: true)
|
with_nico = bool?(:nico, default: true)
|
||||||
present_only = bool?(:present, default: true)
|
present_only = bool?(:present, default: true)
|
||||||
|
|
||||||
alias_rows =
|
alias_rows =
|
||||||
TagName
|
TagName
|
||||||
.where('name LIKE ?', prefix)
|
.where('name LIKE ?', "#{ q }%")
|
||||||
.where.not(canonical_id: nil)
|
.where.not(canonical_id: nil)
|
||||||
.pluck(:canonical_id, :name)
|
.pluck(:canonical_id, :name)
|
||||||
|
|
||||||
@@ -119,57 +118,29 @@ class TagsController < ApplicationController
|
|||||||
matched_alias_by_tag_name_id[canonical_id] ||= alias_name
|
matched_alias_by_tag_name_id[canonical_id] ||= alias_name
|
||||||
end
|
end
|
||||||
|
|
||||||
base =
|
base = Tag.joins(:tag_name)
|
||||||
Tag
|
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||||
.joins(:tag_name)
|
.where(deprecated_at: nil)
|
||||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
|
||||||
.where(deprecated_at: nil)
|
|
||||||
base = base.where('tags.post_count > 0') if present_only
|
base = base.where('tags.post_count > 0') if present_only
|
||||||
|
|
||||||
canonical_hit = base.where('tag_names.name LIKE ?', prefix)
|
canonical_hit =
|
||||||
|
base
|
||||||
|
.where(((with_nico ? '(tags.category = ? AND tag_names.name LIKE ?) OR ' : '') +
|
||||||
|
'tag_names.name LIKE ?'),
|
||||||
|
*(with_nico ? ['nico', "nico:#{ q }%"] : []), "#{ q }%")
|
||||||
|
|
||||||
internal_tags =
|
tags =
|
||||||
if with_nico
|
if canonical_ids.present?
|
||||||
canonical_hit.or(base.where(tag_name_id: canonical_ids.uniq))
|
canonical_hit.or(base.where(tag_name_id: canonical_ids.uniq))
|
||||||
else
|
else
|
||||||
canonical_hit
|
canonical_hit
|
||||||
end
|
end
|
||||||
|
|
||||||
internal_rows =
|
tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a
|
||||||
internal_tags
|
|
||||||
.order(Arel.sql('tags.post_count DESC, tag_names.name'))
|
|
||||||
.limit(20)
|
|
||||||
.map { |tag|
|
|
||||||
TagRepr.base(tag).merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
|
|
||||||
}
|
|
||||||
|
|
||||||
return render json: internal_rows unless with_nico
|
render json: tags.map { |tag|
|
||||||
|
TagRepr.base(tag).merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
|
||||||
external_base = ExternalTag.all
|
}
|
||||||
external_base = external_base.where('post_count > 0') if present_only
|
|
||||||
|
|
||||||
external_rows =
|
|
||||||
external_base
|
|
||||||
.where("CONCAT(platform, ':', name) LIKE ? OR name LIKE ?", prefix, prefix)
|
|
||||||
.order(post_count: :desc, name: :asc)
|
|
||||||
.limit(20)
|
|
||||||
.map { |tag|
|
|
||||||
{ 'id' => tag.id,
|
|
||||||
'name' => "#{ tag.platform }:#{ tag.name }",
|
|
||||||
'category' => 'nico',
|
|
||||||
'deprecated_at' => nil,
|
|
||||||
'created_at' => tag.created_at,
|
|
||||||
'updated_at' => tag.created_at,
|
|
||||||
'post_count' => tag.post_count,
|
|
||||||
'matched_alias' => nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
rows =
|
|
||||||
(internal_rows + external_rows)
|
|
||||||
.sort_by { |row| [-row['post_count'], row['name']] }
|
|
||||||
.first(20)
|
|
||||||
|
|
||||||
render json: rows
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -307,11 +278,17 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
return unless validate_tag_rename(tag, name)
|
return unless validate_tag_rename(tag, name)
|
||||||
|
|
||||||
|
locale = resolve_locale!
|
||||||
|
|
||||||
alias_names = params[:aliases].to_s.split.uniq
|
alias_names = params[:aliases].to_s.split.uniq
|
||||||
parent_names = params[:parent_tags].to_s.split.uniq
|
parent_names = params[:parent_tags].to_s.split.uniq
|
||||||
deprecated = bool?(:deprecated)
|
deprecated = bool?(:deprecated)
|
||||||
|
|
||||||
if category == 'nico'
|
if tag.nico? && deprecated
|
||||||
|
return render_unprocessable_entity 'ニコタグは廃止できません.', field: :deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
if tag.nico? || category == 'nico'
|
||||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -333,7 +310,7 @@ class TagsController < ApplicationController
|
|||||||
alias_names.delete(name)
|
alias_names.delete(name)
|
||||||
|
|
||||||
update_aliases!(tag, alias_names)
|
update_aliases!(tag, alias_names)
|
||||||
update_parent_tags!(tag, parent_names)
|
update_parent_tags!(locale, tag, parent_names)
|
||||||
|
|
||||||
tag.reload
|
tag.reload
|
||||||
|
|
||||||
@@ -359,9 +336,13 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
tag = Tag.find(params[:id])
|
tag = Tag.find(params[:id])
|
||||||
|
|
||||||
|
if tag.nico? && deprecated_given && deprecated
|
||||||
|
return render_unprocessable_entity 'ニコタグは廃止できません.', field: :deprecated
|
||||||
|
end
|
||||||
|
|
||||||
return unless validate_tag_rename(tag, name)
|
return unless validate_tag_rename(tag, name)
|
||||||
|
|
||||||
if category.present? && category == 'nico'
|
if tag.nico? || (category.present? && category == 'nico')
|
||||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -568,6 +549,11 @@ class TagsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def record_tag_version! tag, event_type:, created_by_user:, name_changed: false, wiki_page: nil
|
def record_tag_version! tag, event_type:, created_by_user:, name_changed: false, wiki_page: nil
|
||||||
|
if tag.nico?
|
||||||
|
NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
||||||
|
|
||||||
return unless name_changed
|
return unless name_changed
|
||||||
@@ -675,9 +661,11 @@ class TagsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_parent_tags! tag, parent_names
|
def update_parent_tags! locale, tag, parent_names
|
||||||
parent_tags = Tag.normalise_tags!(parent_names, with_tagme: false,
|
parent_tags = Tag.normalise_tags!(locale, parent_names,
|
||||||
with_no_deerjikist: false)
|
with_tagme: false,
|
||||||
|
with_no_deerjikist: false,
|
||||||
|
deny_nico: true)
|
||||||
|
|
||||||
old_parent_tags = tag.parents.to_a
|
old_parent_tags = tag.parents.to_a
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
class ExternalTag < ApplicationRecord
|
|
||||||
enum :platform, nico: 'nico'
|
|
||||||
validates :platform, presence: true, inclusion: { in: ExternalTag.platforms.keys }
|
|
||||||
|
|
||||||
has_many :post_external_tags, dependent: :delete_all
|
|
||||||
has_many :posts, through: :post_external_tags
|
|
||||||
|
|
||||||
has_many :nico_tag_versions, foreign_key: :tag_id, inverse_of: :external_tag
|
|
||||||
|
|
||||||
has_many :nico_tag_relations,
|
|
||||||
foreign_key: :nico_tag_id,
|
|
||||||
inverse_of: :nico_tag,
|
|
||||||
dependent: :destroy
|
|
||||||
|
|
||||||
has_many :linked_tags, through: :nico_tag_relations, source: :tag
|
|
||||||
|
|
||||||
def snapshot_linked_tag_names
|
|
||||||
linked_tags.joins(:tag_name).order('tag_names.name').pluck('tag_names.name')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
class Language < ApplicationRecord
|
||||||
|
has_many :languages, class_name: 'Locale', foreign_key: :language_code
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
class Locale < ApplicationRecord
|
||||||
|
after_create_commit :generate_tag_names!
|
||||||
|
|
||||||
|
belongs_to :language, foreign_key: :language_code, primary_key: :code
|
||||||
|
belongs_to :script, foreign_key: :script_code, primary_key: :code
|
||||||
|
|
||||||
|
def self.nipponese = Locale.find('ja')
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def generate_tag_names!
|
||||||
|
tag_ids = TagName.where(language_code:, primary_flg: true).pluck(:tag_id)
|
||||||
|
Tag.where.not(id: tag_ids).find_each do
|
||||||
|
TagName.create!(tag_id: _1.id,
|
||||||
|
language_code:,
|
||||||
|
name: TagName.generate_name(self, _1, _1.name),
|
||||||
|
script_code:,
|
||||||
|
primary_flg: true,
|
||||||
|
# TODO: 公証実装したら書く.
|
||||||
|
# auto_generated: true,
|
||||||
|
canonical_id: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
class NicoTagRelation < ApplicationRecord
|
class NicoTagRelation < ApplicationRecord
|
||||||
belongs_to :nico_tag,
|
belongs_to :nico_tag, class_name: 'Tag'
|
||||||
class_name: 'ExternalTag',
|
belongs_to :tag, class_name: 'Tag'
|
||||||
foreign_key: :nico_tag_id,
|
|
||||||
inverse_of: :nico_tag_relations
|
|
||||||
belongs_to :tag, class_name: 'Tag', foreign_key: :tag_id
|
|
||||||
|
|
||||||
validates :nico_tag_id, presence: true
|
validates :nico_tag_id, presence: true
|
||||||
validates :tag_id, presence: true
|
validates :tag_id, presence: true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class NicoTagVersion < ApplicationRecord
|
class NicoTagVersion < ApplicationRecord
|
||||||
include VersionRecord
|
include VersionRecord
|
||||||
|
|
||||||
belongs_to :external_tag, foreign_key: :tag_id, inverse_of: :nico_tag_versions
|
belongs_to :tag
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
end
|
end
|
||||||
|
|||||||
+13
-24
@@ -92,9 +92,6 @@ class Post < ApplicationRecord
|
|||||||
inverse_of: :parent_post
|
inverse_of: :parent_post
|
||||||
has_many :children, through: :child_post_implications, source: :post
|
has_many :children, through: :child_post_implications, source: :post
|
||||||
|
|
||||||
has_many :post_external_tags, dependent: :delete_all
|
|
||||||
has_many :external_tags, through: :post_external_tags
|
|
||||||
|
|
||||||
has_one_attached :thumbnail
|
has_one_attached :thumbnail
|
||||||
|
|
||||||
attribute :version_no, :integer, default: 1
|
attribute :version_no, :integer, default: 1
|
||||||
@@ -153,27 +150,19 @@ class Post < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def snapshot_tags_json
|
def snapshot_tags_json
|
||||||
tag_snapshots =
|
post_tags
|
||||||
post_tags
|
.joins(tag: :tag_name)
|
||||||
.joins(tag: :tag_name)
|
.includes(:sections, tag: :tag_name)
|
||||||
.includes(:sections, tag: :tag_name)
|
.order('tags.id')
|
||||||
.order('tags.id')
|
.map do |pt|
|
||||||
.map { |pt|
|
{ 'id' => pt.tag.id,
|
||||||
{ 'tag_id' => pt.tag.id,
|
'version_no' => pt.tag.version_no,
|
||||||
'version_no' => pt.tag.version_no,
|
'name' => pt.tag.name,
|
||||||
'name' => pt.tag.name,
|
'category' => pt.tag.category,
|
||||||
'category' => pt.tag.category,
|
'sections' => pt.sections.sort_by(&:begin_ms).map {
|
||||||
'sections' => pt.sections.sort_by(&:begin_ms).map {
|
{ 'begin_ms' => _1.begin_ms, 'end_ms' => _1.end_ms }
|
||||||
{ 'begin_ms' => _1.begin_ms, 'end_ms' => _1.end_ms }
|
} }
|
||||||
} }
|
end
|
||||||
}
|
|
||||||
|
|
||||||
external_tag_snapshots =
|
|
||||||
post_external_tags.order(:external_tag_id).map {
|
|
||||||
{ 'external_tag_id' => _1.external_tag_id }
|
|
||||||
}
|
|
||||||
|
|
||||||
tag_snapshots + external_tag_snapshots
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.section_literal section
|
def self.section_literal section
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
class PostExternalTag < ApplicationRecord
|
|
||||||
belongs_to :post
|
|
||||||
belongs_to :external_tag
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
class Script < ApplicationRecord
|
||||||
|
;
|
||||||
|
end
|
||||||
+59
-27
@@ -28,10 +28,11 @@ class Tag < ApplicationRecord
|
|||||||
has_many :post_tags, inverse_of: :tag
|
has_many :post_tags, inverse_of: :tag
|
||||||
has_many :posts, through: :post_tags
|
has_many :posts, through: :post_tags
|
||||||
|
|
||||||
|
has_many :nico_tag_relations, foreign_key: :nico_tag_id, dependent: :destroy
|
||||||
|
has_many :linked_tags, through: :nico_tag_relations, source: :tag
|
||||||
|
|
||||||
has_many :reversed_nico_tag_relations,
|
has_many :reversed_nico_tag_relations,
|
||||||
class_name: 'NicoTagRelation',
|
class_name: 'NicoTagRelation', foreign_key: :tag_id, dependent: :destroy
|
||||||
foreign_key: :nico_tag_id,
|
|
||||||
dependent: :destroy
|
|
||||||
has_many :linked_nico_tags, through: :reversed_nico_tag_relations, source: :nico_tag
|
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 :tag_implications, foreign_key: :parent_tag_id, dependent: :destroy
|
||||||
@@ -49,7 +50,9 @@ class Tag < ApplicationRecord
|
|||||||
has_many :materials
|
has_many :materials
|
||||||
|
|
||||||
has_many :tag_versions
|
has_many :tag_versions
|
||||||
|
has_many :nico_tag_versions
|
||||||
|
|
||||||
|
has_many :tag_names
|
||||||
belongs_to :tag_name
|
belongs_to :tag_name
|
||||||
delegate :wiki_page, to: :tag_name
|
delegate :wiki_page, to: :tag_name
|
||||||
|
|
||||||
@@ -63,15 +66,17 @@ class Tag < ApplicationRecord
|
|||||||
character: 'character',
|
character: 'character',
|
||||||
general: 'general',
|
general: 'general',
|
||||||
material: 'material',
|
material: 'material',
|
||||||
|
nico: 'nico',
|
||||||
meta: 'meta'
|
meta: 'meta'
|
||||||
|
|
||||||
validates :category, presence: true, inclusion: { in: Tag.categories.keys }
|
validates :category, presence: true, inclusion: { in: Tag.categories.keys }
|
||||||
|
|
||||||
validate :tag_name_mustnt_start_with_nico
|
validate :nico_tag_name_must_start_with_nico
|
||||||
validate :tag_name_must_be_canonical
|
validate :tag_name_must_be_canonical
|
||||||
validate :category_must_be_deerjikist_with_deerjikists
|
validate :category_must_be_deerjikist_with_deerjikists
|
||||||
|
validate :nico_tags_cannot_be_deprecated
|
||||||
|
|
||||||
def self.nico_tags = ExternalTag.where(platform: :nico)
|
scope :nico_tags, -> { nico }
|
||||||
|
|
||||||
CATEGORY_PREFIXES = {
|
CATEGORY_PREFIXES = {
|
||||||
'general:' => :general,
|
'general:' => :general,
|
||||||
@@ -97,18 +102,21 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
def has_deerjikists = deerjikists.loaded? ? deerjikists.any? : deerjikists.exists?
|
def has_deerjikists = deerjikists.loaded? ? deerjikists.any? : deerjikists.exists?
|
||||||
|
|
||||||
def self.tagme = find_or_create_by_tag_name!('タグ希望', category: :meta)
|
def self.tagme = find_or_create_by_tag_name!(Locale.nipponese, 'タグ希望', category: :meta)
|
||||||
def self.bot = find_or_create_by_tag_name!('bot操作', category: :meta)
|
def self.bot = find_or_create_by_tag_name!(Locale.nipponese, 'bot操作', category: :meta)
|
||||||
def self.no_deerjikist = find_or_create_by_tag_name!('ニジラー情報不詳', category: :meta)
|
def self.no_deerjikist =
|
||||||
def self.video = find_or_create_by_tag_name!('動画', category: :meta)
|
find_or_create_by_tag_name!(Locale.nipponese, 'ニジラー情報不詳', category: :meta)
|
||||||
def self.niconico = find_or_create_by_tag_name!('ニコニコ', category: :meta)
|
def self.video = find_or_create_by_tag_name!(Locale.nipponese, '動画', category: :meta)
|
||||||
def self.youtube = find_or_create_by_tag_name!('YouTube', category: :meta)
|
def self.niconico = find_or_create_by_tag_name!(Locale.nipponese, 'ニコニコ', category: :meta)
|
||||||
|
def self.youtube = find_or_create_by_tag_name!(Locale.nipponese, 'YouTube', category: :meta)
|
||||||
|
|
||||||
def self.normalise_tags! tag_names, with_tagme: true,
|
def self.normalise_tags! locale, tag_names,
|
||||||
with_no_deerjikist: true,
|
with_tagme: true,
|
||||||
deny_deprecated: false,
|
with_no_deerjikist: true,
|
||||||
with_sections: false
|
deny_nico: true,
|
||||||
if tag_names.any? { |n| n.downcase.start_with?('nico:') }
|
deny_deprecated: false,
|
||||||
|
with_sections: false
|
||||||
|
if deny_nico && tag_names.any? { |n| n.downcase.start_with?('nico:') }
|
||||||
raise NicoTagNormalisationError
|
raise NicoTagNormalisationError
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -136,7 +144,7 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
name = TagName.canonicalise(name).first
|
name = TagName.canonicalise(name).first
|
||||||
|
|
||||||
find_or_create_by_tag_name!(name, category: (cat || :general)).tap do |tag|
|
find_or_create_by_tag_name!(locale, name, category: (cat || :general)).tap do |tag|
|
||||||
if deny_deprecated && tag.deprecated?
|
if deny_deprecated && tag.deprecated?
|
||||||
raise DeprecatedTagNormalisationError, [tag.name]
|
raise DeprecatedTagNormalisationError, [tag.name]
|
||||||
end
|
end
|
||||||
@@ -224,13 +232,25 @@ class Tag < ApplicationRecord
|
|||||||
[left_end_ms, right_end_ms].max
|
[left_end_ms, right_end_ms].max
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_or_create_by_tag_name! name, category:
|
def self.find_or_create_by_tag_name! locale, name, category:
|
||||||
tn = TagName.find_or_create_by!(name: name.to_s.strip)
|
language_code = locale.language_code
|
||||||
tn = tn.canonical if tn.canonical_id?
|
name = name.to_s.strip
|
||||||
|
|
||||||
Tag.find_or_create_by!(tag_name_id: tn.id) do |t|
|
tn = TagName.find_or_create_by!(language_code:, name:) do
|
||||||
t.category = category
|
_1.script_code = locale.script_code
|
||||||
|
_1.primary_flg = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
tag = tn.tag
|
||||||
|
if tag
|
||||||
|
tag.update!(tag_name_id: tn.id) if tag.tag_name_id != tn.id
|
||||||
|
return tag
|
||||||
|
end
|
||||||
|
|
||||||
|
tag = Tag.create!(tag_name: tn, category:)
|
||||||
|
tn.update!(tag:)
|
||||||
|
|
||||||
|
tag
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
retry
|
retry
|
||||||
end
|
end
|
||||||
@@ -268,8 +288,12 @@ class Tag < ApplicationRecord
|
|||||||
TagVersioning.record!(source_tag, event_type: :discard, created_by_user:)
|
TagVersioning.record!(source_tag, event_type: :discard, created_by_user:)
|
||||||
source_tag.destroy!
|
source_tag.destroy!
|
||||||
|
|
||||||
source_tag_name.update_columns(canonical_id: target_tag.tag_name_id,
|
if source_tag.nico?
|
||||||
updated_at: Time.current)
|
source_tag_name.destroy!
|
||||||
|
else
|
||||||
|
source_tag_name.update_columns(canonical_id: target_tag.tag_name_id,
|
||||||
|
updated_at: Time.current)
|
||||||
|
end
|
||||||
|
|
||||||
TagVersioning.record!(target_tag, event_type: :update, created_by_user:)
|
TagVersioning.record!(target_tag, event_type: :update, created_by_user:)
|
||||||
end
|
end
|
||||||
@@ -296,9 +320,11 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def tag_name_mustnt_start_with_nico
|
def nico_tag_name_must_start_with_nico
|
||||||
if name.to_s.downcase.start_with?('nico:')
|
n = name.to_s
|
||||||
errors.add :name, 'タグの命名規則に反してゐます.'
|
if ((nico? && !(n.downcase.start_with?('nico:'))) ||
|
||||||
|
(!(nico?) && n.downcase.start_with?('nico:')))
|
||||||
|
errors.add :name, 'ニコニコ・タグの命名規則に反してゐます.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -339,4 +365,10 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
total_s * 1_000 + match[:ms].to_s.ljust(3, '0')[0, 3].to_i
|
total_s * 1_000 + match[:ms].to_s.ljust(3, '0')[0, 3].to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def nico_tags_cannot_be_deprecated
|
||||||
|
if nico? && deprecated_at.present?
|
||||||
|
errors.add :deprecated_at, 'ニコタグは廃止できません.'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class TagName < ApplicationRecord
|
class TagName < ApplicationRecord
|
||||||
has_one :tag
|
belongs_to :tag, optional: true
|
||||||
has_one :wiki_page
|
has_one :wiki_page
|
||||||
|
|
||||||
belongs_to :canonical, class_name: 'TagName', optional: true
|
belongs_to :canonical, class_name: 'TagName', optional: true
|
||||||
@@ -21,6 +21,12 @@ class TagName < ApplicationRecord
|
|||||||
names.map { |name| tns[name]&.canonical&.name || name }.uniq
|
names.map { |name| tns[name]&.canonical&.name || name }.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.generate_name locale, tag, name
|
||||||
|
# TODO: 言語ごとの自動命名ロジック完成したら書く.
|
||||||
|
|
||||||
|
"Tag_##{ tag.id }"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def canonical_must_be_canonical
|
def canonical_must_be_canonical
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
class NicoTagVersionRecorder < VersionRecorder
|
class NicoTagVersionRecorder < VersionRecorder
|
||||||
def self.record! external_tag:, event_type:, created_by_user:
|
def self.record! tag:, event_type:, created_by_user:
|
||||||
new(external_tag:, event_type:, created_by_user:).record!
|
new(tag:, event_type:, created_by_user:).record!
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize external_tag:, event_type:, created_by_user:
|
def initialize tag:, event_type:, created_by_user:
|
||||||
super(record: external_tag, event_type:, created_by_user:)
|
super(record: tag, event_type:, created_by_user:)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def version_class = NicoTagVersion
|
def version_class = NicoTagVersion
|
||||||
def version_association = :nico_tag_versions
|
def version_association = :nico_tag_versions
|
||||||
def record_key = :external_tag
|
def record_key = :tag
|
||||||
|
|
||||||
def snapshot_attributes
|
def snapshot_attributes
|
||||||
{ name: "#{ @record.platform }:#{ @record.name }",
|
{ name: @record.name, linked_tags: @record.snapshot_linked_tag_names.join(' ') }
|
||||||
linked_tags: @record.snapshot_linked_tag_names.join(' ') }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def tracks_version_no_on_record? = false
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,22 +20,23 @@ class PostCreatePlan
|
|||||||
parent_post_ids = normalise_parent_post_ids
|
parent_post_ids = normalise_parent_post_ids
|
||||||
validate_parent_post_ids!(parent_post_ids)
|
validate_parent_post_ids!(parent_post_ids)
|
||||||
|
|
||||||
{ url: @attributes[:url],
|
{
|
||||||
title: @attributes[:title].to_s,
|
url: @attributes[:url],
|
||||||
thumbnail_base: @attributes[:thumbnail_base].presence,
|
title: @attributes[:title].to_s,
|
||||||
original_created_from: @attributes[:original_created_from].presence,
|
thumbnail_base: @attributes[:thumbnail_base].presence,
|
||||||
original_created_before: @attributes[:original_created_before].presence,
|
original_created_from: @attributes[:original_created_from].presence,
|
||||||
tags: serialised_tags(direct_tag_specs, tag_sections),
|
original_created_before: @attributes[:original_created_before].presence,
|
||||||
display_tags: display_tags(direct_tag_specs, tag_sections),
|
tags: serialised_tags(direct_tag_specs, tag_sections),
|
||||||
duration: @attributes[:duration].to_s,
|
display_tags: display_tags(direct_tag_specs, tag_sections),
|
||||||
video_ms: video_ms,
|
duration: @attributes[:duration].to_s,
|
||||||
parent_post_ids: parent_post_ids.join(' '),
|
video_ms: video_ms,
|
||||||
direct_tag_specs: direct_tag_specs,
|
parent_post_ids: parent_post_ids.join(' '),
|
||||||
default_tag_specs: default_tag_specs,
|
direct_tag_specs: direct_tag_specs,
|
||||||
snapshot_tag_specs: snapshot_tag_specs,
|
default_tag_specs: default_tag_specs,
|
||||||
post_tag_specs: post_tag_specs,
|
snapshot_tag_specs: snapshot_tag_specs,
|
||||||
tag_sections: tag_sections,
|
post_tag_specs: post_tag_specs,
|
||||||
normalised_parent_post_ids: parent_post_ids }
|
tag_sections: tag_sections,
|
||||||
|
normalised_parent_post_ids: parent_post_ids }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -49,6 +50,7 @@ class PostCreatePlan
|
|||||||
tag_names.each do |raw_name|
|
tag_names.each do |raw_name|
|
||||||
tag_name, category, sections = parse_raw_tag_name(raw_name)
|
tag_name, category, sections = parse_raw_tag_name(raw_name)
|
||||||
existing_tag = existing_tags_by_name[tag_name]
|
existing_tag = existing_tags_by_name[tag_name]
|
||||||
|
raise Tag::NicoTagNormalisationError if existing_tag&.nico?
|
||||||
raise Tag::DeprecatedTagNormalisationError, [existing_tag.name] if existing_tag&.deprecated?
|
raise Tag::DeprecatedTagNormalisationError, [existing_tag.name] if existing_tag&.deprecated?
|
||||||
|
|
||||||
direct_tag_specs << {
|
direct_tag_specs << {
|
||||||
@@ -137,14 +139,14 @@ class PostCreatePlan
|
|||||||
Tag.expand_parent_tags(existing_snapshot_tags)
|
Tag.expand_parent_tags(existing_snapshot_tags)
|
||||||
.reject(&:deprecated?)
|
.reject(&:deprecated?)
|
||||||
.map { |tag|
|
.map { |tag|
|
||||||
{ name: tag.name,
|
{
|
||||||
category: tag.category.to_sym }
|
name: tag.name,
|
||||||
}
|
category: tag.category.to_sym } }
|
||||||
merge_tag_specs(snapshot_tag_specs + expanded_parent_specs)
|
merge_tag_specs(snapshot_tag_specs + expanded_parent_specs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_tag_specs specs
|
def merge_tag_specs specs
|
||||||
specs.each_with_object({ }) { |spec, merged|
|
specs.each_with_object({ }) do |spec, merged|
|
||||||
merged[spec[:name]] =
|
merged[spec[:name]] =
|
||||||
if merged.key?(spec[:name]) && merged[spec[:name]][:category] != :general
|
if merged.key?(spec[:name]) && merged[spec[:name]][:category] != :general
|
||||||
merged[spec[:name]]
|
merged[spec[:name]]
|
||||||
@@ -153,7 +155,7 @@ class PostCreatePlan
|
|||||||
name: spec[:name],
|
name: spec[:name],
|
||||||
category: spec[:category] }
|
category: spec[:category] }
|
||||||
end
|
end
|
||||||
}.values.sort_by { _1[:name] }
|
end.values.sort_by { _1[:name] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def existing_tags_by_name
|
def existing_tags_by_name
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class PostCreator
|
|||||||
@field_warnings = { }
|
@field_warnings = { }
|
||||||
end
|
end
|
||||||
|
|
||||||
def create!
|
def create!(locale)
|
||||||
thumbnail_attachment = prepare_thumbnail_attachment
|
thumbnail_attachment = prepare_thumbnail_attachment
|
||||||
post = Post.new(title: @attributes[:title].presence,
|
post = Post.new(title: @attributes[:title].presence,
|
||||||
url: @attributes[:url],
|
url: @attributes[:url],
|
||||||
@@ -25,7 +25,7 @@ class PostCreator
|
|||||||
post_tags = planned_post_tags
|
post_tags = planned_post_tags
|
||||||
sections = planned_sections
|
sections = planned_sections
|
||||||
TagVersioning.record_tag_snapshots!(snapshot_tags, created_by_user: @actor)
|
TagVersioning.record_tag_snapshots!(snapshot_tags, created_by_user: @actor)
|
||||||
post.video_ms = planned_video_ms
|
post.video_ms = planned_video_ms(locale)
|
||||||
post.save!
|
post.save!
|
||||||
sync_post_tags!(post, post_tags, sections)
|
sync_post_tags!(post, post_tags, sections)
|
||||||
sync_parent_posts!(post, planned_parent_post_ids)
|
sync_parent_posts!(post, planned_parent_post_ids)
|
||||||
@@ -53,15 +53,15 @@ class PostCreator
|
|||||||
|
|
||||||
def planned_parent_post_ids = planned_create_attributes[:normalised_parent_post_ids]
|
def planned_parent_post_ids = planned_create_attributes[:normalised_parent_post_ids]
|
||||||
|
|
||||||
def planned_video_ms
|
def planned_video_ms locale
|
||||||
planned_create_attributes[:video_ms]
|
planned_create_attributes(locale)[:video_ms]
|
||||||
end
|
end
|
||||||
|
|
||||||
def planned_create_attributes
|
def planned_create_attributes locale
|
||||||
@planned_create_attributes ||= begin
|
@planned_create_attributes ||= begin
|
||||||
if @attributes.key?(:snapshot_tag_specs)
|
if @attributes.key?(:snapshot_tag_specs)
|
||||||
snapshot_tags = materialise_tags(@attributes[:snapshot_tag_specs] || [])
|
snapshot_tags = materialise_tags(locale, @attributes[:snapshot_tag_specs] || [])
|
||||||
post_tags = materialise_tags(@attributes[:post_tag_specs] || [])
|
post_tags = materialise_tags(locale, @attributes[:post_tag_specs] || [])
|
||||||
{
|
{
|
||||||
snapshot_tags: snapshot_tags,
|
snapshot_tags: snapshot_tags,
|
||||||
post_tags: post_tags,
|
post_tags: post_tags,
|
||||||
@@ -92,13 +92,13 @@ class PostCreator
|
|||||||
video_ms: plan[:video_ms] }
|
video_ms: plan[:video_ms] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def materialise_tags specs
|
def materialise_tags locale, specs
|
||||||
Array(specs).each_with_object({ }) do |spec, tags|
|
Array(specs).each_with_object({ }) do |spec, tags|
|
||||||
name = spec[:name] || spec['name']
|
name = spec[:name] || spec['name']
|
||||||
category = spec[:category] || spec['category']
|
category = spec[:category] || spec['category']
|
||||||
next if name.blank? || category.blank?
|
next if name.blank? || category.blank?
|
||||||
|
|
||||||
tag = Tag.find_or_create_by_tag_name!(name, category:)
|
tag = Tag.find_or_create_by_tag_name!(locale, name, category:)
|
||||||
tag.update!(category:) if tag.category.to_sym != category.to_sym
|
tag.update!(category:) if tag.category.to_sym != category.to_sym
|
||||||
tags[name] ||= tag
|
tags[name] ||= tag
|
||||||
end.values
|
end.values
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
class TagVersioning
|
class TagVersioning
|
||||||
def self.record! tag, event_type:, created_by_user:
|
def self.record! tag, event_type:, created_by_user:
|
||||||
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
if tag.nico?
|
||||||
|
NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
||||||
|
else
|
||||||
|
TagVersionRecorder.record!(tag:, event_type:, created_by_user:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ensure_snapshot! tag, created_by_user:
|
def self.ensure_snapshot! tag, created_by_user:
|
||||||
return if tag.tag_versions.exists?
|
if tag.nico?
|
||||||
|
return if tag.nico_tag_versions.exists?
|
||||||
|
|
||||||
TagVersionRecorder.record!(tag:, event_type: :create, created_by_user:)
|
NicoTagVersionRecorder.record!(tag:, event_type: :create, created_by_user:)
|
||||||
|
else
|
||||||
|
return if tag.tag_versions.exists?
|
||||||
|
|
||||||
|
TagVersionRecorder.record!(tag:, event_type: :create, created_by_user:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.record_tag_snapshot! tag, created_by_user:
|
def self.record_tag_snapshot! tag, created_by_user:
|
||||||
event_type = tag.tag_versions.exists? ? :update : :create
|
event_type =
|
||||||
|
if tag.nico?
|
||||||
|
tag.nico_tag_versions.exists? ? :update : :create
|
||||||
|
else
|
||||||
|
tag.tag_versions.exists? ? :update : :create
|
||||||
|
end
|
||||||
|
|
||||||
record!(tag, event_type:, created_by_user:)
|
record!(tag, event_type:, created_by_user:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -47,14 +47,10 @@ class VersionRecorder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update_record_version_no! version_no
|
def update_record_version_no! version_no
|
||||||
return unless tracks_version_no_on_record?
|
|
||||||
|
|
||||||
@record.update_columns(version_no:)
|
@record.update_columns(version_no:)
|
||||||
@record.version_no = version_no
|
@record.version_no = version_no
|
||||||
end
|
end
|
||||||
|
|
||||||
def tracks_version_no_on_record? = true
|
|
||||||
|
|
||||||
def validate_version_sequence! latest
|
def validate_version_sequence! latest
|
||||||
if !(latest) && @event_type != 'create'
|
if !(latest) && @event_type != 'create'
|
||||||
raise "#{ version_class.name } first event must be create"
|
raise "#{ version_class.name } first event must be create"
|
||||||
@@ -64,7 +60,7 @@ class VersionRecorder
|
|||||||
raise "#{ version_class.name } create event already exists"
|
raise "#{ version_class.name } create event already exists"
|
||||||
end
|
end
|
||||||
|
|
||||||
return if !(latest) || !(tracks_version_no_on_record?)
|
return unless latest
|
||||||
|
|
||||||
if @record.version_no != latest.version_no
|
if @record.version_no != latest.version_no
|
||||||
raise ("#{ record_class.name }##{ @record.id } version_no is #{ @record.version_no }, " +
|
raise ("#{ record_class.name }##{ @record.id } version_no is #{ @record.version_no }, " +
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
class CreateExternalTags < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
create_table :external_tags do |t|
|
|
||||||
t.string :platform, limit: 16, null: false
|
|
||||||
t.string :name, limit: 255, null: false
|
|
||||||
t.integer :post_count, null: false, default: 0
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.index [:platform, :name], unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO
|
|
||||||
external_tags(id, platform, name, post_count, created_at)
|
|
||||||
SELECT
|
|
||||||
t.id
|
|
||||||
, 'nico' AS platform
|
|
||||||
, SUBSTR(tn.name, 6) AS name
|
|
||||||
, t.post_count
|
|
||||||
, t.created_at
|
|
||||||
FROM
|
|
||||||
tags t
|
|
||||||
INNER JOIN
|
|
||||||
tag_names tn
|
|
||||||
ON
|
|
||||||
tn.id = t.tag_name_id
|
|
||||||
AND t.category = 'nico'
|
|
||||||
SQL
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO
|
|
||||||
external_tags(id, platform, name, post_count, created_at)
|
|
||||||
SELECT
|
|
||||||
ntv.tag_id
|
|
||||||
, 'nico' AS platform
|
|
||||||
, SUBSTR(ntv.name, 6) AS name
|
|
||||||
, 0 AS post_count
|
|
||||||
, ntv.created_at
|
|
||||||
FROM
|
|
||||||
nico_tag_versions ntv
|
|
||||||
LEFT JOIN
|
|
||||||
external_tags et
|
|
||||||
ON
|
|
||||||
et.id = ntv.tag_id
|
|
||||||
WHERE
|
|
||||||
ntv.version_no = 1
|
|
||||||
AND et.id IS NULL
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
drop_table :external_tags
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
class CreatePostExternalTags < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
create_table :post_external_tags, primary_key: [:post_id, :external_tag_id] do |t|
|
|
||||||
t.references :post, null: false, index: false, foreign_key: true
|
|
||||||
t.references :external_tag, null: false, foreign_key: true
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
INSERT INTO
|
|
||||||
post_external_tags(post_id, external_tag_id, created_at)
|
|
||||||
SELECT
|
|
||||||
pt.post_id
|
|
||||||
, pt.tag_id AS external_tag_id
|
|
||||||
, pt.created_at
|
|
||||||
FROM
|
|
||||||
post_tags pt
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
pt.tag_id = t.id
|
|
||||||
AND t.category = 'nico'
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
drop_table :post_external_tags
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
class ChangeForeignKeyOnNicoTagRelations < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
remove_foreign_key :nico_tag_relations, :tags, column: :nico_tag_id
|
|
||||||
add_foreign_key :nico_tag_relations, :external_tags, column: :nico_tag_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
class MigrateExternalTags < ActiveRecord::Migration[8.0]
|
|
||||||
class MigrationPostVersion < ActiveRecord::Base
|
|
||||||
self.table_name = 'post_versions'
|
|
||||||
end
|
|
||||||
|
|
||||||
def up
|
|
||||||
x = connection.select_value(<<~SQL)
|
|
||||||
SELECT
|
|
||||||
COUNT(0)
|
|
||||||
FROM
|
|
||||||
post_tag_sections pts
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
t.id = pts.tag_id
|
|
||||||
WHERE
|
|
||||||
t.category = 'nico'
|
|
||||||
SQL
|
|
||||||
if x > 0
|
|
||||||
raise "post_tag_sections に #{ x } 件のチンカスがあります!"
|
|
||||||
end
|
|
||||||
|
|
||||||
x = connection.select_value(<<~SQL)
|
|
||||||
SELECT
|
|
||||||
COUNT(0)
|
|
||||||
FROM
|
|
||||||
materials m
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
t.id = m.tag_id
|
|
||||||
WHERE
|
|
||||||
t.category = 'nico'
|
|
||||||
SQL
|
|
||||||
if x > 0
|
|
||||||
raise "materials に #{ x } 件のチンカスがあります!"
|
|
||||||
end
|
|
||||||
|
|
||||||
x = connection.select_value(<<~SQL)
|
|
||||||
SELECT
|
|
||||||
COUNT(0)
|
|
||||||
FROM
|
|
||||||
tag_implications ti
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
t.id = ti.tag_id
|
|
||||||
OR t.id = ti.parent_tag_id
|
|
||||||
WHERE
|
|
||||||
t.category = 'nico'
|
|
||||||
SQL
|
|
||||||
if x > 0
|
|
||||||
raise "tag_implications に #{ x } 件のチンカスがあります!"
|
|
||||||
end
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
DELETE
|
|
||||||
ts
|
|
||||||
FROM
|
|
||||||
tag_similarities ts
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
t.category = 'nico'
|
|
||||||
AND (t.id = ts.tag_id
|
|
||||||
OR t.id = ts.target_tag_id)
|
|
||||||
SQL
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
DELETE
|
|
||||||
tset
|
|
||||||
FROM
|
|
||||||
theatre_skip_event_tags tset
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
t.category = 'nico'
|
|
||||||
AND t.id = tset.tag_id
|
|
||||||
SQL
|
|
||||||
|
|
||||||
remove_check_constraint :post_versions, name: 'chk_post_versions_tags_json_schema'
|
|
||||||
|
|
||||||
say_with_time 'Migrate post_versions.tags_json' do
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
MigrationPostVersion.find_each(batch_size: 500) do |version|
|
|
||||||
tags = version.tags_json.map do |tag|
|
|
||||||
if tag.fetch('category') == 'nico'
|
|
||||||
{ 'external_tag_id' => tag.fetch('id') }
|
|
||||||
else
|
|
||||||
{ 'tag_id' => tag.fetch('id'),
|
|
||||||
'version_no' => tag.fetch('version_no'),
|
|
||||||
'name' => tag.fetch('name'),
|
|
||||||
'category' => tag.fetch('category'),
|
|
||||||
'sections' => tag.fetch('sections') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
version.update_columns(tags_json: tags)
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
count
|
|
||||||
end
|
|
||||||
|
|
||||||
add_tags_json_constraint!
|
|
||||||
|
|
||||||
tag_name_ids = connection.select_values(<<~SQL)
|
|
||||||
SELECT
|
|
||||||
tag_name_id
|
|
||||||
FROM
|
|
||||||
tags
|
|
||||||
WHERE
|
|
||||||
category = 'nico'
|
|
||||||
SQL
|
|
||||||
|
|
||||||
connection.transaction do
|
|
||||||
execute <<~SQL
|
|
||||||
DELETE
|
|
||||||
pt
|
|
||||||
FROM
|
|
||||||
post_tags pt
|
|
||||||
INNER JOIN
|
|
||||||
tags t
|
|
||||||
ON
|
|
||||||
t.category = 'nico'
|
|
||||||
AND t.id = pt.tag_id
|
|
||||||
SQL
|
|
||||||
|
|
||||||
execute <<~SQL
|
|
||||||
DELETE
|
|
||||||
FROM
|
|
||||||
tags
|
|
||||||
WHERE
|
|
||||||
category = 'nico'
|
|
||||||
SQL
|
|
||||||
|
|
||||||
unless tag_name_ids.empty?
|
|
||||||
execute <<~SQL
|
|
||||||
DELETE
|
|
||||||
FROM
|
|
||||||
tag_names
|
|
||||||
WHERE
|
|
||||||
id IN (#{ tag_name_ids.join(', ') })
|
|
||||||
SQL
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
raise ActiveRecord::IrreversibleMigration, '戻せません.'
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def add_tags_json_constraint!
|
|
||||||
schema = { type: 'array',
|
|
||||||
items: { oneOf: [internal_tag_schema, external_tag_schema] } }
|
|
||||||
|
|
||||||
quoted_schema = connection.quote(JSON.generate(schema))
|
|
||||||
|
|
||||||
add_check_constraint :post_versions,
|
|
||||||
"JSON_SCHEMA_VALID(#{ quoted_schema }, tags_json)",
|
|
||||||
name: 'chk_post_versions_tags_json_schema'
|
|
||||||
end
|
|
||||||
|
|
||||||
def internal_tag_schema
|
|
||||||
{ type: 'object',
|
|
||||||
properties: { tag_id: { type: 'integer', minimum: 1 },
|
|
||||||
version_no: { type: 'integer', minimum: 1 },
|
|
||||||
name: { type: 'string', minLength: 1 },
|
|
||||||
category: { type: 'string',
|
|
||||||
enum: ['deerjikist',
|
|
||||||
'meme',
|
|
||||||
'character',
|
|
||||||
'general',
|
|
||||||
'material',
|
|
||||||
'meta'] },
|
|
||||||
sections: sections_schema },
|
|
||||||
required: ['tag_id', 'version_no', 'sections'],
|
|
||||||
additionalProperties: false }
|
|
||||||
end
|
|
||||||
|
|
||||||
def external_tag_schema
|
|
||||||
{ type: 'object',
|
|
||||||
properties: { external_tag_id: { type: 'integer', minimum: 1 } },
|
|
||||||
required: ['external_tag_id'],
|
|
||||||
additionalProperties: false }
|
|
||||||
end
|
|
||||||
|
|
||||||
def sections_schema
|
|
||||||
{ type: 'array',
|
|
||||||
items: { type: 'object',
|
|
||||||
properties: { begin_ms: { type: 'integer', minimum: 0 },
|
|
||||||
end_ms: { type: ['integer', 'null'], minimum: 0 } },
|
|
||||||
required: ['begin_ms', 'end_ms'],
|
|
||||||
additionalProperties: false } }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class CreateLanguages < ActiveRecord::Migration[8.0]
|
||||||
|
def up
|
||||||
|
create_table :languages, id: { type: :string, limit: 16 }, primary_key: :code do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.datetime :deprecated_at, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO
|
||||||
|
languages(code, name, created_at)
|
||||||
|
VALUES
|
||||||
|
('ja', '日本語', #{ connection.quote Time.current })
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :languages
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class CreateScripts < ActiveRecord::Migration[8.0]
|
||||||
|
def up
|
||||||
|
create_table :scripts, id: { type: 'CHAR(4)' }, primary_key: :code do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.datetime :deprecated_at, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO
|
||||||
|
scripts(code, name, created_at)
|
||||||
|
VALUES
|
||||||
|
('Jpan', '漢字および仮名文字', #{ connection.quote Time.current })
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :scripts
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
class CreateLocales < ActiveRecord::Migration[8.0]
|
||||||
|
def up
|
||||||
|
create_table :locales, id: { type: :string, limit: 32 }, primary_key: :code do |t|
|
||||||
|
t.string :language_code, limit: 16, null: false, index: true
|
||||||
|
t.column :script_code, 'CHAR(4)', null: false
|
||||||
|
t.string :name, null: false
|
||||||
|
t.datetime :deprecated_at, index: true
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
|
||||||
|
t.foreign_key :languages, column: :language_code, primary_key: :code
|
||||||
|
t.foreign_key :scripts, column: :script_code, primary_key: :code
|
||||||
|
end
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO
|
||||||
|
locales(code, language_code, script_code, name, created_at)
|
||||||
|
VALUES
|
||||||
|
('ja', 'ja', 'Jpan', '日本語', #{ connection.quote Time.current })
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :locales
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
class AddColumnsToTagNames < ActiveRecord::Migration[8.0]
|
||||||
|
def up
|
||||||
|
add_reference :tag_names, :tag, after: :id, foreign_key: true
|
||||||
|
add_column :tag_names, :language_code, :string,
|
||||||
|
limit: 16, null: false, after: :tag_id, default: 'ja'
|
||||||
|
add_column :tag_names, :script_code, 'CHAR(4)',
|
||||||
|
null: false, after: :name, default: 'Jpan'
|
||||||
|
add_column :tag_names, :primary_flg, :boolean,
|
||||||
|
null: false, after: :script_code, default: true
|
||||||
|
|
||||||
|
add_foreign_key :tag_names, :languages, column: :language_code, primary_key: :code
|
||||||
|
add_foreign_key :tag_names, :scripts, column: :script_code, primary_key: :code
|
||||||
|
|
||||||
|
remove_index :tag_names, :name
|
||||||
|
add_index :tag_names, [:language_code, :name], unique: true
|
||||||
|
|
||||||
|
change_column_default :tag_names, :language_code, from: 'ja', to: nil
|
||||||
|
change_column_default :tag_names, :script_code, from: 'Jpan', to: nil
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE
|
||||||
|
tag_names tn
|
||||||
|
LEFT JOIN
|
||||||
|
tags AS t
|
||||||
|
ON
|
||||||
|
t.tag_name_id = COALESCE(tn.canonical_id, tn.id)
|
||||||
|
SET
|
||||||
|
tn.tag_id = t.id
|
||||||
|
, tn.primary_flg = CASE
|
||||||
|
WHEN tn.canonical_id IS NULL THEN
|
||||||
|
1
|
||||||
|
ELSE
|
||||||
|
0
|
||||||
|
END
|
||||||
|
SQL
|
||||||
|
|
||||||
|
change_column_default :tag_names, :primary_flg, from: true, to: nil
|
||||||
|
|
||||||
|
add_column :tag_names, :primary_tag_id, :bigint,
|
||||||
|
as: 'CASE WHEN primary_flg THEN tag_id ELSE NULL END'
|
||||||
|
add_index :tag_names, [:primary_tag_id, :language_code], unique: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_index :tag_names, [:primary_tag_id, :language_code]
|
||||||
|
remove_column :tag_names, :primary_tag_id
|
||||||
|
|
||||||
|
remove_foreign_key :tag_names, column: :language_code
|
||||||
|
remove_foreign_key :tag_names, column: :script_code
|
||||||
|
|
||||||
|
remove_index :tag_names, [:language_code, :name]
|
||||||
|
add_index :tag_names, :name, unique: true
|
||||||
|
|
||||||
|
remove_column :tag_names, :primary_flg
|
||||||
|
remove_column :tag_names, :script_code
|
||||||
|
remove_column :tag_names, :language_code
|
||||||
|
remove_reference :tag_names, :tag, foreign_key: true
|
||||||
|
end
|
||||||
|
end
|
||||||
生成ファイル
+3
-20
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2026_09_21_230000) do
|
ActiveRecord::Schema[8.0].define(version: 2026_09_21_030000) do
|
||||||
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@@ -48,14 +48,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_230000) do
|
|||||||
t.index ["tag_id"], name: "index_deerjikists_on_tag_id"
|
t.index ["tag_id"], name: "index_deerjikists_on_tag_id"
|
||||||
end
|
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
|
|
||||||
t.integer "post_count", default: 0, null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["platform", "name"], name: "index_external_tags_on_platform_and_name", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "gekanator_ai_runs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "gekanator_ai_runs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.string "model", null: false
|
t.string "model", null: false
|
||||||
t.integer "input_tokens", default: 0, null: false
|
t.integer "input_tokens", default: 0, null: false
|
||||||
@@ -289,13 +281,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_230000) do
|
|||||||
t.check_constraint "`version_no` > 0", name: "nico_tag_versions_version_no_positive"
|
t.check_constraint "`version_no` > 0", name: "nico_tag_versions_version_no_positive"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "post_external_tags", primary_key: ["post_id", "external_tag_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
|
||||||
t.bigint "post_id", null: false
|
|
||||||
t.bigint "external_tag_id", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["external_tag_id"], name: "index_post_external_tags_on_external_tag_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "post_implications", primary_key: ["post_id", "parent_post_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "post_implications", primary_key: ["post_id", "parent_post_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "post_id", null: false
|
t.bigint "post_id", null: false
|
||||||
t.bigint "parent_post_id", null: false
|
t.bigint "parent_post_id", null: false
|
||||||
@@ -367,7 +352,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_230000) do
|
|||||||
t.check_constraint "(`video_ms` is null) or (`video_ms` > 0)", name: "chk_post_versions_video_ms_positive"
|
t.check_constraint "(`video_ms` is null) or (`video_ms` > 0)", name: "chk_post_versions_video_ms_positive"
|
||||||
t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')", name: "post_versions_event_type_valid"
|
t.check_constraint "`event_type` in (_utf8mb4'create',_utf8mb4'update',_utf8mb4'discard',_utf8mb4'restore')", name: "post_versions_event_type_valid"
|
||||||
t.check_constraint "`version_no` > 0", name: "post_versions_version_no_positive"
|
t.check_constraint "`version_no` > 0", name: "post_versions_version_no_positive"
|
||||||
t.check_constraint "json_schema_valid(_utf8mb4'{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"object\",\"properties\":{\"tag_id\":{\"type\":\"integer\",\"minimum\":1},\"version_no\":{\"type\":\"integer\",\"minimum\":1},\"name\":{\"type\":\"string\",\"minLength\":1},\"category\":{\"type\":\"string\",\"enum\":[\"deerjikist\",\"meme\",\"character\",\"general\",\"material\",\"meta\"]},\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"begin_ms\":{\"type\":\"integer\",\"minimum\":0},\"end_ms\":{\"type\":[\"integer\",\"null\"],\"minimum\":0}},\"required\":[\"begin_ms\",\"end_ms\"],\"additionalProperties\":false}}},\"required\":[\"tag_id\",\"version_no\",\"sections\"],\"additionalProperties\":false},{\"type\":\"object\",\"properties\":{\"external_tag_id\":{\"type\":\"integer\",\"minimum\":1}},\"required\":[\"external_tag_id\"],\"additionalProperties\":false}]}}',`tags_json`)", name: "chk_post_versions_tags_json_schema"
|
t.check_constraint "json_schema_valid(_utf8mb4'{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\",\"minimum\":1},\"version_no\":{\"type\":\"integer\",\"minimum\":1},\"name\":{\"type\":\"string\",\"minLength\":1},\"category\":{\"type\":\"string\",\"enum\":[\"deerjikist\",\"meme\",\"character\",\"general\",\"material\",\"meta\",\"nico\"]},\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"begin_ms\":{\"type\":\"integer\",\"minimum\":0},\"end_ms\":{\"type\":[\"integer\",\"null\"],\"minimum\":0}},\"required\":[\"begin_ms\",\"end_ms\"],\"additionalProperties\":false}}},\"required\":[\"id\",\"version_no\",\"name\",\"category\",\"sections\"],\"additionalProperties\":false}}',`tags_json`)", name: "chk_post_versions_tags_json_schema"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
@@ -715,11 +700,9 @@ ActiveRecord::Schema[8.0].define(version: 2026_09_21_230000) do
|
|||||||
add_foreign_key "materials", "tags"
|
add_foreign_key "materials", "tags"
|
||||||
add_foreign_key "materials", "users", column: "created_by_user_id"
|
add_foreign_key "materials", "users", column: "created_by_user_id"
|
||||||
add_foreign_key "materials", "users", column: "updated_by_user_id"
|
add_foreign_key "materials", "users", column: "updated_by_user_id"
|
||||||
add_foreign_key "nico_tag_relations", "external_tags", column: "nico_tag_id"
|
|
||||||
add_foreign_key "nico_tag_relations", "tags"
|
add_foreign_key "nico_tag_relations", "tags"
|
||||||
|
add_foreign_key "nico_tag_relations", "tags", column: "nico_tag_id"
|
||||||
add_foreign_key "nico_tag_versions", "users", column: "created_by_user_id"
|
add_foreign_key "nico_tag_versions", "users", column: "created_by_user_id"
|
||||||
add_foreign_key "post_external_tags", "external_tags"
|
|
||||||
add_foreign_key "post_external_tags", "posts"
|
|
||||||
add_foreign_key "post_implications", "posts"
|
add_foreign_key "post_implications", "posts"
|
||||||
add_foreign_key "post_implications", "posts", column: "parent_post_id"
|
add_foreign_key "post_implications", "posts", column: "parent_post_id"
|
||||||
add_foreign_key "post_similarities", "posts"
|
add_foreign_key "post_similarities", "posts"
|
||||||
|
|||||||
@@ -30,28 +30,9 @@ namespace :nico do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PostTag.where(post_id: post.id, tag_id: to_remove.to_a).find_each(&:destroy!)
|
PostTag.where(post_id: post.id, tag_id: to_remove.to_a).find_each do |pt|
|
||||||
end
|
pt.destroy!
|
||||||
|
|
||||||
def sync_post_external_tags! post, desired_external_tag_ids, current_external_tag_ids: nil
|
|
||||||
current_external_tag_ids ||=
|
|
||||||
PostExternalTag.where(post_id: post.id).pluck(:external_tag_id).to_set
|
|
||||||
desired_external_tag_ids = desired_external_tag_ids.compact.to_set
|
|
||||||
|
|
||||||
to_add = desired_external_tag_ids - current_external_tag_ids
|
|
||||||
to_remove = current_external_tag_ids - desired_external_tag_ids
|
|
||||||
|
|
||||||
ExternalTag.where(id: to_add.to_a).find_each do |external_tag|
|
|
||||||
begin
|
|
||||||
PostExternalTag.create!(post:, external_tag:)
|
|
||||||
rescue ActiveRecord::RecordNotUnique
|
|
||||||
;
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
PostExternalTag
|
|
||||||
.where(post_id: post.id, external_tag_id: to_remove.to_a)
|
|
||||||
.find_each(&:destroy!)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
mysql_user = ENV['MYSQL_USER']
|
mysql_user = ENV['MYSQL_USER']
|
||||||
@@ -130,63 +111,58 @@ namespace :nico do
|
|||||||
sync_post_tags!(post, [Tag.tagme.id, Tag.bot.id, Tag.niconico.id, Tag.video.id])
|
sync_post_tags!(post, [Tag.tagme.id, Tag.bot.id, Tag.niconico.id, Tag.video.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
tags = post.tags
|
||||||
# 既存のタグ Id. 集合
|
# 既存のタグ Id. 集合
|
||||||
kept_tag_ids = post.tags.pluck(:id).to_set
|
kept_tag_ids = tags.pluck(:id).to_set
|
||||||
|
# うち内部タグ Id. 集合
|
||||||
# 既存の外部タグ Id. 集合
|
kept_non_nico_tag_ids = tags.not_nico.pluck(:id).to_set
|
||||||
kept_external_tag_ids = post.external_tags.pluck(:id).to_set
|
|
||||||
|
|
||||||
# 記載すべき外部タグ Id. のリスト
|
|
||||||
desired_external_tag_ids = []
|
|
||||||
|
|
||||||
|
# 記載すべき外部タグ Id. および連携される内部タグ Id. のリスト
|
||||||
|
desired_nico_tag_based_ids = []
|
||||||
# 記載すべき内部タグ Id. のリスト
|
# 記載すべき内部タグ Id. のリスト
|
||||||
desired_tag_ids = kept_tag_ids.to_a
|
desired_non_nico_tag_ids = []
|
||||||
|
|
||||||
datum['tags'].each do |raw|
|
datum['tags'].each do |raw|
|
||||||
tag = ExternalTag.find_or_create_by!(platform: :nico, name: raw)
|
name = TagNameSanitisationRule.sanitise("nico:#{ raw }")
|
||||||
|
tag = Tag.find_or_create_by_tag_name!(Locale.nipponese, name, category: :nico)
|
||||||
|
|
||||||
unless tag.nico_tag_versions.exists?
|
event_type = tag.nico_tag_versions.exists? ? :update : :create
|
||||||
NicoTagVersionRecorder.record!(external_tag: tag,
|
NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user: nil)
|
||||||
event_type: :create,
|
|
||||||
created_by_user: nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
desired_external_tag_ids << tag.id
|
desired_nico_tag_based_ids << tag.id
|
||||||
|
|
||||||
# 新たに記載される外部タグと連携される内部タグを記載
|
# 新たに記載される外部タグと連携される内部タグを記載
|
||||||
# 連携タグは記載すれども消除せず.
|
unless tag.id.in?(kept_tag_ids)
|
||||||
unless tag.id.in?(kept_external_tag_ids)
|
linked_ids = tag.linked_tags.pluck(:id)
|
||||||
desired_tag_ids.concat(tag.linked_tags.pluck(:id))
|
desired_non_nico_tag_ids.concat(linked_ids)
|
||||||
|
desired_nico_tag_based_ids.concat(linked_ids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
deerjikist = Deerjikist.find_by(platform: :nico, code: datum['user'])
|
deerjikist = Deerjikist.find_by(platform: :nico, code: datum['user'])
|
||||||
if deerjikist
|
if deerjikist
|
||||||
desired_tag_ids << deerjikist.tag_id
|
desired_non_nico_tag_ids << deerjikist.tag_id
|
||||||
elsif !(Tag.where(id: kept_tag_ids).where(category: :deerjikist).exists?)
|
desired_nico_tag_based_ids << deerjikist.tag_id
|
||||||
desired_tag_ids << Tag.no_deerjikist.id
|
elsif !(Tag.where(id: kept_non_nico_tag_ids).where(category: :deerjikist).exists?)
|
||||||
|
desired_non_nico_tag_ids << Tag.no_deerjikist.id
|
||||||
|
desired_nico_tag_based_ids << Tag.no_deerjikist.id
|
||||||
end
|
end
|
||||||
|
|
||||||
desired_external_tag_ids.uniq!
|
desired_nico_tag_based_ids.uniq!
|
||||||
desired_tag_ids.uniq!
|
|
||||||
|
|
||||||
# 外部タグの記載に際しては “bot 操作” タグを記載しなぃ.
|
desired_all_tag_ids = kept_non_nico_tag_ids.to_a + desired_nico_tag_based_ids
|
||||||
if kept_tag_ids != desired_tag_ids.to_set
|
desired_non_nico_tag_ids.concat(kept_non_nico_tag_ids.to_a)
|
||||||
desired_tag_ids << Tag.bot.id
|
desired_non_nico_tag_ids.uniq!
|
||||||
desired_tag_ids.uniq!
|
if kept_non_nico_tag_ids != desired_non_nico_tag_ids.to_set
|
||||||
|
desired_all_tag_ids << Tag.bot.id
|
||||||
end
|
end
|
||||||
|
desired_all_tag_ids.uniq!
|
||||||
|
|
||||||
tags_changed =
|
sync_post_tags!(post, desired_all_tag_ids, current_tag_ids: kept_tag_ids)
|
||||||
kept_tag_ids != desired_tag_ids.to_set ||
|
|
||||||
kept_external_tag_ids != desired_external_tag_ids.to_set
|
|
||||||
|
|
||||||
sync_post_tags!(post, desired_tag_ids, current_tag_ids: kept_tag_ids)
|
|
||||||
sync_post_external_tags!(post, desired_external_tag_ids,
|
|
||||||
current_external_tag_ids: kept_external_tag_ids)
|
|
||||||
|
|
||||||
if post_created
|
if post_created
|
||||||
PostVersionRecorder.record!(post:, event_type: :create, created_by_user: nil)
|
PostVersionRecorder.record!(post:, event_type: :create, created_by_user: nil)
|
||||||
elsif post_changed || tags_changed
|
elsif post_changed || kept_tag_ids != desired_all_tag_ids.to_set
|
||||||
PostVersionRecorder.ensure_snapshot!(post, created_by_user: nil)
|
PostVersionRecorder.ensure_snapshot!(post, created_by_user: nil)
|
||||||
PostVersionRecorder.record!(post:, event_type: :update, created_by_user: nil)
|
PostVersionRecorder.record!(post:, event_type: :update, created_by_user: nil)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -378,119 +378,4 @@ RSpec.describe 'nico:sync' do
|
|||||||
expect(versions.second.title).to eq('changed title')
|
expect(versions.second.title).to eq('changed title')
|
||||||
expect(versions.second.tags).to eq(snapshot_tags(post.reload))
|
expect(versions.second.tags).to eq(snapshot_tags(post.reload))
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_external_tag!(name)
|
|
||||||
ExternalTag.create!(platform: :nico, name:)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_nico_sync_post!
|
|
||||||
post = Post.create!(
|
|
||||||
title: 't',
|
|
||||||
url: 'https://www.nicovideo.jp/watch/sm9',
|
|
||||||
uploaded_user: nil
|
|
||||||
)
|
|
||||||
|
|
||||||
PostTag.create!(post:, tag: Tag.no_deerjikist)
|
|
||||||
|
|
||||||
post
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_nico_sync_with_tags! tags
|
|
||||||
stub_python([{
|
|
||||||
'code' => 'sm9',
|
|
||||||
'title' => 't',
|
|
||||||
'tags' => tags,
|
|
||||||
'user' => nil
|
|
||||||
}])
|
|
||||||
|
|
||||||
allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
|
|
||||||
|
|
||||||
run_rake_task('nico:sync')
|
|
||||||
end
|
|
||||||
|
|
||||||
it '外部タグが新規記載されたとき,その時点の連携タグを記載する' do
|
|
||||||
post = create_nico_sync_post!
|
|
||||||
|
|
||||||
external_tag = create_external_tag!('AAA')
|
|
||||||
linked_tag = create_tag!('spec_linked', category: :general)
|
|
||||||
link_nico_to_tag!(external_tag, linked_tag)
|
|
||||||
|
|
||||||
run_nico_sync_with_tags!(['AAA'])
|
|
||||||
|
|
||||||
expect(post.reload.external_tags).to include(external_tag)
|
|
||||||
expect(post.tags).to include(linked_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it '外部タグに差分がない場合,内外マッピングが変はっても連携タグを再評価しない' do
|
|
||||||
post = create_nico_sync_post!
|
|
||||||
|
|
||||||
external_tag = create_external_tag!('AAA')
|
|
||||||
old_linked_tag = create_tag!('spec_old_linked', category: :general)
|
|
||||||
new_linked_tag = create_tag!('spec_new_linked', category: :general)
|
|
||||||
|
|
||||||
relation = link_nico_to_tag!(external_tag, old_linked_tag)
|
|
||||||
|
|
||||||
run_nico_sync_with_tags!(['AAA'])
|
|
||||||
|
|
||||||
expect(post.reload.tags).to include(old_linked_tag)
|
|
||||||
|
|
||||||
# 人手で連携タグを消除する.
|
|
||||||
PostTag.find_by!(post:, tag: old_linked_tag).destroy!
|
|
||||||
|
|
||||||
# 内外マッピングを変更する.
|
|
||||||
relation.destroy!
|
|
||||||
link_nico_to_tag!(external_tag, new_linked_tag)
|
|
||||||
|
|
||||||
# 外部タグ自体には差分が無い.
|
|
||||||
run_nico_sync_with_tags!(['AAA'])
|
|
||||||
|
|
||||||
post.reload
|
|
||||||
|
|
||||||
expect(post.external_tags).to include(external_tag)
|
|
||||||
expect(post.tags).not_to include(old_linked_tag)
|
|
||||||
expect(post.tags).not_to include(new_linked_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
it '外部タグの消除では連携タグを消除せず,再記載時にその時点の内外マッピングを適用する' do
|
|
||||||
post = create_nico_sync_post!
|
|
||||||
|
|
||||||
external_tag = create_external_tag!('AAA')
|
|
||||||
old_linked_tag = create_tag!('spec_old_linked', category: :general)
|
|
||||||
new_linked_tag = create_tag!('spec_new_linked', category: :general)
|
|
||||||
|
|
||||||
relation = link_nico_to_tag!(external_tag, old_linked_tag)
|
|
||||||
|
|
||||||
# 外部タグを新規記載する.
|
|
||||||
run_nico_sync_with_tags!(['AAA'])
|
|
||||||
|
|
||||||
post.reload
|
|
||||||
expect(post.external_tags).to include(external_tag)
|
|
||||||
expect(post.tags).to include(old_linked_tag)
|
|
||||||
|
|
||||||
# 外部タグを消除する.
|
|
||||||
run_nico_sync_with_tags!([])
|
|
||||||
|
|
||||||
post.reload
|
|
||||||
expect(post.external_tags).not_to include(external_tag)
|
|
||||||
|
|
||||||
# 外部タグの消除によって連携タグまでは消除されない.
|
|
||||||
expect(post.tags).to include(old_linked_tag)
|
|
||||||
|
|
||||||
# 外部タグが記載されてゐない間に内外マッピングを変更する.
|
|
||||||
relation.destroy!
|
|
||||||
link_nico_to_tag!(external_tag, new_linked_tag)
|
|
||||||
|
|
||||||
# 同じ外部タグを再記載する.
|
|
||||||
run_nico_sync_with_tags!(['AAA'])
|
|
||||||
|
|
||||||
post.reload
|
|
||||||
|
|
||||||
expect(post.external_tags).to include(external_tag)
|
|
||||||
|
|
||||||
# 旧連携タグは自動的には消除されない.
|
|
||||||
expect(post.tags).to include(old_linked_tag)
|
|
||||||
|
|
||||||
# 再記載時点の内外マッピングが新たに適用される.
|
|
||||||
expect(post.tags).to include(new_linked_tag)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする