diff --git a/app/controllers/api/v1/oni_controller.rb b/app/controllers/api/v1/oni_controller.rb index 555a8523..dc031191 100644 --- a/app/controllers/api/v1/oni_controller.rb +++ b/app/controllers/api/v1/oni_controller.rb @@ -2,16 +2,11 @@ module Api module V1 class OniController < ApplicationController def objects - limit = (params[:limit] || 100).to_i - offset = (params[:offset] || 0).to_i - sortBy = params[:sortBy] || 'identifier' - memberOf = params[:memberOf] - unless %w[identifier title created_at].include?(sortBy) - orderBy = 'identifier' - end - sortDirection = (params[:sortDirection] || '').upcase === 'DESC' ? 'DESC' : 'ASC' - conforms_to = params[:conformsTo] + query = Oni::ObjectsValidator.new(params) + + render json: { errors: query.errors.full_messages }, status: :unprocessable_entity unless query.valid? + # TODO: Expand this once we have an authenticated version of this endpoint collections_table = Collection.where(private: false).arel_table items_table = Item.where(private: false).arel_table @@ -26,11 +21,11 @@ def objects collections_query = collections_table.where(collections_table[:private].eq(false)) items_query = items_table .join(collections_table).on(items_table[:collection_id].eq(collections_table[:id])) - .project(items_table[:id], items_table[:created_at], item_identifier, items_table[:title], item_label.as('type')) + .project(items_table[:id], items_table[:created_at], items_table[:updated_at], item_identifier, items_table[:title], item_label.as('type')) .where(items_table[:private].eq(false)) - if memberOf - md = params[:memberOf].match(repository_collection_url(collection_identifier: '(.*)')) + if query.member_of + md = query.member_of.match(repository_collection_url(collection_identifier: '(.*)')) unless md render json: { error: 'Invalid memberOf parameter' }, status: :bad_request return @@ -40,17 +35,17 @@ def objects items_query = items_query.where(items_table[:collection_id].in(collections_query.clone.project(collections_table[:id]))) end - collections_query = collections_query.project(:id, :created_at, :identifier, :title, collection_label.as('type')) + collections_query = collections_query.project(:id, :created_at, :updated_at, :identifier, :title, collection_label.as('type')) - combined_query = case conforms_to - when 'https://w3id.org/ldac/profile#Collection' + combined_query = case query.conforms_to + when ['https://w3id.org/ldac/profile#Collection'] # A bit hacky but we dont' have colletctions of collections - if memberOf + if query.member_of collections_query.where(collections_table[:identifier].eq('DUMMYsajkdhakshfvksfslkj')) else collections_query end - when 'https://w3id.org/ldac/profile#Item' + when ['https://w3id.org/ldac/profile#Object'] items_query else collections_query.union(items_query) @@ -65,7 +60,7 @@ def objects # Final query with limit and offset final_query = Arel::SelectManager.new(Arel::Table.engine) - final_query.from(combined_query.as('combined')).project(Arel.star).order("#{sortBy} #{sortDirection}").skip(offset).take(limit) + final_query.from(combined_query.as('combined')).project(Arel.star).order("#{query.sort} #{query.order}").skip(query.offset).take(query.limit) ids = ActiveRecord::Base.connection.select_all(final_query.to_sql) @@ -75,7 +70,7 @@ def objects collections = Collection.where(id: collection_ids).includes(:access_condition) items = Item.where(id: item_ids).includes(:collection, :access_condition, :content_languages) - @data = ids.map do |id| + @objects = ids.map do |id| if id['type'] == 'collection' collections.find { |c| c.id == id['id'] } else diff --git a/app/validators/oni/objects_validator.rb b/app/validators/oni/objects_validator.rb new file mode 100644 index 00000000..8b90e7e7 --- /dev/null +++ b/app/validators/oni/objects_validator.rb @@ -0,0 +1,44 @@ +module Oni + class ObjectsValidator + include ActiveModel::Validations + + # NOTE: We remap name to title below to match the database column + SORT_FIELDS = %w[id title created_at updated_at].freeze + ORDER_FIELDS = %w[asc desc].freeze + CONFORMS_TO_VALUES = %w[https://w3id.org/ldac/profile#Collection https://w3id.org/ldac/profile#Object].freeze + + ATTRIBUTES = %i[member_of conforms_to limit offset order sort].freeze + attr_accessor(*ATTRIBUTES) + + validates :member_of, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_nil: true + validates :conforms_to, inclusion: { in: CONFORMS_TO_VALUES, message: '%{value} is not a valid conformsTo' }, allow_nil: true + validates :order, inclusion: { in: ORDER_FIELDS, message: '%{value} is not a valid order' }, allow_nil: true + validates :sort, inclusion: { in: SORT_FIELDS, message: '%{value} is not a valid sort field' }, allow_nil: true + validates :limit, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 1000 }, allow_nil: true + validates :offset, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true + + def initialize(params) + object_params = params.permit(*ATTRIBUTES.map { | attr| attr.to_s.camelize(:lower).to_sym }).except(:format) + object_params.each do |key, value| + snake_key = key.to_s.underscore + + if %w[limit offset].include?(snake_key) + value = value.to_i if value.present? + end + + value = value.underscore if snake_key == 'sort' && value.present? + + snake_key = 'title' if snake_key == 'name' + + value = value.split(',') if snake_key == 'conforms_to' && value.present? + + send("#{snake_key}=", value) if respond_to?("#{snake_key}=") + end + + @limit ||= 1000 + @offset ||= 0 + @order ||= 'asc' + @sort ||= 'title' + end + end +end diff --git a/app/views/api/v1/oni/objects.json.jb b/app/views/api/v1/oni/objects.json.jb index 39332fa7..708c6745 100644 --- a/app/views/api/v1/oni/objects.json.jb +++ b/app/views/api/v1/oni/objects.json.jb @@ -1,22 +1,24 @@ { total: @total, - data: @data.map do |data| + objects: @objects.map do |object| class_name = data.class.name is_item = class_name == 'Item' response = { - crateId: is_item ? repository_item_url(data.collection, data) : repository_collection_url(data), + id: is_item ? repository_item_url(object.collection, object) : repository_collection_url(object), + name: object.title, + description: object.description.truncate(256), conformsTo: "https://w3id.org/ldac/profile##{is_item ? 'Object' : 'Collection'}", recordType: ['Data', 'Object', is_item ? 'RepositoryObject' : 'RepositoryCollection'], - name: data.title, - license: data.access_condition_name, - description: data.description.truncate(256), - inLanguage: (is_item ? data.content_languages : data.languages).map { |language| { name: language.name, code: language.code } } + # FIXME: Outside of current API spec and subject to change + inLanguage: (is_item ? object.content_languages : object.languages).map { |language| { name: language.name, code: language.code } }[0] } if is_item - response[:memberOf] = repository_collection_url(data.collection) + response[:memberOf] = repository_collection_url(object.collection) + response[:parent] = repository_collection_url(object.collection) + response[:root] = repository_collection_url(object.collection) end response diff --git a/config/application.rb b/config/application.rb index 9fff361c..bbff6fa0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -32,6 +32,7 @@ class Application < Rails::Application config.solid_queue.silence_polling = true ActiveSupport::Dependencies.autoload_paths << Rails.root.join('app/services') + ActiveSupport::Dependencies.autoload_paths << Rails.root.join('app/validators') ActiveSupport::Dependencies.autoload_paths << Rails.root.join('lib') config.viewer_url = '/viewer'