|
|
@@ -173,15 +173,41 @@ class PostsController < 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? |
|
|
|
|
|
|
|
|
|
|
|
base_version_no = parse_base_version_no |
|
|
|
|
|
force = truthy_param?(params[:force]) |
|
|
|
|
|
|
|
|
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] |
|
|
original_created_before = params[:original_created_before] |
|
|
original_created_before = params[:original_created_before] |
|
|
parent_post_ids = parse_parent_post_ids |
|
|
parent_post_ids = parse_parent_post_ids |
|
|
|
|
|
|
|
|
post = Post.find(params[:id].to_i) |
|
|
|
|
|
|
|
|
post = nil |
|
|
|
|
|
conflict_json = nil |
|
|
|
|
|
|
|
|
ApplicationRecord.transaction do |
|
|
ApplicationRecord.transaction do |
|
|
|
|
|
post = Post.find(params[:id].to_i) |
|
|
|
|
|
|
|
|
|
|
|
base_version = post.post_versions.find_by!(version_no: base_version_no) |
|
|
|
|
|
|
|
|
|
|
|
base_snapshot = post_snapshot_from_version(base_version) |
|
|
|
|
|
current_snapshot = post_snapshot_from_record(post) |
|
|
|
|
|
incoming_snapshot = post_incoming_snapshot(post, |
|
|
|
|
|
title:, |
|
|
|
|
|
original_created_from:, |
|
|
|
|
|
original_created_before:, |
|
|
|
|
|
tag_names:, |
|
|
|
|
|
parent_post_ids:) |
|
|
|
|
|
|
|
|
|
|
|
if !(force) && post.version_no != base_version_no |
|
|
|
|
|
conflict_json = post_conflict_json(post:, |
|
|
|
|
|
base_version_no:, |
|
|
|
|
|
base_snapshot:, |
|
|
|
|
|
current_snapshot:, |
|
|
|
|
|
incoming_snapshot:) |
|
|
|
|
|
raise ActiveRecord::Rollback |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
PostVersionRecorder.ensure_snapshot!(post, created_by_user: current_user) |
|
|
PostVersionRecorder.ensure_snapshot!(post, created_by_user: current_user) |
|
|
|
|
|
|
|
|
post.update!(title:, original_created_from:, original_created_before:) |
|
|
post.update!(title:, original_created_from:, original_created_before:) |
|
|
@@ -198,8 +224,10 @@ class PostsController < ApplicationController |
|
|
PostVersionRecorder.record!(post:, event_type: :update, created_by_user: current_user) |
|
|
PostVersionRecorder.record!(post:, event_type: :update, created_by_user: current_user) |
|
|
end |
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
return render json: conflict_json, status: :conflict if conflict_json |
|
|
|
|
|
|
|
|
post.reload |
|
|
post.reload |
|
|
json = post.as_json |
|
|
|
|
|
|
|
|
json = PostRepr.base(post, current_user) |
|
|
json['tags'] = build_tag_tree_for(post.tags) |
|
|
json['tags'] = build_tag_tree_for(post.tags) |
|
|
render json:, status: :ok |
|
|
render json:, status: :ok |
|
|
rescue Tag::NicoTagNormalisationError |
|
|
rescue Tag::NicoTagNormalisationError |
|
|
@@ -404,4 +432,178 @@ class PostsController < ApplicationController |
|
|
PostImplication.create_or_find_by!(post_id: post.id, parent_post_id:) |
|
|
PostImplication.create_or_find_by!(post_id: post.id, parent_post_id:) |
|
|
end |
|
|
end |
|
|
end |
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def parse_base_version_no |
|
|
|
|
|
version_no = Integer(params[:base_version_no], exception: false) |
|
|
|
|
|
raise ArgumentError, 'base_version_no は必須です.' unless version_no&.positive? |
|
|
|
|
|
|
|
|
|
|
|
version_no |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def truthy_param?(value) = ActiveModel::Type::Boolean.new.cast(value) |
|
|
|
|
|
|
|
|
|
|
|
def post_snapshot_from_version version |
|
|
|
|
|
{ title: version.title, |
|
|
|
|
|
original_created_from: snapshot_time(version.original_created_from), |
|
|
|
|
|
original_created_before: snapshot_time(version.original_created_before), |
|
|
|
|
|
tag_names: version.tags.to_s.split.sort, |
|
|
|
|
|
parent_post_ids: snapshot_parent_post_ids_from_version(version) } |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def post_snapshot_form_record post |
|
|
|
|
|
{ title: post.title, |
|
|
|
|
|
original_created_from: snapshot_time(post.original_created_from), |
|
|
|
|
|
original_created_before: snapshot_time(post.original_created_before), |
|
|
|
|
|
tag_names: post.tags.joins(:tag_name).order('tag_names.name').pluck('tag_names.name'), |
|
|
|
|
|
parent_post_ids: post.parent_posts.order(:id).pluck(:id) } |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def post_incoming_snapshot post, title:, original_created_from:, original_created_before:, |
|
|
|
|
|
tag_names:, parent_post_ids: |
|
|
|
|
|
{ title: |
|
|
|
|
|
original_created_from: snapshot_time(original_created_from), |
|
|
|
|
|
original_created_before: snapshot_time(original_created_before), |
|
|
|
|
|
tag_names: incoming_tag_names_for_snapshot(post, tag_names), |
|
|
|
|
|
parent_post_ids: parent_post_ids.sort } |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def snapshot_parent_post_ids_from_version version |
|
|
|
|
|
if version.respond_to?(:parent_post_ids) |
|
|
|
|
|
version.parent_post_ids.to_s.split.map { |id| id.to_i }.sort |
|
|
|
|
|
elsif version.respond_to?(:parent_id) && version.parent_id |
|
|
|
|
|
[version.parent_id] |
|
|
|
|
|
else |
|
|
|
|
|
[] |
|
|
|
|
|
end |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def snapshot_time value |
|
|
|
|
|
return nil if value.blank? |
|
|
|
|
|
|
|
|
|
|
|
value = Time.zone.parse(value.to_s) if value in String |
|
|
|
|
|
value&.in_time_zone&.iso8601(6) |
|
|
|
|
|
rescue ArgumentError, TypeError |
|
|
|
|
|
value.to_s |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def incoming_tag_names_for_snapshot post, raw_tag_names |
|
|
|
|
|
manual_names = normalised_manual_tag_names_for_snapshot(raw_tag_names) |
|
|
|
|
|
nico_names = post.tags.nico.joins(:tag_name).pluck('tag_names.name') |
|
|
|
|
|
|
|
|
|
|
|
existing_tags = |
|
|
|
|
|
Tag |
|
|
|
|
|
.joins(:tag_name) |
|
|
|
|
|
.where(tag_names: { name: manual_names + nico_names }) |
|
|
|
|
|
.to_a |
|
|
|
|
|
|
|
|
|
|
|
expanded_names = Tag.expand_parent_tags(existing_tags).map(&:name) |
|
|
|
|
|
|
|
|
|
|
|
(manual_names + nico_names + expanded_names).uniq.sort |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def normalised_manual_tag_names_for_snapshot raw_tag_names |
|
|
|
|
|
if raw_tag_names.any? { |name| name.downcase.start_with?('nico:') } |
|
|
|
|
|
raise Tag::NicoTagNormalisationError |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
pairs = raw_tag_names.map do |raw_name| |
|
|
|
|
|
prefix, category = |
|
|
|
|
|
Tag::CATEGORY_PREFIXES.find { |p, _| raw_name.downcase.start_with?(p) } || ['', nil] |
|
|
|
|
|
|
|
|
|
|
|
name = TagName.canonicalise(raw_name.sub(/\A#{ Regexp.escape(prefix) }/i, '')).first |
|
|
|
|
|
|
|
|
|
|
|
[name, category] |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
names = pairs.map(&:first) |
|
|
|
|
|
|
|
|
|
|
|
has_deerjikist = pairs.any? do |name, category| |
|
|
|
|
|
category == :deerjikist || |
|
|
|
|
|
Tag.joins(:tag_name).where(category: :deerjikist, tag_names: { name: }).exists? |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
names << Tag.no_deerjikist.name unless has_deerjikist |
|
|
|
|
|
|
|
|
|
|
|
names.uniq.sort |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def post_conflict_json post:, base_version_no:, base_snapshot:, |
|
|
|
|
|
current_snapshot:, incoming_snapshot: |
|
|
|
|
|
changes = post_snapshot_changes(base_snapshot, current_snapshot, incoming_snapshot) |
|
|
|
|
|
conflicts = changes.select { |change| change[:conflict] } |
|
|
|
|
|
|
|
|
|
|
|
{ error: 'conflict', |
|
|
|
|
|
message: '競合が発生しました.', |
|
|
|
|
|
post_id: post.id, |
|
|
|
|
|
base_version_no:, |
|
|
|
|
|
current_version_no: post.version_no, |
|
|
|
|
|
base: base_snapshot, |
|
|
|
|
|
current: current_snapshot, |
|
|
|
|
|
mine: incoming_snapshot, |
|
|
|
|
|
changes:, |
|
|
|
|
|
conflicts:, |
|
|
|
|
|
mergeable: conflicts.empty? } |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def post_snapshot_changes base_snapshot, current_snapshot, incoming_snapshot |
|
|
|
|
|
[scalar_snapshot_change(:title, 'タイトル', |
|
|
|
|
|
base_snapshot, current_snapshot, incoming_snapshot), |
|
|
|
|
|
scalar_snapshot_change(:original_created_from, '元コンテンツ作成日時(開始)', |
|
|
|
|
|
base_snapshot, current_snapshot, incoming_snapshot), |
|
|
|
|
|
scalar_snapshot_change(:original_created_before, '元コンテンツ作成日時(終了)', |
|
|
|
|
|
base_snapshot, current_snapshot, incoming_snapshot), |
|
|
|
|
|
set_snapshot_change(:tag_names, 'タグ', |
|
|
|
|
|
base_snapshot, current_snapshot, incoming_snapshot), |
|
|
|
|
|
set_snapshot_change(:parent_post_ids, '親投稿', |
|
|
|
|
|
base_snapshot, current_snapshot, incoming_snapshot)].compact |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def scalar_snapshot_change field, label, base_snapshot, current_snapshot, incoming_snapshot |
|
|
|
|
|
base = base_snapshot[field] |
|
|
|
|
|
current = current_snapshot[field] |
|
|
|
|
|
mine = incoming_snapshot[field] |
|
|
|
|
|
|
|
|
|
|
|
return nil if current == base && mine == base |
|
|
|
|
|
|
|
|
|
|
|
{ field:, label:, base:, current:, mine:, |
|
|
|
|
|
changed_by_current: current != base, |
|
|
|
|
|
changed_by_me: mine != base, |
|
|
|
|
|
conflict: scalar_snapshot_conflict?(base, current, mine) } |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def scalar_snapshot_conflict? base, current, mine |
|
|
|
|
|
current != base && mine != base && current != mine |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def set_snapshot_change field, label, base_snapshot, current_snapshot, incoming_snapshot |
|
|
|
|
|
base = base_snapshot[field].to_a |
|
|
|
|
|
current = current_snapshot[field].to_a |
|
|
|
|
|
mine = incoming_snapshot[field].to_a |
|
|
|
|
|
|
|
|
|
|
|
added_by_current = current - base |
|
|
|
|
|
removed_by_current = base - current |
|
|
|
|
|
added_by_me = mine - base |
|
|
|
|
|
removed_by_me = base - mine |
|
|
|
|
|
|
|
|
|
|
|
if (added_by_current.empty? && |
|
|
|
|
|
removed_by_current.empty? && |
|
|
|
|
|
added_by_me.empty? && |
|
|
|
|
|
removed_by_me.empty?) |
|
|
|
|
|
return nil |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
{ field:, label:, base:, current:, mine:, added_by_current:, removed_by_current:, |
|
|
|
|
|
added_by_me:, removed_by_me:, |
|
|
|
|
|
changed_by_current: added_by_current.present? || removed_by_current.present?, |
|
|
|
|
|
changed_by_me: added_by_me.present? || removed_by_me.present?, |
|
|
|
|
|
conflict: set_snapshot_conflict?(added_by_current:, |
|
|
|
|
|
removed_by_current:, |
|
|
|
|
|
added_by_me:, |
|
|
|
|
|
removed_by_me:) } |
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def set_snapshot_conflict? added_by_current:, removed_by_current:, |
|
|
|
|
|
added_by_me:, removed_by_me: |
|
|
|
|
|
(added_by_current & removed_by_me).present? || (removed_by_current & added_by_me).present? |
|
|
|
|
|
end |
|
|
end |
|
|
end |