Skip to content

Commit

Permalink
Create an endpoint for exposing the data as JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
jcoyne committed Sep 5, 2019
1 parent 8923d60 commit 13b617b
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 14 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Expand Up @@ -35,3 +35,7 @@ RSpec/MultipleExpectations:
RSpec/ExpectActual:
Exclude:
- 'spec/routing/**'

RSpec/DescribeClass:
Exclude:
- 'spec/requests/**'
19 changes: 13 additions & 6 deletions .rubocop_todo.yml
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-06-04 08:56:27 -0500 using RuboCop version 0.65.0.
# on 2019-09-05 13:08:27 -0500 using RuboCop version 0.65.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -23,13 +23,20 @@ Lint/MissingCopEnableDirective:
Exclude:
- 'spec/dor/update_marc_record_service_spec.rb'

# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
Lint/UnusedMethodArgument:
Exclude:
- 'app/services/public_desc_metadata_service.rb'

# Offense count: 2
Lint/UriEscapeUnescape:
Exclude:
- 'app/controllers/sdr_controller.rb'
- 'spec/controllers/sdr_controller_spec.rb'

# Offense count: 22
# Offense count: 23
Metrics/AbcSize:
Max: 91

Expand All @@ -38,7 +45,7 @@ Metrics/AbcSize:
Metrics/ClassLength:
Max: 124

# Offense count: 4
# Offense count: 5
Metrics/CyclomaticComplexity:
Max: 11

Expand All @@ -47,7 +54,7 @@ Metrics/CyclomaticComplexity:
Metrics/MethodLength:
Max: 43

# Offense count: 3
# Offense count: 4
Metrics/PerceivedComplexity:
Max: 11

Expand Down Expand Up @@ -166,7 +173,7 @@ RSpec/InstanceVariable:
- 'spec/services/cleanup_reset_service_spec.rb'
- 'spec/services/registration_service_spec.rb'

# Offense count: 39
# Offense count: 41
# Configuration parameters: EnforcedStyle.
# SupportedStyles: have_received, receive
RSpec/MessageSpies:
Expand Down Expand Up @@ -357,7 +364,7 @@ Style/StringLiterals:
- 'spec/rails_helper.rb'
- 'spec/spec_helper.rb'

# Offense count: 4
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: .
# SupportedStyles: percent, brackets
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Expand Up @@ -25,6 +25,7 @@ gem 'honeybadger'
gem 'okcomputer'

gem 'faraday'
gem 'jbuilder'
gem 'jwt'
gem 'marc'
gem 'rest-client'
Expand All @@ -36,6 +37,8 @@ gem 'uuidtools', '~> 2.1.4'

# DLSS/domain-specific dependencies
gem 'dor-services', '~> 7.0'
gem 'dry-struct'
gem 'dry-types'

group :test, :development do
gem 'coveralls', '~> 0.8', require: false
Expand Down
11 changes: 11 additions & 0 deletions Gemfile.lock
Expand Up @@ -173,6 +173,11 @@ GEM
dry-initializer (~> 3.0)
dry-logic (~> 1.0)
dry-types (~> 1.0)
dry-struct (1.0.0)
dry-core (~> 0.4, >= 0.4.3)
dry-equalizer (~> 0.2)
dry-types (~> 1.0)
ice_nine (~> 0.11)
dry-types (1.1.1)
concurrent-ruby (~> 1.0)
dry-container (~> 0.3)
Expand Down Expand Up @@ -205,8 +210,11 @@ GEM
domain_name (~> 0.5)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
iso-639 (0.2.8)
jaro_winkler (1.5.3)
jbuilder (2.9.1)
activesupport (>= 4.2.0)
json (2.2.0)
jwt (2.2.1)
link_header (0.0.8)
Expand Down Expand Up @@ -448,9 +456,12 @@ DEPENDENCIES
coveralls (~> 0.8)
dlss-capistrano
dor-services (~> 7.0)
dry-struct
dry-types
equivalent-xml
faraday
honeybadger
jbuilder
jwt
listen (~> 3.0.5)
marc
Expand Down
12 changes: 6 additions & 6 deletions app/controllers/content_controller.rb
Expand Up @@ -2,21 +2,17 @@

# API to retrieve file listings and file content from the DOR workspace
class ContentController < ApplicationController
rescue_from ActionController::MissingFile do
render status: :not_found
end

def read
location = druid_tools.find(:content, params[:path])
return render status: :not_found unless location
return not_found(location) unless location

send_file location
end

def list
location = druid_tools.content_dir(false)

raise ActionController::MissingFile, location unless Dir.exist? location
return not_found(location) unless Dir.exist? location

render json: {
items: Dir.glob(File.join(location, '**', '*')).map do |file|
Expand All @@ -32,6 +28,10 @@ def list

private

def not_found(location)
render status: :not_found, plain: "Unable to locate file #{location}"
end

def druid_tools
DruidTools::Druid.new(params[:id], Settings.content.content_base_dir)
end
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/objects_controller.rb
Expand Up @@ -46,6 +46,10 @@ def update
head :no_content
end

def show
render json: Cocina::Mapper.build(@item)
end

def publish
PublishMetadataService.publish(@item)
head :created
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/queries_controller.rb
@@ -0,0 +1,12 @@
# frozen_string_literal: true

# Responds to queries about objects
class QueriesController < ApplicationController
before_action :load_item, only: [:collections]

# Returns a list of collections this object is in.
def collections
# If we move to Valkyrie this can be find_inverse_references_by
@collections = @item.collections.map { |collection| Cocina::Mapper.build(collection) }
end
end
18 changes: 18 additions & 0 deletions app/models/cocina/dro.rb
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Cocina
# A digital repository object. See https://github.com/sul-dlss-labs/taco/blob/master/maps/DRO.json
class DRO < Dry::Struct
attribute :externalIdentifier, Types::Strict::String
attribute :type, Types::Strict::String
attribute :label, Types::Strict::String

def as_json(*)
{
externalIdentifier: externalIdentifier,
type: type,
label: label
}
end
end
end
8 changes: 8 additions & 0 deletions app/models/cocina/types.rb
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module Cocina
# This defines the data types supported by the model
module Types
include Dry.Types()
end
end
36 changes: 36 additions & 0 deletions app/services/cocina/mapper.rb
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Cocina
# Maps Dor::Items to Cocina objects
class Mapper
def self.build(item)
new(item).build
end

def initialize(item)
@item = item
end

def build
Cocina::DRO.new(externalIdentifier: item.pid,
type: type,
label: item.label)
end

private

attr_reader :item

# @todo This should have more speicific type such as found in identityMetadata.objectType
def type
case item
when Dor::Item
'object'
when Dor::Collection
'collection'
else
raise "Unknown type for #{item.class}"
end
end
end
end
2 changes: 1 addition & 1 deletion app/services/public_desc_metadata_service.rb
Expand Up @@ -16,7 +16,7 @@ def doc
end

# @return [String] Public descriptive medatada XML
def to_xml(include_access_conditions: true)
def to_xml(include_access_conditions: true, prefixes: nil, template: nil)
ng_xml(include_access_conditions: include_access_conditions).to_xml
end

Expand Down
3 changes: 3 additions & 0 deletions app/views/queries/collections.json.jbuilder
@@ -0,0 +1,3 @@
# frozen_string_literal: true

json.collections @collections, :externalIdentifier
8 changes: 7 additions & 1 deletion config/routes.rb
Expand Up @@ -20,7 +20,7 @@
get 'catkey', to: 'marcxml#catkey'
end

resources :objects, only: [:create, :update] do
resources :objects, only: [:create, :update, :show] do
member do
post 'publish'
post 'update_embargo'
Expand All @@ -33,6 +33,12 @@
get 'contents/*path', to: 'content#read', format: false, as: :read_content
end

resource :query, only: [], defaults: { format: :json } do
collection do
get 'collections'
end
end

resource :workspace, only: [:create, :destroy]

resources :metadata, only: [] do
Expand Down
27 changes: 27 additions & 0 deletions spec/requests/collections_for_object_spec.rb
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Get the object' do
let(:payload) { { sub: 'argo' } }
let(:jwt) { JWT.encode(payload, Settings.dor.hmac_secret, 'HS256') }
let(:basic_auth) { ActionController::HttpAuthentication::Basic.encode_credentials(user, password) }
let(:object) { instance_double(Dor::Item, collections: [collection]) }
let(:collection_id) { 'druid:999123' }
let(:collection) do
Dor::Collection.new(pid: collection_id, label: 'collection #1')
end

before do
allow(Dor).to receive(:find).and_return(object)
end

describe 'as used by WAS crawl seed registration' do
it 'returns (at a minimum) the identifiers of the collections ' do
get '/v1/objects/druid:mk420bs7601/query/collections',
headers: { 'X-Auth' => "Bearer #{jwt}" }
expect(response).to be_successful
expect(response.body).to eq '{"collections":[{"externalIdentifier":"druid:999123"}]}'
end
end
end
52 changes: 52 additions & 0 deletions spec/requests/show_object_spec.rb
@@ -0,0 +1,52 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Get the object' do
let(:payload) { { sub: 'argo' } }
let(:jwt) { JWT.encode(payload, Settings.dor.hmac_secret, 'HS256') }
let(:basic_auth) { ActionController::HttpAuthentication::Basic.encode_credentials(user, password) }
let(:object) { Dor::Item.new(pid: 'druid:1234') }

before do
allow(Dor).to receive(:find).and_return(object)
end

context 'when the object exists' do
before do
object.descMetadata.title_info.main_title = 'Hello'
object.label = 'foo'
end

it 'returns the object' do
get '/v1/objects/druid:mk420bs7601',
headers: { 'X-Auth' => "Bearer #{jwt}" }
expect(response).to be_successful
expect(response.body).to eq '{"externalIdentifier":"druid:1234","type":"object","label":"foo"}'
end
end

describe 'as used by WAS crawl seed registration' do
# In this case it's necessary that the URL for the crawl be in the label field
# and the collection id is passed

let(:collection_id) { 'druid:999123' }
let(:collection) { instance_double(Dor::Collection, is_a?: true, new_record?: false, pid: collection_id) }

before do
# Stubbing out the internals of ActiveFedora here:
allow(Dor::Collection).to receive(:find).and_return([collection])

object.descMetadata.title_info.main_title = 'Hello'
object.label = 'foo'
object.collection_ids = [collection_id]
end

it 'returns minimally the label (url) and collection id' do
get '/v1/objects/druid:mk420bs7601',
headers: { 'X-Auth' => "Bearer #{jwt}" }
expect(response).to be_successful
expect(response.body).to eq '{"externalIdentifier":"druid:1234","type":"object","label":"foo"}'
end
end
end

0 comments on commit 13b617b

Please sign in to comment.