80 行
3.5 KiB
Ruby
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
|