otwarchive-symphonyarchive/app/controllers/api/v2/bookmarks_controller.rb

230 lines
8.6 KiB
Ruby
Raw Normal View History

2026-03-11 22:22:11 +00:00
class Api::V2::BookmarksController < Api::V2::BaseController
respond_to :json
# POST - search for bookmarks for this archivist
def search
archivist = User.find_by(login: params[:archivist])
bookmarks = params[:bookmarks]
# check for top-level errors (not an archivist, no bookmarks...)
status, messages = batch_errors(archivist, bookmarks)
results = []
if status == :ok
archivist_bookmarks = Bookmark.where(pseud_id: archivist.default_pseud.id)
results = bookmarks.map do |bookmark|
found_result = {}
found_result = check_archivist_bookmark(archivist, bookmark[:url], archivist_bookmarks) unless archivist_bookmarks.empty?
bookmark_response(
status: found_result[:bookmark_status] || :not_found,
bookmark_url: found_result[:bookmark_url] || "",
bookmark_id: bookmark[:id],
original_url: bookmark[:url],
messages: found_result[:bookmark_messages] || ["No bookmark found for archivist \"#{archivist.login}\" and URL \"#{bookmark[:url]}\""]
)
end
messages = ["Successfully searched bookmarks for archivist '#{archivist.login}'"]
end
render_api_response(status, messages, bookmarks: results)
end
# POST - create a bookmark for this archivist
def create
archivist = User.find_by(login: params[:archivist])
bookmarks = params[:bookmarks]
bookmarks_responses = []
@bookmarks = []
# check for top-level errors (not an archivist, no bookmarks...)
status, messages = batch_errors(archivist, bookmarks)
if status == :ok
# Flag error and successes
@some_errors = @some_success = false
# Process the bookmarks
archivist_bookmarks = Bookmark.where(pseud_id: archivist.default_pseud_id, bookmarkable_type: "ExternalWork")
bookmarks.each do |bookmark|
bookmarks_responses << create_bookmark(archivist, bookmark, archivist_bookmarks)
end
# set final response code and message depending on the flags
status = :bad_request if bookmarks_responses.any? { |r| [:ok, :created, :found].exclude?(r[:status]) }
messages = response_message(messages)
end
render_api_response(status, messages, bookmarks: bookmarks_responses)
end
private
# Find bookmarks for this archivist
def check_archivist_bookmark(archivist, current_bookmark_url, archivist_bookmarks)
archivist_bookmarks = archivist_bookmarks
.select { |b| b&.bookmarkable.is_a?(ExternalWork) ? b&.bookmarkable&.url == current_bookmark_url : false }
.map { |b| [b, b.bookmarkable] }
if archivist_bookmarks.present?
archivist_bookmark, archivist_bookmarkable = archivist_bookmarks.first
find_bookmark_response(
bookmarkable: archivist_bookmarkable,
bookmark_status: :found,
bookmark_message: "There is already a bookmark for #{archivist.login} and the URL #{current_bookmark_url}",
bookmark_url: bookmark_url(archivist_bookmark)
)
else
find_bookmark_response(
bookmarkable: nil,
bookmark_status: :not_found,
bookmark_message: "There is no bookmark for #{archivist.login} and the URL #{current_bookmark_url}",
bookmark_url: ""
)
end
end
# Create a bookmark for this archivist using the Bookmark model
def create_bookmark(archivist, params, archivist_bookmarks)
found_result = {}
bookmark_attributes = bookmark_attributes(archivist, params)
external_work_attributes = external_work_attributes(params)
bookmark_status, bookmark_messages = external_work_errors(external_work_attributes)
bookmark_url = nil
original_url = nil
bookmarkable = nil
@some_errors = true
if bookmark_status == :ok
begin
# Check if this bookmark is already imported by filtering the archivist's bookmarks
unless archivist_bookmarks.empty?
found_result = check_archivist_bookmark(archivist, external_work_attributes[:url], archivist_bookmarks)
bookmarkable = found_result[:bookmarkable]
end
if found_result[:bookmark_status] == :found
found_result[:bookmark_status] = :already_imported
else
bookmarkable = ExternalWork.new(external_work_attributes)
bookmark = bookmarkable.bookmarks.build(bookmark_attributes)
if bookmarkable.save && bookmark.save
@bookmarks << bookmark
@some_success = true
@some_errors = false
bookmark_status = :created
bookmark_url = bookmark_url(bookmark)
bookmark_messages << "Successfully created bookmark for \"" + bookmarkable.title + "\"."
else
bookmark_status = :unprocessable_entity
bookmark_messages << bookmarkable.errors.full_messages + bookmark.errors.full_messages
end
end
rescue StandardError => exception
bookmark_status = :unprocessable_entity
bookmark_messages << exception.message
end
original_url = bookmarkable.url if bookmarkable
end
bookmark_response(
status: bookmark_status || found_result[:bookmark_status],
bookmark_url: bookmark_url || found_result[:bookmark_url],
bookmark_id: params[:id],
original_url: original_url,
messages: bookmark_messages.flatten || found_result[:bookmark_messages]
)
end
# Error handling
# Set messages based on success and error flags
def response_message(messages)
messages << if @some_success && @some_errors
"At least one bookmark was not created. Please check the individual bookmark results for further information."
elsif !@some_success && @some_errors
"None of the bookmarks were created. Please check the individual bookmark results for further information."
else
"All bookmarks were successfully created."
end
messages
end
# Handling for incomplete requests
def external_work_errors(external_work_attributes)
status = :bad_request
errors = []
# Perform basic validation which the ExternalWork model doesn't do or returns strange messages for
# (title is validated correctly in the model and so isn't checked here)
url = external_work_attributes[:url]
author = external_work_attributes[:author]
fandom = external_work_attributes[:fandom_string]
if url.nil?
# Unreachable and AO3 URLs are handled in the ExternalWork model
errors << "This bookmark does not contain a URL to an external site. Please specify a valid, non-AO3 URL."
end
if author.nil? || author == ""
errors << "This bookmark does not contain an external author name. Please specify an author."
end
if fandom.nil? || fandom == ""
errors << "This bookmark does not contain a fandom. Please specify a fandom."
end
status = :ok if errors.empty?
[status, errors]
end
# Request and response hashes
# Map JSON request to attributes for bookmark
def bookmark_attributes(archivist, params)
{
pseud_id: archivist.default_pseud_id,
bookmarker_notes: params[:bookmarker_notes],
tag_string: params[:tag_string] || "",
collection_names: params[:collection_names],
private: params[:private].blank? ? false : params[:private],
rec: params[:recommendation].blank? ? false : params[:recommendation]
}
end
# Map JSON request to attributes for external work
def external_work_attributes(params)
{
url: params[:url],
author: params[:author],
title: params[:title],
summary: params[:summary],
fandom_string: params[:fandom_string] || "",
rating_string: params[:rating_string] || "",
category_string: params[:category_string] ? params[:category_string].to_s.split(",") : [], # category is actually an array on bookmarks
relationship_string: params[:relationship_string] || "",
character_string: params[:character_string] || ""
}
end
def bookmark_response(status:, bookmark_url:, bookmark_id:, original_url:, messages:)
messages = [messages] unless messages.respond_to?('each')
{
status: status,
archive_url: bookmark_url,
original_id: bookmark_id,
original_url: original_url,
messages: messages
}
end
def find_bookmark_response(bookmarkable:, bookmark_status:, bookmark_message:, bookmark_url:)
bookmark_status = :not_found unless [:found, :not_found].include?(bookmark_status)
{
bookmarkable: bookmarkable,
bookmark_status: bookmark_status,
bookmark_messages: bookmark_message,
bookmark_url: bookmark_url
}
end
end