**本番 DB のバックアップをかならずすること** Reviewed-on: #420 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #420 でマージされました。
このコミットが含まれているのは:
@@ -15,30 +15,35 @@ class NicoTagsController < ApplicationController
|
||||
limit = 1 if limit < 1
|
||||
|
||||
post_tag_max_sql =
|
||||
PostTag
|
||||
.select('tag_id, MAX(created_at) AS max_created_at')
|
||||
.group('tag_id')
|
||||
PostExternalTag
|
||||
.select('external_tag_id, MAX(created_at) AS max_created_at')
|
||||
.group('external_tag_id')
|
||||
.to_sql
|
||||
|
||||
q = Tag.nico_tags
|
||||
.joins(:tag_name)
|
||||
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
||||
'ON post_tag_max.tag_id = tags.id')
|
||||
.includes(:tag_name, tag_name: :wiki_page, linked_tags: { tag_name: :wiki_page })
|
||||
q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name
|
||||
q =
|
||||
ExternalTag
|
||||
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
||||
'ON post_tag_max.external_tag_id = external_tags.id')
|
||||
.includes(linked_tags: { tag_name: :wiki_page })
|
||||
if name
|
||||
q = q.where(('external_tags.name LIKE ? ' +
|
||||
"OR CONCAT(external_tags.platform, ':', external_tags.name) LIKE ?"),
|
||||
"%#{ name }%", "%#{ name }")
|
||||
end
|
||||
|
||||
if linked_tag
|
||||
linked_tag_ids =
|
||||
Tag
|
||||
.joins(:tag_name)
|
||||
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
||||
.pluck(:id)
|
||||
Tag
|
||||
.joins(:tag_name)
|
||||
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
||||
.pluck(:id)
|
||||
linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
|
||||
q = q.where(id: linked_nico_tag_ids)
|
||||
end
|
||||
if link_status.in?(['linked', 'unlinked'])
|
||||
exists_sql =
|
||||
'EXISTS (SELECT 1 FROM nico_tag_relations ' \
|
||||
'WHERE nico_tag_relations.nico_tag_id = tags.id)'
|
||||
'WHERE nico_tag_relations.nico_tag_id = external_tags.id)'
|
||||
q = link_status == 'linked' ? q.where(exists_sql) : q.where("NOT #{ exists_sql }")
|
||||
end
|
||||
|
||||
@@ -46,34 +51,39 @@ class NicoTagsController < ApplicationController
|
||||
sort_sql =
|
||||
case order[0]
|
||||
when 'name'
|
||||
'tag_names.name'
|
||||
'external_tags.name'
|
||||
when 'updated_at'
|
||||
'post_tag_max.max_created_at'
|
||||
else
|
||||
"tags.#{ order[0] }"
|
||||
"external_tags.#{ order[0] }"
|
||||
end
|
||||
tags = q.reselect('tags.*',
|
||||
tags = q.reselect('external_tags.*',
|
||||
Arel.sql('post_tag_max.max_created_at AS recent_post_tag_created_at'))
|
||||
.order(Arel.sql("#{ sort_sql } #{ order[1] }, tags.id #{ order[1] }"))
|
||||
.order(Arel.sql("#{ sort_sql } #{ order[1] }, external_tags.id #{ order[1] }"))
|
||||
.limit(limit)
|
||||
.offset((page - 1) * limit)
|
||||
.to_a
|
||||
|
||||
render json: { tags: tags.map { |tag|
|
||||
TagRepr.base(tag).merge(
|
||||
external_tag_json(tag).merge(
|
||||
recent_post_tag_created_at: tag.recent_post_tag_created_at,
|
||||
linked_tags: tag.linked_tags.map { |lt| TagRepr.base(lt) })
|
||||
}, count: }
|
||||
end
|
||||
|
||||
def show
|
||||
tag = ExternalTag.find(params[:id])
|
||||
|
||||
render json: external_tag_json(tag)
|
||||
end
|
||||
|
||||
def update
|
||||
return head :unauthorized unless current_user
|
||||
return head :forbidden unless current_user.gte_member?
|
||||
|
||||
id = params[:id].to_i
|
||||
|
||||
tag = Tag.find(id)
|
||||
return render_bad_request('ニコニコ・タグを指定してください.') unless tag.nico?
|
||||
tag = ExternalTag.find(id)
|
||||
|
||||
linked_tag_names = params[:tags].to_s.split
|
||||
linked_tags = nil
|
||||
@@ -81,16 +91,15 @@ class NicoTagsController < ApplicationController
|
||||
ApplicationRecord.transaction do
|
||||
linked_tags = Tag.normalise_tags!(linked_tag_names, 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)
|
||||
|
||||
tag.linked_tags = linked_tags
|
||||
tag.save!
|
||||
|
||||
NicoTagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user)
|
||||
NicoTagVersionRecorder.record!(external_tag: tag,
|
||||
event_type: :update,
|
||||
created_by_user: current_user)
|
||||
end
|
||||
|
||||
render json: tag.linked_tags.map { |t| TagRepr.base(t) }, status: :ok
|
||||
@@ -102,6 +111,21 @@ class NicoTagsController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def external_tag_json tag
|
||||
{ id: tag.id,
|
||||
name: "#{ tag.platform }:#{ tag.name }",
|
||||
category: 'nico',
|
||||
post_count: tag.post_count,
|
||||
created_at: tag.created_at,
|
||||
updated_at: tag.created_at,
|
||||
deprecated_at: nil,
|
||||
aliases: [],
|
||||
parents: [],
|
||||
has_wiki: false,
|
||||
material_id: nil,
|
||||
has_deerjikists: false }
|
||||
end
|
||||
|
||||
def render_nico_tag_form_record_invalid record
|
||||
if record.is_a?(TagName) || record.is_a?(Tag)
|
||||
render_validation_error fields: { tags: record.errors.full_messages.map { |message|
|
||||
|
||||
@@ -2,6 +2,9 @@ class PostVersionsController < ApplicationController
|
||||
def index
|
||||
post_id = params[:post].presence
|
||||
tag_id = params[:tag].presence&.to_i
|
||||
external_tag_id = params[:external_tag].presence&.to_i
|
||||
return head :bad_request if tag_id && external_tag_id
|
||||
|
||||
page = (params[:page].presence || 1).to_i
|
||||
limit = (params[:limit].presence || 20).to_i
|
||||
|
||||
@@ -26,9 +29,14 @@ class PostVersionsController < ApplicationController
|
||||
'prev.original_created_from AS prev_original_created_from',
|
||||
'prev.original_created_before AS prev_original_created_before')
|
||||
q = q.where('post_versions.post_id = ?', post_id) if post_id
|
||||
if tag_id
|
||||
q = q.where("JSON_CONTAINS(post_versions.tags_json, JSON_OBJECT('id', #{ tag_id })) " +
|
||||
"OR JSON_CONTAINS(prev.tags_json, JSON_OBJECT('id', #{ tag_id }))")
|
||||
if external_tag_id || (tag_id && !(Tag.exists?(id: tag_id)))
|
||||
q = q.where('JSON_CONTAINS(post_versions.tags_json,' +
|
||||
"JSON_OBJECT('external_tag_id', #{ external_tag_id || tag_id })) " +
|
||||
'OR JSON_CONTAINS(prev.tags_json,' +
|
||||
"JSON_OBJECT('external_tag_id', #{ external_tag_id || tag_id }))")
|
||||
elsif tag_id
|
||||
q = q.where("JSON_CONTAINS(post_versions.tags_json, JSON_OBJECT('tag_id', #{ tag_id })) " +
|
||||
"OR JSON_CONTAINS(prev.tags_json, JSON_OBJECT('tag_id', #{ tag_id }))")
|
||||
end
|
||||
|
||||
count = q.except(:select, :order, :limit, :offset).count
|
||||
@@ -43,50 +51,19 @@ class PostVersionsController < ApplicationController
|
||||
private
|
||||
|
||||
def serialise_versions rows
|
||||
rows = rows.to_a
|
||||
user_ids = rows.map(&:created_by_user_id).compact.uniq
|
||||
users_by_id = User.where(id: user_ids).pluck(:id, :name).to_h
|
||||
snapshots = rows.flat_map { |row|
|
||||
[normalise_json(row.tags_json),
|
||||
normalise_json(row.attributes['prev_tags_json']) || []]
|
||||
}
|
||||
external_tag_names = external_tag_names_for(snapshots)
|
||||
|
||||
rows.map do |row|
|
||||
cur_tags =
|
||||
normalise_json(row.tags_json)
|
||||
.sort_by { [(case _1.fetch('category')
|
||||
when 'deerjikist'
|
||||
0
|
||||
when 'meme'
|
||||
1
|
||||
when 'character'
|
||||
2
|
||||
when 'general'
|
||||
3
|
||||
when 'material'
|
||||
4
|
||||
when 'meta'
|
||||
5
|
||||
else
|
||||
6
|
||||
end),
|
||||
_1.fetch('name').downcase] }
|
||||
.map { Post.tag_snapshot_literal(_1) }
|
||||
prev_tags =
|
||||
(normalise_json(row.attributes['prev_tags_json']) || [])
|
||||
.sort_by { [(case _1.fetch('category')
|
||||
when 'deerjikist'
|
||||
0
|
||||
when 'meme'
|
||||
1
|
||||
when 'character'
|
||||
2
|
||||
when 'general'
|
||||
3
|
||||
when 'material'
|
||||
4
|
||||
when 'meta'
|
||||
5
|
||||
else
|
||||
6
|
||||
end),
|
||||
_1.fetch('name').downcase] }
|
||||
.map { Post.tag_snapshot_literal(_1) }
|
||||
cur_tags = snapshot_tag_literals(normalise_json(row.tags_json), external_tag_names)
|
||||
prev_tags = snapshot_tag_literals(
|
||||
normalise_json(row.attributes['prev_tags_json']) || [], external_tag_names)
|
||||
|
||||
{ post_id: row.post_id,
|
||||
version_no: row.version_no,
|
||||
@@ -113,21 +90,52 @@ class PostVersionsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def build_version_tags(cur_tags, prev_tags)
|
||||
def external_tag_names_for snapshots
|
||||
ids = snapshots.flatten.filter_map { _1['external_tag_id'] }.uniq
|
||||
names = ExternalTag.where(id: ids).pluck(:id, :platform, :name).to_h { |id, platform, name|
|
||||
[id, "#{ platform }:#{ name }"]
|
||||
}
|
||||
missing_ids = ids - names.keys
|
||||
|
||||
NicoTagVersion
|
||||
.where(tag_id: missing_ids)
|
||||
.order(:tag_id, version_no: :desc)
|
||||
.pluck(:tag_id, :name)
|
||||
.each { |id, name| names[id] ||= name }
|
||||
|
||||
names
|
||||
end
|
||||
|
||||
def snapshot_tag_literals snapshots, external_tag_names
|
||||
snapshots
|
||||
.filter_map { |snapshot|
|
||||
if snapshot.key?('tag_id')
|
||||
[tag_category_order(snapshot['category']),
|
||||
Post.tag_snapshot_literal(snapshot)]
|
||||
elsif external_tag_names[snapshot['external_tag_id']]
|
||||
[6, external_tag_names[snapshot['external_tag_id']]]
|
||||
end
|
||||
}
|
||||
.sort_by { |order, name| [order, name.downcase] }
|
||||
.map(&:second)
|
||||
end
|
||||
|
||||
def tag_category_order category
|
||||
['deerjikist', 'meme', 'character', 'general', 'material', 'meta'].index(category) || 6
|
||||
end
|
||||
|
||||
def build_version_tags cur_tags, prev_tags
|
||||
(cur_tags | prev_tags).map do |name|
|
||||
type =
|
||||
if cur_tags.include?(name) && prev_tags.include?(name)
|
||||
'context'
|
||||
elsif cur_tags.include?(name)
|
||||
'added'
|
||||
else
|
||||
'removed'
|
||||
end
|
||||
if cur_tags.include?(name) && prev_tags.include?(name)
|
||||
'context'
|
||||
elsif cur_tags.include?(name)
|
||||
'added'
|
||||
else
|
||||
'removed'
|
||||
end
|
||||
|
||||
{
|
||||
name:,
|
||||
type:
|
||||
}
|
||||
{ name:, type: }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,7 +49,7 @@ class PostsController < ApplicationController
|
||||
filtered_posts
|
||||
.joins("LEFT JOIN (#{ pt_max_sql }) pt_max ON pt_max.post_id = posts.id")
|
||||
.reselect('posts.*', Arel.sql("#{ updated_at_all_sql } AS updated_at_all"))
|
||||
.preload(:uploaded_user, :parents, :children,
|
||||
.preload(:external_tags, :uploaded_user, :parents, :children,
|
||||
post_tags: [:sections, { tag: [:deerjikists, :materials,
|
||||
{ tag_name: :wiki_page }] }])
|
||||
.with_attached_thumbnail
|
||||
@@ -102,12 +102,14 @@ class PostsController < ApplicationController
|
||||
end
|
||||
|
||||
def random
|
||||
post = filtered_posts.preload(:uploaded_user, :parents, :children,
|
||||
post_tags: [:sections, { tag: [:deerjikists, :materials,
|
||||
{ tag_name: :wiki_page }] }])
|
||||
.with_attached_thumbnail
|
||||
.order('RAND()')
|
||||
.first
|
||||
post =
|
||||
filtered_posts
|
||||
.preload(:uploaded_user, :parents, :children,
|
||||
post_tags: [:sections, { tag: [:deerjikists, :materials,
|
||||
{ tag_name: :wiki_page }] }])
|
||||
.with_attached_thumbnail
|
||||
.order('RAND()')
|
||||
.first
|
||||
return head :not_found unless post
|
||||
|
||||
render json: PostRepr.base(post, current_user)
|
||||
@@ -426,8 +428,20 @@ class PostsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def tagged_post_ids_for(name) =
|
||||
Post.joins(tags: :tag_name).where(tag_names: { name: }).select(:id)
|
||||
def tagged_post_ids_for(name)
|
||||
posts_by_internal_tags =
|
||||
Post
|
||||
.joins(tags: :tag_name)
|
||||
.where(tag_names: { name: })
|
||||
.select(:id)
|
||||
|
||||
posts_by_external_tags =
|
||||
Post
|
||||
.joins(:external_tags)
|
||||
.where("CONCAT(external_tags.platform, ':', external_tags.name) = ?", name)
|
||||
|
||||
(posts_by_internal_tags + posts_by_external_tags).uniq(&:id)
|
||||
end
|
||||
|
||||
def sync_post_tags! post, desired_tags, sections
|
||||
desired_tags.each do |t|
|
||||
@@ -505,7 +519,10 @@ class PostsController < ApplicationController
|
||||
memo[tag_id] = TagRepr.inline(tag).merge(children:, sections:)
|
||||
end
|
||||
|
||||
root_ids.filter_map { |id| build_node.call(id, []) }
|
||||
internal_tags = root_ids.filter_map { |id| build_node.call(id, []) }
|
||||
external_tags =
|
||||
post.external_tags.map { ExternalTagRepr.inline(_1).merge(children: [], sections: []) }
|
||||
internal_tags + external_tags
|
||||
end
|
||||
|
||||
def sibling_posts_by_parent parent_post_ids
|
||||
@@ -653,7 +670,7 @@ class PostsController < ApplicationController
|
||||
|
||||
def editable_tag_names_from_version version
|
||||
version.tags_json
|
||||
.reject { _1.fetch('category') == 'nico' }
|
||||
.select { _1.key?('tag_id') }
|
||||
.map { Post.tag_snapshot_literal(_1) }
|
||||
.sort
|
||||
end
|
||||
@@ -671,7 +688,6 @@ class PostsController < ApplicationController
|
||||
post
|
||||
.post_tags
|
||||
.joins(tag: :tag_name)
|
||||
.merge(Tag.not_nico)
|
||||
.merge(Tag.where(deprecated_at: nil))
|
||||
.includes(:sections, tag: :tag_name)
|
||||
.order('tag_names.name')
|
||||
@@ -689,8 +705,7 @@ class PostsController < ApplicationController
|
||||
tag_names:, video_ms_param:, duration_param:, parent_post_ids:
|
||||
validate_original_created_values!(original_created_from, original_created_before)
|
||||
Tag.normalise_tags!(tag_names, with_tagme: false, deny_deprecated: true,
|
||||
with_sections: true) =>
|
||||
{ tags:, sections: }
|
||||
with_sections: true) => { tags:, sections: }
|
||||
|
||||
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
||||
video_ms = normalise_video_ms(tags, video_ms_param:, duration_param:)
|
||||
@@ -833,15 +848,12 @@ class PostsController < ApplicationController
|
||||
original_created_from: snapshot[:original_created_from],
|
||||
original_created_before: snapshot[:original_created_before])
|
||||
|
||||
Tag.normalise_tags!(snapshot[:tag_names], with_tagme: false,
|
||||
deny_deprecated: true,
|
||||
with_sections: true) =>
|
||||
{ tags: editable_tags, sections: }
|
||||
TagVersioning.record_tag_snapshots!(editable_tags, created_by_user: current_user)
|
||||
Tag.normalise_tags!(snapshot[:tag_names],
|
||||
with_tagme: false,
|
||||
deny_deprecated: true,
|
||||
with_sections: true) => { tags:, sections: }
|
||||
TagVersioning.record_tag_snapshots!(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?)
|
||||
|
||||
post.video_ms = tags.any? { _1.id == Tag.video.id } ? snapshot[:video_ms] : nil
|
||||
|
||||
@@ -10,7 +10,6 @@ class TagChildrenController < ApplicationController
|
||||
|
||||
parent = Tag.find(parent_id)
|
||||
child = Tag.find(child_id)
|
||||
return render_bad_request('ニコニコ・タグの階層は変更できません.') if parent.nico? || child.nico?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
||||
@@ -33,7 +32,6 @@ class TagChildrenController < ApplicationController
|
||||
|
||||
parent = Tag.find(parent_id)
|
||||
child = Tag.find(child_id)
|
||||
return render_bad_request('ニコニコ・タグの階層は変更できません.') if parent.nico? || child.nico?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
TagVersioning.ensure_snapshot!(child, created_by_user: current_user)
|
||||
|
||||
@@ -34,49 +34,155 @@ class TagsController < ApplicationController
|
||||
|
||||
offset = (page - 1) * limit
|
||||
|
||||
q =
|
||||
tags =
|
||||
if post_id.present?
|
||||
Tag.joins(:posts, :tag_name)
|
||||
Tag.joins(:posts, :tag_name).where(posts: { id: post_id })
|
||||
else
|
||||
Tag.joins(:tag_name)
|
||||
end
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
q = q.where(posts: { id: post_id }) if post_id.present?
|
||||
|
||||
q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name
|
||||
q = q.where(category:) if category
|
||||
q = q.where('tags.post_count >= ?', post_count_between[0]) if post_count_between[0]
|
||||
q = q.where('tags.post_count <= ?', post_count_between[1]) if post_count_between[1]
|
||||
q = q.where('tags.created_at >= ?', created_between[0]) if created_between[0]
|
||||
q = q.where('tags.created_at <= ?', created_between[1]) if created_between[1]
|
||||
q = q.where('tags.updated_at >= ?', updated_between[0]) if updated_between[0]
|
||||
q = q.where('tags.updated_at <= ?', updated_between[1]) if updated_between[1]
|
||||
if deprecated_given
|
||||
q = deprecated ? q.where.not(deprecated_at: nil) : q.where(deprecated_at: nil)
|
||||
external_tags =
|
||||
if post_id.present?
|
||||
ExternalTag.joins(:posts).where(posts: { id: post_id })
|
||||
else
|
||||
ExternalTag.all
|
||||
end
|
||||
|
||||
if name
|
||||
tags = tags.where('tag_names.name LIKE ?', "%#{ name }%")
|
||||
external_tags =
|
||||
external_tags.where("CONCAT(external_tags.platform, ':', external_tags.name) LIKE ?",
|
||||
"%#{ name }%")
|
||||
end
|
||||
|
||||
if category == 'nico'
|
||||
tags = tags.none
|
||||
elsif category
|
||||
tags = tags.where(category:)
|
||||
external_tags = external_tags.none
|
||||
end
|
||||
|
||||
if post_count_between[0]
|
||||
tags = tags.where('tags.post_count >= ?', post_count_between[0])
|
||||
external_tags = external_tags.where('external_tags.post_count >= ?', post_count_between[0])
|
||||
end
|
||||
|
||||
if post_count_between[1]
|
||||
tags = tags.where('tags.post_count <= ?', post_count_between[1])
|
||||
external_tags = external_tags.where('external_tags.post_count <= ?', post_count_between[1])
|
||||
end
|
||||
|
||||
if created_between[0]
|
||||
tags = tags.where('tags.created_at >= ?', created_between[0])
|
||||
external_tags = external_tags.where('external_tags.created_at >= ?', created_between[0])
|
||||
end
|
||||
|
||||
if created_between[1]
|
||||
tags = tags.where('tags.created_at <= ?', created_between[1])
|
||||
external_tags = external_tags.where('external_tags.created_at <= ?', created_between[1])
|
||||
end
|
||||
|
||||
if updated_between[0]
|
||||
tags = tags.where('tags.updated_at >= ?', updated_between[0])
|
||||
external_tags = external_tags.where('external_tags.created_at >= ?', updated_between[0])
|
||||
end
|
||||
|
||||
if updated_between[1]
|
||||
tags = tags.where('tags.updated_at <= ?', updated_between[1])
|
||||
external_tags = external_tags.where('external_tags.created_at <= ?', updated_between[1])
|
||||
end
|
||||
|
||||
if deprecated_given
|
||||
if deprecated
|
||||
tags = tags.where.not(deprecated_at: nil)
|
||||
external_tags = external_tags.none
|
||||
else
|
||||
tags = tags.where(deprecated_at: nil)
|
||||
end
|
||||
end
|
||||
|
||||
tag_sql = tags.select("'tag' AS source",
|
||||
'tags.id',
|
||||
'tag_names.name',
|
||||
'tags.category',
|
||||
'tags.post_count',
|
||||
'tags.created_at',
|
||||
'tags.updated_at',
|
||||
'tags.deprecated_at').to_sql
|
||||
|
||||
external_tag_sql = external_tags.select(
|
||||
"'external_tag' AS source",
|
||||
'external_tags.id',
|
||||
"CONCAT(external_tags.platform, ':', external_tags.name) AS name",
|
||||
"'nico' AS category",
|
||||
'external_tags.post_count',
|
||||
'external_tags.created_at',
|
||||
'external_tags.created_at AS updated_at',
|
||||
'NULL AS deprecated_at').to_sql
|
||||
|
||||
union_sql = "#{ tag_sql } UNION ALL #{ external_tag_sql }"
|
||||
|
||||
sort_sql =
|
||||
case order[0]
|
||||
when 'name'
|
||||
'tag_names.name'
|
||||
when 'category'
|
||||
'CASE tags.category ' +
|
||||
if order[0] == 'category'
|
||||
'CASE category ' +
|
||||
"WHEN 'deerjikist' THEN 0 " +
|
||||
"WHEN 'meme' THEN 1 " +
|
||||
"WHEN 'character' THEN 2 " +
|
||||
"WHEN 'general' THEN 3 " +
|
||||
"WHEN 'material' THEN 4 " +
|
||||
"WHEN 'meta' THEN 5 " +
|
||||
"WHEN 'nico' THEN 6 END"
|
||||
"WHEN 'nico' THEN 6 " +
|
||||
'END'
|
||||
else
|
||||
"tags.#{ order[0] }"
|
||||
order[0]
|
||||
end
|
||||
tags = q.order(Arel.sql("#{ sort_sql } #{ order[1] }, tags.id #{ order[1] }"))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.to_a
|
||||
|
||||
render json: { tags: TagRepr.many(tags), count: q.size }
|
||||
connection = ApplicationRecord.connection
|
||||
|
||||
count = connection.select_value(<<~SQL)
|
||||
SELECT
|
||||
COUNT(0)
|
||||
FROM
|
||||
(#{ union_sql }) legacy_tags
|
||||
SQL
|
||||
|
||||
rows = connection.select_all(<<~SQL).to_a
|
||||
SELECT
|
||||
source
|
||||
, id
|
||||
FROM
|
||||
(#{ union_sql }) legacy_tags
|
||||
ORDER BY
|
||||
#{ sort_sql } #{ order[1] }
|
||||
, id #{ order[1] }
|
||||
, source #{ order[1] }
|
||||
LIMIT
|
||||
#{ limit }
|
||||
OFFSET
|
||||
#{ offset }
|
||||
SQL
|
||||
|
||||
tag_ids = rows.filter { _1['source'] == 'tag' }.map { _1['id'] }
|
||||
external_tag_ids = rows.filter { _1['source'] == 'external_tag' }.map { _1['id'] }
|
||||
|
||||
tags_by_id =
|
||||
Tag
|
||||
.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.where(id: tag_ids)
|
||||
.index_by(&:id)
|
||||
external_tags_by_id =
|
||||
ExternalTag
|
||||
.where(id: external_tag_ids)
|
||||
.index_by(&:id)
|
||||
|
||||
render json: { tags: rows.map { |row|
|
||||
if row['source'] == 'tag'
|
||||
TagRepr.base(tags_by_id.fetch(row['id']))
|
||||
else
|
||||
external_tag_json(external_tags_by_id.fetch(row['id']))
|
||||
end
|
||||
}, count: }
|
||||
end
|
||||
|
||||
def with_depth
|
||||
@@ -100,13 +206,14 @@ class TagsController < ApplicationController
|
||||
|
||||
def autocomplete
|
||||
q = params[:q].to_s.strip.sub(/\Anot:/i, '')
|
||||
prefix = "#{ ActiveRecord::Base.sanitize_sql_like(q) }%"
|
||||
|
||||
with_nico = bool?(:nico, default: true)
|
||||
present_only = bool?(:present, default: true)
|
||||
|
||||
alias_rows =
|
||||
TagName
|
||||
.where('name LIKE ?', "#{ q }%")
|
||||
.where('name LIKE ?', prefix)
|
||||
.where.not(canonical_id: nil)
|
||||
.pluck(:canonical_id, :name)
|
||||
|
||||
@@ -118,54 +225,77 @@ class TagsController < ApplicationController
|
||||
matched_alias_by_tag_name_id[canonical_id] ||= alias_name
|
||||
end
|
||||
|
||||
base = Tag.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.where(deprecated_at: nil)
|
||||
base =
|
||||
Tag
|
||||
.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.where(deprecated_at: nil)
|
||||
base = base.where('tags.post_count > 0') if present_only
|
||||
|
||||
canonical_hit =
|
||||
base
|
||||
.where(((with_nico ? '(tags.category = ? AND tag_names.name LIKE ?) OR ' : '') +
|
||||
'tag_names.name LIKE ?'),
|
||||
*(with_nico ? ['nico', "nico:#{ q }%"] : []), "#{ q }%")
|
||||
canonical_hit = base.where('tag_names.name LIKE ?', prefix)
|
||||
|
||||
tags =
|
||||
if canonical_ids.present?
|
||||
canonical_hit.or(base.where(tag_name_id: canonical_ids.uniq))
|
||||
else
|
||||
canonical_hit
|
||||
end
|
||||
internal_tags = canonical_hit.or(base.where(tag_name_id: canonical_ids.uniq))
|
||||
|
||||
tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a
|
||||
internal_rows =
|
||||
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])
|
||||
}
|
||||
|
||||
render json: tags.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
|
||||
|
||||
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 { external_tag_json(_1) }
|
||||
|
||||
rows =
|
||||
(internal_rows + external_rows)
|
||||
.sort_by { |row| [-row['post_count'], row['name']] }
|
||||
.first(20)
|
||||
|
||||
render json: rows
|
||||
end
|
||||
|
||||
def show
|
||||
tag = Tag.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.find_by(id: params[:id])
|
||||
if tag
|
||||
render json: TagRepr.base(tag)
|
||||
else
|
||||
head :not_found
|
||||
end
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.find_by(id: params[:id])
|
||||
return render json: TagRepr.base(tag) if tag
|
||||
|
||||
external_tag = ExternalTag.find_by(id: params[:id])
|
||||
return render json: ExternalTagRepr.base(external_tag) if external_tag
|
||||
|
||||
head :not_found
|
||||
end
|
||||
|
||||
def show_by_name
|
||||
name = params[:name].to_s.strip
|
||||
return render_bad_request('name は必須です.') if name.blank?
|
||||
|
||||
tag = Tag.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.find_by(tag_names: { name: })
|
||||
if tag
|
||||
render json: TagRepr.base(tag)
|
||||
else
|
||||
head :not_found
|
||||
end
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_name)
|
||||
.includes(:tag_name, :materials, tag_name: :wiki_page)
|
||||
.find_by(tag_names: { name: })
|
||||
return render json: TagRepr.base(tag) if tag
|
||||
|
||||
platform, external_name = name.split(':', 2)
|
||||
return head :not_found unless external_name
|
||||
|
||||
external_tag = ExternalTag.find_by(platform:, name: external_name)
|
||||
return head :not_found unless external_tag
|
||||
|
||||
render json: ExternalTagRepr.base(external_tag)
|
||||
end
|
||||
|
||||
def deerjikists
|
||||
@@ -282,11 +412,7 @@ class TagsController < ApplicationController
|
||||
parent_names = params[:parent_tags].to_s.split.uniq
|
||||
deprecated = bool?(:deprecated)
|
||||
|
||||
if tag.nico? && deprecated
|
||||
return render_unprocessable_entity 'ニコタグは廃止できません.', field: :deprecated
|
||||
end
|
||||
|
||||
if tag.nico? || category == 'nico'
|
||||
if category == 'nico'
|
||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||
end
|
||||
|
||||
@@ -334,13 +460,9 @@ class TagsController < ApplicationController
|
||||
|
||||
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)
|
||||
|
||||
if tag.nico? || (category.present? && category == 'nico')
|
||||
if category.present? && category == 'nico'
|
||||
return render_unprocessable_entity 'ニコタグは変更できません.', field: :category
|
||||
end
|
||||
|
||||
@@ -547,11 +669,6 @@ class TagsController < ApplicationController
|
||||
end
|
||||
|
||||
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:)
|
||||
|
||||
return unless name_changed
|
||||
@@ -661,8 +778,7 @@ class TagsController < ApplicationController
|
||||
|
||||
def update_parent_tags! tag, parent_names
|
||||
parent_tags = Tag.normalise_tags!(parent_names, with_tagme: false,
|
||||
with_no_deerjikist: false,
|
||||
deny_nico: true)
|
||||
with_no_deerjikist: false)
|
||||
|
||||
old_parent_tags = tag.parents.to_a
|
||||
|
||||
@@ -790,4 +906,20 @@ class TagsController < ApplicationController
|
||||
|
||||
render_validation_error fields:
|
||||
end
|
||||
|
||||
def external_tag_json 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,
|
||||
'aliases' => [],
|
||||
'parents' => [],
|
||||
'has_wiki' => false,
|
||||
'material_id' => nil,
|
||||
'has_deerjikists' => false }
|
||||
end
|
||||
end
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする