ファイル
btrc-hub/backend/app/services/post_import_previewer.rb
T
2026-07-11 21:11:36 +09:00

80 行
3.5 KiB
Ruby

class PostImportPreviewer
FIELDS = %w[title thumbnail_base original_created_from original_created_before duration tags parent_post_ids].freeze
def initialize parsed:, url_column:, mappings: { }, existing: 'skip'
@parsed = parsed
@url_column = url_column.to_i
@mappings = mappings.to_h.stringify_keys
@existing = existing == 'error' ? 'error' : 'skip'
end
def preview
urls = { }
@parsed[:rows].map do |row|
values = row[:values]
attributes = FIELDS.to_h { |field| [field, mapped_value(field, values)] }
url = values[@url_column].to_s.strip
metadata, warnings = fetch_metadata(url)
attributes = metadata.merge(attributes) { |_key, automatic, input| input.presence || automatic }
post = Post.new(url:)
post.valid?
normal_url = post.url
errors = post.errors.to_hash.transform_values(&:dup)
errors[:url] = ['URL が重複しています.'] if normal_url.present? && urls.key?(normal_url)
urls[normal_url] = true if normal_url.present?
if normal_url.present? && Post.exists?(url: normal_url)
@existing == 'error' ? errors[:url] = ['既存投稿です.'] : warnings << '既存投稿のためスキップします.'
end
validate_preview_tags(attributes['tags'], errors)
validate_parents(attributes['parent_post_ids'], errors)
{ source_row: row[:source_row], url: normal_url || url, attributes:, warnings:, errors:,
status: errors.present? ? 'error' : (warnings.present? ? 'warning' : 'ready'),
existing: normal_url.present? && Post.exists?(url: normal_url) }
end
end
private
def mapped_value field, values
mapping = @mappings[field]
return '' if mapping.blank?
return mapping['value'].to_s if mapping['kind'] == 'fixed'
Array(mapping['columns']).filter_map { |index| values[index.to_i].to_s.presence }.join(field == 'tags' ? ' ' : '')
end
def fetch_metadata url
return [{ }, ['URL が空です.']] if url.blank?
data = PostMetadataFetcher.fetch(url)
warnings = []
warnings << 'タイトルを取得できませんでした.' if data[:title].blank?
warnings << 'サムネールを取得できませんでした.' if data[:thumbnail_base].blank?
[data.compact, warnings]
rescue Preview::UrlSafety::UnsafeUrl, Preview::HttpFetcher::FetchFailed, Preview::HttpFetcher::ResponseTooLarge => e
[{ }, ["メタデータを取得できませんでした: #{ e.message }"]]
end
def validate_preview_tags raw, errors
names = raw.to_s.split
return if names.empty?
if names.any? { _1.downcase.start_with?('nico:') }
errors[:tags] = ['ニコニコ・タグは直接指定できません.']
return
end
canonical = names.map { TagName.canonicalise(_1.sub(/\[.*\]\z/, '')).first }
if Tag.joins(:tag_name).where(tag_names: { name: canonical }, deprecated_at: nil).count < canonical.uniq.length
errors[:tags] = ['存在しないタグ又は廃止済みタグがあります.']
end
rescue Tag::SectionLiteralParseError
errors[:tags] = ['タグ区間の記法が不正です.']
end
def validate_parents raw, errors
ids = raw.to_s.split.map { Integer(_1, exception: false) }
return if ids.compact.empty? && raw.to_s.blank?
errors[:parent_post_ids] = ['親投稿 Id. が不正です.'] and return if ids.any? { _1.nil? || _1 <= 0 }
errors[:parent_post_ids] = ['存在しない親投稿 Id. があります.'] if Post.where(id: ids).count != ids.uniq.length
end
end