225 lines
8.4 KiB
Ruby
225 lines
8.4 KiB
Ruby
class Api::V2::WorksController < Api::V2::BaseController
|
|
respond_to :json
|
|
|
|
# POST - search for works based on imported url
|
|
def search
|
|
works = params[:works]
|
|
original_urls = works.map { |w| w[:original_urls] }.flatten
|
|
|
|
results = []
|
|
messages = []
|
|
if original_urls.nil? || original_urls.blank? || original_urls.empty?
|
|
status = :empty_request
|
|
messages << "Please provide a list of URLs to find."
|
|
elsif original_urls.size >= ArchiveConfig.IMPORT_MAX_CHAPTERS
|
|
status = :too_many_request
|
|
messages << "Please provide no more than #{ArchiveConfig.IMPORT_MAX_CHAPTERS} URLs to find."
|
|
else
|
|
status = :ok
|
|
results = find_existing_works(original_urls)
|
|
messages << "Successfully searched all provided URLs."
|
|
end
|
|
render_api_response(status, messages, works: results)
|
|
end
|
|
|
|
# POST - create a work and invite authors to claim
|
|
def create
|
|
archivist = User.find_by(login: params[:archivist])
|
|
external_works = params[:items] || params[:works]
|
|
works_responses = []
|
|
|
|
# check for top-level errors (not an archivist, no works...)
|
|
status, messages = batch_errors(archivist, external_works)
|
|
|
|
if status == :ok
|
|
# Process the works, updating the flags
|
|
works_responses = external_works.map { |external_work| import_work(archivist, external_work.merge(params.permit!)) }
|
|
success_responses, error_responses = works_responses.partition { |r| [:ok, :created, :found].include?(r[:status]) }
|
|
|
|
# Send claim notification emails for successful works
|
|
if params[:send_claim_emails] && success_responses.present?
|
|
notified_authors = notify_and_return_authors(success_responses.map { |r| r[:work] }, archivist)
|
|
messages << "Claim emails sent to #{notified_authors.to_sentence}."
|
|
end
|
|
|
|
# set final status code and message depending on the flags
|
|
status = :bad_request if error_responses.present?
|
|
messages = response_message(messages, success_responses.present?, error_responses.present?)
|
|
end
|
|
render_api_response(status, messages, works: works_responses.map { |r| r.except!(:work) })
|
|
end
|
|
|
|
private
|
|
|
|
# Set messages based on success and error flags
|
|
def response_message(messages, any_success, any_errors)
|
|
messages << if any_success && any_errors
|
|
"At least one work was not imported. Please check individual work responses for further information."
|
|
elsif !any_success && any_errors
|
|
"None of the works were imported. Please check individual work responses for further information."
|
|
else
|
|
"All works were successfully imported."
|
|
end
|
|
messages
|
|
end
|
|
|
|
# Work-level error handling for requests that are incomplete or too large
|
|
def work_errors(work)
|
|
status = :bad_request
|
|
errors = []
|
|
urls = work[:chapter_urls]
|
|
if urls.nil? || urls.empty?
|
|
status = :empty_request
|
|
errors << "This work doesn't contain any chapter URLs. " +
|
|
"Works can only be imported from publicly-accessible chapter URLs."
|
|
elsif urls.length >= ArchiveConfig.IMPORT_MAX_CHAPTERS
|
|
status = :too_many_requests
|
|
errors << "This work contains too many chapter URLs. A maximum of #{ArchiveConfig.IMPORT_MAX_CHAPTERS} " \
|
|
"chapters can be imported per work."
|
|
end
|
|
status = :ok if errors.empty?
|
|
[status, errors]
|
|
end
|
|
|
|
# Search for works imported from the provided URLs
|
|
def find_existing_works(original_urls)
|
|
results = []
|
|
messages = []
|
|
original_urls.each do |original|
|
|
original_id = ""
|
|
if original.class == String
|
|
original_url = original
|
|
else
|
|
original_id = original[:id]
|
|
original_url = original[:url]
|
|
end
|
|
|
|
# Search for works - there may be duplicates
|
|
search_results = find_work_by_import_url(original_url)
|
|
if search_results[:works].empty?
|
|
results << { status: :not_found,
|
|
original_id: original_id,
|
|
original_url: original_url,
|
|
messages: [search_results[:message]] }
|
|
else
|
|
work_results = search_results[:works].map do |work|
|
|
archive_url = work_url(work)
|
|
message = "Work \"#{work.title}\", created on #{work.created_at.to_date.to_fs(:iso_date)} was found at \"#{archive_url}\"."
|
|
messages << message
|
|
{ archive_url: archive_url,
|
|
created: work.created_at,
|
|
message: message }
|
|
end
|
|
results << { status: :found,
|
|
original_id: original_id,
|
|
original_url: original_url,
|
|
search_results: work_results,
|
|
messages: messages
|
|
}
|
|
end
|
|
end
|
|
results
|
|
end
|
|
|
|
def find_work_by_import_url(original_url)
|
|
works = nil
|
|
if original_url.blank?
|
|
message = "Please provide the original URL for the work."
|
|
else
|
|
# We know the url will be identical no need for a call to find_by_url
|
|
works = Work.where(imported_from_url: original_url)
|
|
message = if works.empty?
|
|
"No work has been imported from \"" + original_url + "\"."
|
|
else
|
|
"Found #{works.size} work(s) imported from \"" + original_url + "\"."
|
|
end
|
|
end
|
|
{
|
|
original_url: original_url,
|
|
works: works,
|
|
message: message
|
|
}
|
|
end
|
|
|
|
# Use the story parser to scrape works from the chapter URLs
|
|
def import_work(archivist, external_work)
|
|
work_status, work_messages = work_errors(external_work)
|
|
work_url = ""
|
|
original_url = ""
|
|
if work_status == :ok
|
|
urls = external_work[:chapter_urls]
|
|
original_url = urls.first
|
|
storyparser = StoryParser.new
|
|
options = story_parser_options(archivist, external_work)
|
|
begin
|
|
response = storyparser.import_chapters_into_story(urls, options)
|
|
work = response[:work]
|
|
work_status = response[:status]
|
|
|
|
work.save if work_status == :created
|
|
work_url = work_url(work)
|
|
work_messages << response[:message]
|
|
rescue => exception
|
|
Rails.logger.error("------- API v2: error: #{exception.inspect}")
|
|
work_status = :unprocessable_entity
|
|
work_messages << "Unable to import this work."
|
|
work_messages << exception.message
|
|
end
|
|
end
|
|
|
|
{
|
|
status: work_status,
|
|
archive_url: work_url,
|
|
original_id: external_work[:id],
|
|
original_url: original_url,
|
|
messages: work_messages,
|
|
work: work
|
|
}
|
|
end
|
|
|
|
# Send invitations to external authors for a given set of works
|
|
def notify_and_return_authors(success_works, archivist)
|
|
notified_authors = []
|
|
external_authors = success_works.map(&:external_authors).flatten.uniq
|
|
external_authors&.each do |external_author|
|
|
external_author.find_or_invite(archivist)
|
|
# One of the external author pseuds is its email address so filter that one out
|
|
author_names = external_author.names.reject { |a| a.name == external_author.email }.map(&:name).flatten.join(", ")
|
|
notified_authors << author_names
|
|
end
|
|
notified_authors
|
|
end
|
|
|
|
# Request and response hashes
|
|
|
|
# Create options map for StoryParser
|
|
def story_parser_options(archivist, work_params)
|
|
{
|
|
archivist: archivist,
|
|
import_multiple: "chapters",
|
|
importing_for_others: true,
|
|
do_not_set_current_author: true,
|
|
post_without_preview: work_params[:post_without_preview].blank? ? true : work_params[:post_without_preview],
|
|
restricted: work_params[:restricted],
|
|
override_tags: work_params[:override_tags].nil? ? true : work_params[:override_tags],
|
|
detect_tags: work_params[:detect_tags].nil? ? true : work_params[:detect_tags],
|
|
collection_names: work_params[:collection_names],
|
|
title: work_params[:title],
|
|
fandom: work_params[:fandoms],
|
|
archive_warning: work_params[:warnings],
|
|
character: work_params[:characters],
|
|
rating: work_params[:rating],
|
|
relationship: work_params[:relationships],
|
|
category: work_params[:categories],
|
|
freeform: work_params[:additional_tags],
|
|
language_id: Language.find_by(short: work_params[:language_code])&.id,
|
|
summary: work_params[:summary],
|
|
notes: work_params[:notes],
|
|
encoding: work_params[:encoding],
|
|
external_author_name: work_params[:external_author_name],
|
|
external_author_email: work_params[:external_author_email],
|
|
external_coauthor_name: work_params[:external_coauthor_name],
|
|
external_coauthor_email: work_params[:external_coauthor_email]
|
|
}
|
|
end
|
|
end
|