Reviewed-on: #433 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
@@ -1,22 +1,52 @@
|
||||
class ApplicationController < ActionController::API
|
||||
include ActionController::Cookies
|
||||
include ActionController::RequestForgeryProtection
|
||||
|
||||
class UnsupportedLocale < StandardError
|
||||
;
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
|
||||
rescue_from ActiveRecord::RecordNotUnique, with: :render_record_not_unique
|
||||
rescue_from(UnsupportedLocale) { head :not_found }
|
||||
|
||||
before_action :reject_banned_ip_address!
|
||||
before_action :authenticate_user
|
||||
before_action :reject_banned_user!
|
||||
before_action :validate_explicit_locale!
|
||||
protect_from_forgery with: :exception
|
||||
self.forgery_protection_origin_check = true
|
||||
rescue_from ActionController::InvalidAuthenticityToken, with: :reject_invalid_csrf
|
||||
after_action :prevent_authenticated_caching
|
||||
|
||||
def current_user = @current_user
|
||||
|
||||
private
|
||||
|
||||
def reject_invalid_csrf
|
||||
response.headers['X-CSRF-Invalid'] = '1'
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
head :forbidden
|
||||
end
|
||||
|
||||
def authenticate_user
|
||||
code = request.headers['X-Transfer-Code'] || request.headers['HTTP_X_TRANSFER_CODE']
|
||||
code = session[:inheritance_code]
|
||||
return if code.blank?
|
||||
|
||||
@current_user = User.find_by(inheritance_code: code)
|
||||
end
|
||||
|
||||
def prevent_authenticated_caching
|
||||
response.headers['Cache-Control'] = 'private, no-store' if current_user
|
||||
end
|
||||
|
||||
def establish_user_session user
|
||||
reset_session
|
||||
session[:inheritance_code] = user.inheritance_code
|
||||
@current_user = user
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
end
|
||||
|
||||
def bool? key, default: false
|
||||
return default if params[key].nil?
|
||||
|
||||
@@ -93,5 +123,21 @@ class ApplicationController < ActionController::API
|
||||
value
|
||||
end
|
||||
|
||||
def resolve_locale! = Locale.find_by(code: params[:locale]) || Locale.nipponese
|
||||
def resolve_locale!
|
||||
return @resolved_locale if defined?(@resolved_locale)
|
||||
|
||||
code = params.fetch(:locale, 'ja')
|
||||
unless code.is_a?(String) && Locale.codes.value?(code)
|
||||
raise UnsupportedLocale
|
||||
end
|
||||
|
||||
locale = Locale.find_by(code:)
|
||||
raise UnsupportedLocale unless locale && locale.deprecated_at.nil?
|
||||
|
||||
@resolved_locale = locale
|
||||
end
|
||||
|
||||
def validate_explicit_locale!
|
||||
resolve_locale! if params.key?(:locale)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class LocalesController < ApplicationController
|
||||
def index
|
||||
locales = Locale.where(deprecated_at: nil).order(:created_at).map do |locale|
|
||||
{ code: locale.code_before_type_cast, name: locale.name }
|
||||
end
|
||||
|
||||
response.headers['Cache-Control'] = 'no-store'
|
||||
render json: { locales: }
|
||||
end
|
||||
end
|
||||
@@ -255,7 +255,8 @@ class MaterialsController < ApplicationController
|
||||
end
|
||||
|
||||
def resolve_material_tag! locale, tag_name_raw
|
||||
Tag.find_or_create_by_tag_name!(locale, tag_name_raw, category: :material)
|
||||
Tag.find_or_create_by_tag_name!(locale, tag_name_raw, category: :material,
|
||||
created_by_user: current_user)
|
||||
end
|
||||
|
||||
def material_index_needs_tag_name? filters
|
||||
|
||||
@@ -24,7 +24,7 @@ class NicoTagsController < ApplicationController
|
||||
|
||||
q =
|
||||
ExternalTag
|
||||
.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')
|
||||
.includes(linked_tags: [:tag_names, :tag_descriptions])
|
||||
if name
|
||||
@@ -37,7 +37,7 @@ class NicoTagsController < ApplicationController
|
||||
linked_tag_ids =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.where(tag_names: { language_code: 'ja', primary_flg: true })
|
||||
.where(tag_names: { language_code: locale.language_code, primary_flg: true })
|
||||
.where('tag_names.name LIKE ?', "%#{ linked_tag }%")
|
||||
.pluck(:id)
|
||||
linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
|
||||
@@ -45,7 +45,7 @@ class NicoTagsController < ApplicationController
|
||||
end
|
||||
if link_status.in?(['linked', 'unlinked'])
|
||||
exists_sql =
|
||||
'EXISTS (SELECT 1 FROM nico_tag_relations ' \
|
||||
'EXISTS (SELECT 1 FROM nico_tag_relations ' +
|
||||
'WHERE nico_tag_relations.nico_tag_id = external_tags.id)'
|
||||
q = link_status == 'linked' ? q.where(exists_sql) : q.where("NOT #{ exists_sql }")
|
||||
end
|
||||
@@ -98,7 +98,8 @@ class NicoTagsController < ApplicationController
|
||||
ApplicationRecord.transaction do
|
||||
linked_tags = Tag.normalise_tags!(locale, linked_tag_names,
|
||||
with_tagme: false,
|
||||
with_no_deerjikist: false)
|
||||
with_no_deerjikist: false,
|
||||
created_by_user: current_user)
|
||||
|
||||
TagVersioning.record_tag_snapshots!(
|
||||
linked_tags,
|
||||
@@ -140,10 +141,23 @@ class NicoTagsController < ApplicationController
|
||||
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|
|
||||
"タグ名 “#{ record.name }”: #{ message }"
|
||||
} }
|
||||
names =
|
||||
case record
|
||||
when TagName
|
||||
[record]
|
||||
when Tag
|
||||
record.tag_names.to_a
|
||||
else
|
||||
[]
|
||||
end
|
||||
messages = names.flat_map do |tag_name|
|
||||
tag_name.errors.full_messages.map do |message|
|
||||
"タグ名 “#{ tag_name.name }”: #{ message }"
|
||||
end
|
||||
end
|
||||
|
||||
if messages.any?
|
||||
render_validation_error fields: { tags: messages }
|
||||
else
|
||||
render_validation_error record
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class PostVersionsController < ApplicationController
|
||||
def index
|
||||
locale = resolve_locale!
|
||||
post_id = params[:post].presence
|
||||
tag_id = params[:tag].presence&.to_i
|
||||
external_tag_id = params[:external_tag].presence&.to_i
|
||||
@@ -45,12 +46,12 @@ class PostVersionsController < ApplicationController
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
|
||||
render json: { versions: serialise_versions(versions), count: }
|
||||
render json: { versions: serialise_versions(versions, locale.language_code), count: }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialise_versions rows
|
||||
def serialise_versions rows, language_code
|
||||
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
|
||||
@@ -58,12 +59,16 @@ class PostVersionsController < ApplicationController
|
||||
[normalise_json(row.tags_json),
|
||||
normalise_json(row.attributes['prev_tags_json']) || []]
|
||||
}
|
||||
tag_ids = snapshots.flatten.filter_map { _1['tag_id'] }.uniq
|
||||
tag_names = TagName.where(tag_id: tag_ids, language_code:, primary_flg: true)
|
||||
.pluck(:tag_id, :name).to_h
|
||||
external_tag_names = external_tag_names_for(snapshots)
|
||||
|
||||
rows.map do |row|
|
||||
cur_tags = snapshot_tag_literals(normalise_json(row.tags_json), external_tag_names)
|
||||
cur_tags = snapshot_tag_literals(
|
||||
normalise_json(row.tags_json), tag_names, external_tag_names)
|
||||
prev_tags = snapshot_tag_literals(
|
||||
normalise_json(row.attributes['prev_tags_json']) || [], external_tag_names)
|
||||
normalise_json(row.attributes['prev_tags_json']) || [], tag_names, external_tag_names)
|
||||
|
||||
{ post_id: row.post_id,
|
||||
version_no: row.version_no,
|
||||
@@ -75,6 +80,7 @@ class PostVersionsController < ApplicationController
|
||||
prev: row.attributes['prev_thumbnail_base'] },
|
||||
video_ms: { current: row.video_ms, prev: row.attributes['prev_video_ms'] },
|
||||
tags: build_version_tags(cur_tags, prev_tags),
|
||||
parent_post_ids: row.parent_post_ids,
|
||||
original_created_from: {
|
||||
current: row.original_created_from&.iso8601,
|
||||
prev: row.attributes['prev_original_created_from']&.iso8601 },
|
||||
@@ -106,17 +112,27 @@ class PostVersionsController < ApplicationController
|
||||
names
|
||||
end
|
||||
|
||||
def snapshot_tag_literals snapshots, external_tag_names
|
||||
def snapshot_tag_literals snapshots, tag_names, external_tag_names
|
||||
snapshots
|
||||
.filter_map { |snapshot|
|
||||
if snapshot.key?('tag_id')
|
||||
sections = snapshot.fetch('sections', []).map { |section|
|
||||
[section.fetch('begin_ms'), section['end_ms']]
|
||||
}.sort_by { |begin_ms, end_ms| [begin_ms, end_ms || Float::INFINITY] }
|
||||
name = tag_names[snapshot['tag_id']] || snapshot.fetch('name')
|
||||
[tag_category_order(snapshot['category']),
|
||||
Post.tag_snapshot_literal(snapshot)]
|
||||
{ identity: [:tag, snapshot['tag_id'], sections],
|
||||
name: Post.tag_snapshot_literal(snapshot.merge('name' => name)),
|
||||
restore_name: tag_names[snapshot['tag_id']] &&
|
||||
Post.tag_snapshot_literal(snapshot.merge('name' => name)),
|
||||
external: false }]
|
||||
elsif external_tag_names[snapshot['external_tag_id']]
|
||||
[6, external_tag_names[snapshot['external_tag_id']]]
|
||||
[6, { identity: [:external_tag, snapshot['external_tag_id']],
|
||||
name: external_tag_names[snapshot['external_tag_id']],
|
||||
restore_name: nil, external: true }]
|
||||
end
|
||||
}
|
||||
.sort_by { |order, name| [order, name.downcase] }
|
||||
.sort_by { |order, tag| [order, tag[:name].downcase] }
|
||||
.map(&:second)
|
||||
end
|
||||
|
||||
@@ -125,17 +141,22 @@ class PostVersionsController < ApplicationController
|
||||
end
|
||||
|
||||
def build_version_tags cur_tags, prev_tags
|
||||
(cur_tags | prev_tags).map do |name|
|
||||
current = cur_tags.index_by { _1[:identity] }
|
||||
previous = prev_tags.index_by { _1[:identity] }
|
||||
|
||||
(current.keys | previous.keys).map do |identity|
|
||||
type =
|
||||
if cur_tags.include?(name) && prev_tags.include?(name)
|
||||
if current.key?(identity) && previous.key?(identity)
|
||||
'context'
|
||||
elsif cur_tags.include?(name)
|
||||
elsif current.key?(identity)
|
||||
'added'
|
||||
else
|
||||
'removed'
|
||||
end
|
||||
|
||||
{ name:, type: }
|
||||
entry = current[identity] || previous[identity]
|
||||
{ name: entry[:name], restore_name: entry[:restore_name],
|
||||
external: entry[:external], type: }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -400,8 +400,76 @@ class PostsController < ApplicationController
|
||||
render_post_form_record_invalid e.record
|
||||
end
|
||||
|
||||
def tag_filters
|
||||
source = resolve_locale!
|
||||
code = params[:target_locale]
|
||||
return head :not_found unless code.is_a?(String) && Locale.codes.value?(code)
|
||||
|
||||
target = Locale.find_by(code:, deprecated_at: nil)
|
||||
return head :not_found unless target
|
||||
|
||||
literals = params[:tags].to_s.split
|
||||
references = tag_filter_references(literals)
|
||||
names = literals.each_with_index.map do |literal, index|
|
||||
reference = references&.[](index)
|
||||
(reference && reference['negative'] == false) ? literal : literal.sub(/\Anot:/i, '')
|
||||
end
|
||||
internal = references ? {} : TagName.where(
|
||||
language_code: source.language_code, name: names).pluck(:name, :tag_id).to_h
|
||||
canonical = references ? {} : TagName.where(
|
||||
language_code: source.language_code, primary_flg: true,
|
||||
tag_id: internal.values).pluck(:tag_id, :name).to_h
|
||||
external_names = names.map { canonical[internal[_1]] || _1 }
|
||||
external = references ? {} : ExternalTag.where(
|
||||
"CONCAT(platform, ':', name) IN (?)", external_names).pluck(
|
||||
Arel.sql("CONCAT(platform, ':', name)"), :id).to_h
|
||||
ids = references ? references.filter_map { _1['tag_id'] } : internal.values
|
||||
translated = TagName.where(language_code: target.language_code,
|
||||
primary_flg: true, tag_id: ids).pluck(:tag_id, :name).to_h
|
||||
|
||||
filters = literals.each_with_index.map do |literal, index|
|
||||
name = names[index]
|
||||
reference = if references
|
||||
references[index].except('name')
|
||||
else
|
||||
matches = {}
|
||||
matches['tag_id'] = internal[name] if internal[name]
|
||||
external_id = external[external_names[index]]
|
||||
matches['external_tag_id'] = external_id if external_id
|
||||
matches.empty? ? { 'missing' => true } : matches
|
||||
end
|
||||
name = translated[reference['tag_id']] || name
|
||||
negative = reference.fetch('negative', literal.downcase.start_with?('not:'))
|
||||
prefix = negative ? literal[/\Anot:/i] || 'not:' : ''
|
||||
reference.merge('name' => "#{ prefix }#{ name }", 'negative' => negative)
|
||||
end
|
||||
render json: { references: filters }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tag_filter_references literals
|
||||
raw = params[:tag_refs]
|
||||
return nil if raw.blank?
|
||||
|
||||
references = JSON.parse(raw)
|
||||
valid = references.is_a?(Array) && references.all? do |reference|
|
||||
next false unless reference.is_a?(Hash) && reference['name'].is_a?(String)
|
||||
|
||||
ids = [reference['tag_id'], reference['external_tag_id']].compact
|
||||
((ids.any? || reference['missing'] == true) &&
|
||||
ids.all? { _1.is_a?(Integer) && _1.positive? } &&
|
||||
(!reference.key?('missing') || (reference['missing'] == true && ids.empty?)) &&
|
||||
(!reference.key?('negative') || [true, false].include?(reference['negative'])))
|
||||
end
|
||||
raise ActionController::BadRequest unless valid
|
||||
return nil unless references.map { _1['name'] } == literals
|
||||
|
||||
references
|
||||
rescue JSON::ParserError, TypeError
|
||||
raise ActionController::BadRequest
|
||||
end
|
||||
|
||||
def filtered_posts locale
|
||||
tag_names = params[:tags].to_s.split
|
||||
match_type = params[:match]
|
||||
@@ -413,20 +481,29 @@ class PostsController < ApplicationController
|
||||
end
|
||||
|
||||
def filter_posts_by_tags locale, tag_names, match_type
|
||||
literals = tag_names.map do |raw_name|
|
||||
{ name: TagName.canonicalise(locale, raw_name.sub(/\Anot:/i, '')).first,
|
||||
negative: raw_name.downcase.start_with?('not:') }
|
||||
references = tag_filter_references(tag_names)
|
||||
literals = tag_names.each_with_index.map do |raw_name, index|
|
||||
name = raw_name.sub(/\Anot:/i, '')
|
||||
name = TagName.canonicalise(locale, name).first unless references
|
||||
reference = references&.[](index)
|
||||
negative = if reference&.key?('negative')
|
||||
reference['negative']
|
||||
else
|
||||
raw_name.downcase.start_with?('not:')
|
||||
end
|
||||
{ name:, negative:, reference: }
|
||||
end
|
||||
|
||||
return Post.all if literals.empty?
|
||||
|
||||
if match_type == 'any'
|
||||
literals.reduce(Post.none) do |posts, literal|
|
||||
posts.or(tag_literal_relation(locale, literal[:name], negative: literal[:negative]))
|
||||
posts.or(tag_literal_relation(locale, literal[:name], negative: literal[:negative],
|
||||
reference: literal[:reference]))
|
||||
end
|
||||
else
|
||||
literals.reduce(Post.all) do |posts, literal|
|
||||
ids = tagged_post_ids_for(locale, literal[:name])
|
||||
ids = tagged_post_ids_for(locale, literal[:name], reference: literal[:reference])
|
||||
if literal[:negative]
|
||||
posts.where.not(id: ids)
|
||||
else
|
||||
@@ -436,8 +513,8 @@ class PostsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def tag_literal_relation locale, name, negative:
|
||||
ids = tagged_post_ids_for(locale, name)
|
||||
def tag_literal_relation locale, name, negative:, reference: nil
|
||||
ids = tagged_post_ids_for(locale, name, reference:)
|
||||
if negative
|
||||
Post.where.not(id: ids)
|
||||
else
|
||||
@@ -445,7 +522,20 @@ class PostsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def tagged_post_ids_for locale, name
|
||||
def tagged_post_ids_for locale, name, reference: nil
|
||||
if reference
|
||||
matches = Post.none
|
||||
if reference['tag_id']
|
||||
matches = matches.or(Post.where(id: PostTag.where(
|
||||
tag_id: reference['tag_id']).select(:post_id)))
|
||||
end
|
||||
if reference['external_tag_id']
|
||||
matches = matches.or(Post.where(id: PostExternalTag.where(
|
||||
external_tag_id: reference['external_tag_id']).select(:post_id)))
|
||||
end
|
||||
return matches.select(:id)
|
||||
end
|
||||
|
||||
posts_by_internal_tags =
|
||||
Post
|
||||
.joins(tags: :tag_names)
|
||||
@@ -728,7 +818,8 @@ class PostsController < ApplicationController
|
||||
with_tagme: false,
|
||||
with_no_deerjikist: false,
|
||||
deny_deprecated: true,
|
||||
with_sections: true) => { tags:, sections: }
|
||||
with_sections: true,
|
||||
created_by_user: current_user) => { tags:, sections: }
|
||||
|
||||
tags = Tag.expand_parent_tags(tags).reject(&:deprecated?)
|
||||
video_ms = normalise_video_ms(tags, video_ms_param:, duration_param:)
|
||||
@@ -874,7 +965,8 @@ class PostsController < ApplicationController
|
||||
Tag.normalise_tags!(locale, snapshot[:tag_names],
|
||||
with_tagme: false,
|
||||
deny_deprecated: true,
|
||||
with_sections: true) => { tags:, sections: }
|
||||
with_sections: true,
|
||||
created_by_user: current_user) => { tags:, sections: }
|
||||
TagVersioning.record_tag_snapshots!(tags, created_by_user: current_user,
|
||||
language_code: locale.language_code)
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
class SitemapEntriesController < ApplicationController
|
||||
PAGE_SIZE = 1_000
|
||||
RESOURCES = [
|
||||
'posts', 'tags', 'deerjikists', 'materials',
|
||||
'descriptions', 'tag_groups', 'theatres'].freeze
|
||||
|
||||
def index
|
||||
locale = resolve_locale!
|
||||
resources = RESOURCES.map { |resource| { resource:, count: entries(resource, locale).count } }
|
||||
render json: { resources:, page_size: PAGE_SIZE }
|
||||
end
|
||||
|
||||
def show
|
||||
locale = resolve_locale!
|
||||
resource = params[:resource]
|
||||
return head :not_found unless RESOURCES.include?(resource)
|
||||
|
||||
page = Integer(params.fetch(:page, '1'), exception: false)
|
||||
return head :bad_request unless page && page.positive?
|
||||
|
||||
scope = entries(resource, locale)
|
||||
return head :not_found if page > [(scope.count.to_f / PAGE_SIZE).ceil, 1].max
|
||||
|
||||
ids = scope.order(:id).offset((page - 1) * PAGE_SIZE).limit(PAGE_SIZE).pluck(:id)
|
||||
render json: { ids: }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def entries resource, locale
|
||||
named_tags = TagName.where(language_code: locale.language_code, primary_flg: true)
|
||||
.select(:tag_id)
|
||||
case resource
|
||||
when 'posts'
|
||||
Post.all
|
||||
when 'tags'
|
||||
Tag.where(id: named_tags)
|
||||
when 'deerjikists'
|
||||
Tag.where(id: named_tags).where(id: Deerjikist.select(:tag_id))
|
||||
when 'materials'
|
||||
Material.all
|
||||
when 'descriptions'
|
||||
TagDescription.where(language_code: locale.language_code, tag_id: named_tags)
|
||||
.where.not(body: [nil, ''])
|
||||
.where(TagDescriptionVersion
|
||||
.where('tag_description_versions.tag_id = tag_descriptions.tag_id')
|
||||
.where('tag_description_versions.language_code = tag_descriptions.language_code')
|
||||
.where('tag_description_versions.version_no = tag_descriptions.version_no')
|
||||
.arel.exists)
|
||||
when 'tag_groups'
|
||||
TagGroup.joins(:versions)
|
||||
.where('tag_group_versions.version_no = tag_groups.version_no')
|
||||
when 'theatres'
|
||||
Theatre.kept
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -35,8 +35,23 @@ class TagVersionsController < ApplicationController
|
||||
private
|
||||
|
||||
def serialise_versions rows, language_code
|
||||
rows = rows.to_a
|
||||
tag_ids = rows.map(&:tag_id).uniq
|
||||
current_names_by_tag_id = TagName.where(tag_id: tag_ids, language_code:, primary_flg: true)
|
||||
.pluck(:tag_id, :name, :auto_generated).to_h do |id, name, automatic|
|
||||
[id, { name:, auto_generated: automatic }]
|
||||
end
|
||||
user_ids = rows.map(&:created_by_user_id).compact.uniq
|
||||
users_by_id = User.where(id: user_ids).pluck(:id, :name).to_h
|
||||
parent_ids = rows.flat_map do |row|
|
||||
split_parent_tag_ids(row.parent_tag_ids) +
|
||||
split_parent_tag_ids(row.attributes['prev_parent_tag_ids'])
|
||||
end.uniq
|
||||
parents_by_id = Tag.includes(
|
||||
:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.where(id: parent_ids).index_by(&:id)
|
||||
parent_json = parents_by_id.transform_values { TagRepr.base(_1, language_code:) }
|
||||
|
||||
rows.map do |row|
|
||||
current_names = names_for_locale(row.names, language_code)
|
||||
@@ -47,18 +62,10 @@ class TagVersionsController < ApplicationController
|
||||
cur_parent_tag_ids = split_parent_tag_ids(row.parent_tag_ids)
|
||||
prev_parent_tag_ids = split_parent_tag_ids(row.attributes['prev_parent_tag_ids'])
|
||||
|
||||
all_parent_tag_ids = (cur_parent_tag_ids | prev_parent_tag_ids)
|
||||
|
||||
tags_by_id =
|
||||
Tag
|
||||
.includes(:tag_names, :materials, :tag_descriptions, parents: :tag_descriptions)
|
||||
.where(id: all_parent_tag_ids)
|
||||
.index_by(&:id)
|
||||
|
||||
parent_tags =
|
||||
build_version_values(cur_parent_tag_ids, prev_parent_tag_ids, key: :tag_id)
|
||||
.map do |h|
|
||||
{ tag: TagRepr.base(tags_by_id[h[:tag_id]], language_code:),
|
||||
{ tag: parent_json.fetch(h[:tag_id]),
|
||||
type: h[:type] }
|
||||
end
|
||||
|
||||
@@ -66,6 +73,8 @@ class TagVersionsController < ApplicationController
|
||||
version_no: row.version_no,
|
||||
event_type: row.event_type,
|
||||
names: row.names,
|
||||
name_changes: name_changes(row.names, row.attributes['prev_names']),
|
||||
current_name_reference: current_names_by_tag_id[row.tag_id],
|
||||
name: { current: current_names['name'], prev: previous_names['name'] },
|
||||
category: { current: row.category, prev: row.attributes['prev_category'] },
|
||||
deprecated_at: { current: row.deprecated_at&.iso8601,
|
||||
@@ -97,11 +106,44 @@ class TagVersionsController < ApplicationController
|
||||
def split_values(values) = values.to_s.split(/\s+/).reject(&:blank?)
|
||||
|
||||
def names_for_locale names, language_code
|
||||
values = snapshot_names(names)[language_code]
|
||||
values.is_a?(Hash) ? values : {}
|
||||
end
|
||||
|
||||
def snapshot_names names
|
||||
values = names.is_a?(String) ? JSON.parse(names) : names
|
||||
values&.fetch(language_code, {}) || {}
|
||||
values.is_a?(Hash) ? values : {}
|
||||
rescue JSON::ParserError
|
||||
{}
|
||||
end
|
||||
|
||||
def name_changes current_names, previous_names
|
||||
current = snapshot_names(current_names)
|
||||
previous = snapshot_names(previous_names)
|
||||
(current.keys | previous.keys).sort.filter_map do |language|
|
||||
current_name = language_snapshot(current[language])
|
||||
previous_name = language_snapshot(previous[language])
|
||||
next if current_name == previous_name
|
||||
|
||||
{ language_code: language, current: current_name, prev: previous_name }
|
||||
end
|
||||
end
|
||||
|
||||
def language_snapshot entry
|
||||
return nil unless entry.is_a?(Hash)
|
||||
|
||||
aliases = Array(entry['aliases']).filter_map do |value|
|
||||
value = { 'name' => value } if value.is_a?(String)
|
||||
next unless value.is_a?(Hash) && value['name'].is_a?(String)
|
||||
|
||||
{ name: value['name'], script_code: value['script_code'] }
|
||||
end
|
||||
aliases = aliases.uniq.sort_by { [_1[:name], _1[:script_code].to_s] }
|
||||
{ name: entry['name'],
|
||||
script_code: entry['script_code'],
|
||||
auto_generated: entry['auto_generated'],
|
||||
aliases: }
|
||||
end
|
||||
|
||||
def split_parent_tag_ids(values) = split_values(values).map(&:to_i)
|
||||
end
|
||||
|
||||
@@ -175,7 +175,8 @@ class TagsController < ApplicationController
|
||||
tags_by_id =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :materials, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.where(tag_names: { language_code: locale.language_code, primary_flg: true },
|
||||
id: tag_ids)
|
||||
.index_by(&:id)
|
||||
@@ -238,7 +239,8 @@ class TagsController < ApplicationController
|
||||
base =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :materials, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.where(tag_names: { language_code: locale.language_code, primary_flg: true },
|
||||
deprecated_at: nil)
|
||||
base = base.where('tags.post_count > 0') if present_only
|
||||
@@ -282,7 +284,8 @@ class TagsController < ApplicationController
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :materials, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find_by(id: params[:id],
|
||||
tag_names: { language_code: locale.language_code, primary_flg: true })
|
||||
return render json: TagRepr.base(tag, language_code: locale.language_code) if tag
|
||||
@@ -302,8 +305,9 @@ class TagsController < ApplicationController
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :materials, :tag_descriptions, parents: :tag_descriptions)
|
||||
.find_by(tag_names: { name:, language_code: locale.language_code, primary_flg: true })
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find_by(tag_names: { name:, language_code: locale.language_code })
|
||||
return render json: TagRepr.base(tag, language_code: locale.language_code) if tag
|
||||
|
||||
platform, external_name = name.split(':', 2)
|
||||
@@ -320,7 +324,8 @@ class TagsController < ApplicationController
|
||||
|
||||
tag =
|
||||
Tag.joins(:tag_names)
|
||||
.includes(:tag_names, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find_by(id: params[:id],
|
||||
tag_names: { language_code: locale.language_code, primary_flg: true })
|
||||
return head :not_found unless tag
|
||||
@@ -340,7 +345,8 @@ class TagsController < ApplicationController
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find_by(tag_names: { name:, language_code: locale.language_code, primary_flg: true })
|
||||
return head :not_found unless tag
|
||||
|
||||
@@ -359,7 +365,8 @@ class TagsController < ApplicationController
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find_by(id: params[:id],
|
||||
tag_names: { language_code: locale.language_code, primary_flg: true })
|
||||
return head :not_found unless tag
|
||||
@@ -368,6 +375,7 @@ class TagsController < ApplicationController
|
||||
return if performed?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
SharedResourceIdAllocator.lock_space!(Tag, 'tags')
|
||||
tag.lock!
|
||||
|
||||
requested_keys = rows.map { |row| [row[:platform], row[:code]] }.uniq
|
||||
@@ -424,7 +432,8 @@ class TagsController < ApplicationController
|
||||
tag =
|
||||
Tag
|
||||
.joins(:tag_names)
|
||||
.includes(:tag_names, :materials, :tag_descriptions, parents: :tag_descriptions)
|
||||
.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find_by(tag_names: { name:, language_code: locale.language_code, primary_flg: true })
|
||||
return head :not_found unless tag
|
||||
|
||||
@@ -835,7 +844,8 @@ class TagsController < ApplicationController
|
||||
def update_parent_tags! locale, tag, parent_names
|
||||
parent_tags = Tag.normalise_tags!(locale, parent_names,
|
||||
with_tagme: false,
|
||||
with_no_deerjikist: false)
|
||||
with_no_deerjikist: false,
|
||||
created_by_user: current_user)
|
||||
|
||||
old_parent_tags = tag.parents.to_a
|
||||
|
||||
|
||||
@@ -1,39 +1,69 @@
|
||||
class UsersController < ApplicationController
|
||||
def create
|
||||
if current_user
|
||||
return render json: { code: current_user.inheritance_code,
|
||||
user: current_user.slice(:id, :name, :inheritance_code, :role) }
|
||||
end
|
||||
|
||||
user = nil
|
||||
User.transaction do
|
||||
user = User.create!(inheritance_code: SecureRandom.uuid, role: :guest)
|
||||
attach_ip_address!(user)
|
||||
end
|
||||
|
||||
establish_user_session(user)
|
||||
render json: { code: user.inheritance_code,
|
||||
user: user.slice(:id, :name, :inheritance_code, :role) },
|
||||
status: :created
|
||||
end
|
||||
|
||||
def verify
|
||||
user = User.find_by(inheritance_code: params[:code])
|
||||
user = if bool?(:restore) && current_user
|
||||
current_user
|
||||
else
|
||||
User.find_by(inheritance_code: params[:code])
|
||||
end
|
||||
return render json: { valid: false } unless user
|
||||
return head :forbidden if user.banned?
|
||||
|
||||
attach_ip_address!(user)
|
||||
establish_user_session(user)
|
||||
|
||||
render json: { valid: true, user: user.slice(:id, :name, :inheritance_code, :role) }
|
||||
end
|
||||
|
||||
def show_session
|
||||
return head :unauthorized unless current_user
|
||||
|
||||
render json: current_user.slice(:id, :name, :role)
|
||||
end
|
||||
|
||||
def csrf
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
render json: { token: form_authenticity_token }
|
||||
end
|
||||
|
||||
def destroy_session
|
||||
reset_session
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def renew
|
||||
return head :unauthorized unless current_user
|
||||
|
||||
user = current_user
|
||||
user.inheritance_code = SecureRandom.uuid
|
||||
user.save!
|
||||
establish_user_session(user)
|
||||
render json: { code: user.inheritance_code }
|
||||
end
|
||||
|
||||
def me
|
||||
user = User.find_by(inheritance_code: params[:code])
|
||||
return head :not_found unless user
|
||||
user = current_user
|
||||
return head :unauthorized unless user
|
||||
|
||||
attach_ip_address!(user)
|
||||
render json: user.slice(:id, :name, :inheritance_code, :role)
|
||||
end
|
||||
|
||||
|
||||
@@ -34,11 +34,33 @@ class WikiPagesController < ApplicationController
|
||||
render json:
|
||||
end
|
||||
|
||||
def autolink_candidates
|
||||
locale = resolve_locale!
|
||||
language = params.fetch(:language, locale.language_code)
|
||||
language = 'ja' if language == 'und'
|
||||
return head :not_found unless language.is_a?(String)
|
||||
return head :not_found unless Language.exists?(code: language)
|
||||
|
||||
names = TagDescription.joins(tag: :tag_names)
|
||||
.where(language_code: locale.language_code, tag_names: { language_code: language })
|
||||
.pluck('tag_descriptions.tag_id', 'tag_descriptions.id', 'tag_names.name')
|
||||
candidates = names.map do |tag_id, description_id, name|
|
||||
WikiPageRepr.autolink_tag(locale, tag_id:, description_id:, name:, language:)
|
||||
end
|
||||
candidates.concat(TagGroup.order(:id).pluck(:id, :name).map do |id, name|
|
||||
WikiPageRepr.autolink_group(locale, id:, name:, language:)
|
||||
end)
|
||||
render json: candidates
|
||||
end
|
||||
|
||||
def show
|
||||
locale = resolve_locale!
|
||||
tag_description = TagDescription.find_by(id: params[:id], language_code: locale.language_code)
|
||||
reference = TagDescription.find_by(id: params[:id])
|
||||
tag_description = reference && TagDescription.find_by(
|
||||
tag_id: reference.tag_id, language_code: locale.language_code)
|
||||
|
||||
return render_wiki_page_by_tag_description locale, tag_description if tag_description
|
||||
return head :not_found if reference
|
||||
|
||||
tag_group = TagGroup.find_by(id: params[:id])
|
||||
return render_tag_group locale, tag_group if tag_group
|
||||
@@ -63,6 +85,17 @@ class WikiPagesController < ApplicationController
|
||||
head :not_found
|
||||
end
|
||||
|
||||
def tag_reference
|
||||
locale = resolve_locale!
|
||||
description = TagDescription.find_by(id: params[:id])
|
||||
return head :not_found unless description
|
||||
|
||||
tag = Tag.preload(:tag_names, :materials, :deerjikists, :tag_descriptions,
|
||||
parents: [:tag_names, :materials, :deerjikists, :tag_descriptions])
|
||||
.find(description.tag_id)
|
||||
render json: TagRepr.base(tag, language_code: locale.language_code)
|
||||
end
|
||||
|
||||
def exists
|
||||
locale = resolve_locale!
|
||||
if TagDescription.exists?(id: params[:id], language_code: locale.language_code)
|
||||
@@ -214,6 +247,7 @@ class WikiPagesController < ApplicationController
|
||||
base_revision_id = params[:base_revision_id].presence
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
SharedResourceIdAllocator.lock_space!(Tag, 'tags')
|
||||
tag.lock!
|
||||
old_title = tag_name.name
|
||||
if title != old_title
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする