Skip to content

Commit

Permalink
Merge pull request #10 from performant-software/feature/iiif5_resources
Browse files Browse the repository at this point in the history
IIIF #5 - Resources
  • Loading branch information
dleadbetter authored Jul 21, 2022
2 parents dfcee23 + 63d9a6d commit 77ec856
Show file tree
Hide file tree
Showing 57 changed files with 2,924 additions and 89 deletions.
11 changes: 10 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ gem 'pagy', '~> 5.10'
gem 'rack-cors'

# Resource API
gem 'resource_api', git: 'https://github.com/performant-software/resource-api.git', tag: 'v0.4.1'
gem 'resource_api', git: 'https://github.com/performant-software/resource-api.git', tag: 'v0.4.2'

# Use Json Web Token (JWT) for token based authentication
gem 'jwt'
Expand All @@ -39,6 +39,15 @@ gem 'controlled_vocabulary', git: 'https://github.com/performant-software/contro
# Active storage service
gem 'aws-sdk-s3'

# Image processing
gem 'mini_magick', '~> 4.11'

# EXIF data
gem 'exif', '~> 2.2.3'

# HTTP requests
gem 'httparty', '~> 0.17.3'

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem 'debug', platforms: %i[ mri mingw x64_mingw ]
Expand Down
16 changes: 14 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ GIT

GIT
remote: https://github.com/performant-software/resource-api.git
revision: fee23a1eacd9fda1d5c4dcaed15a941e12cc214c
tag: v0.4.1
revision: f1de1653b67fc1d623d7418e92d3bb3134df800e
tag: v0.4.2
specs:
resource_api (0.1.0)
pagy (~> 5.10)
Expand Down Expand Up @@ -116,8 +116,12 @@ GEM
dotenv (= 2.7.6)
railties (>= 3.2)
erubi (1.10.0)
exif (2.2.3)
globalid (1.0.0)
activesupport (>= 5.0)
httparty (0.17.3)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
io-console (0.5.11)
Expand All @@ -132,10 +136,15 @@ GEM
mini_mime (>= 0.1.1)
marcel (1.0.2)
method_source (1.0.0)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.15.0)
msgpack (1.5.1)
multi_xml (0.6.0)
net-imap (0.2.3)
digest
net-protocol
Expand Down Expand Up @@ -217,7 +226,10 @@ DEPENDENCIES
controlled_vocabulary!
debug
dotenv-rails
exif (~> 2.2.3)
httparty (~> 0.17.3)
jwt
mini_magick (~> 4.11)
pagy (~> 5.10)
pg
puma (~> 5.0)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Api::ProjectsController < Api::BaseController
search_attributes :name, :description, :uid

# Preloads
preloads Project.attachment_preloads
preloads Project.attachment_preloads, :organization

# Actions
before_action :validate_new_project, unless: -> { current_user.admin? }, only: :create
Expand Down
42 changes: 42 additions & 0 deletions app/controllers/api/resources_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class Api::ResourcesController < Api::BaseController
# Includes
include Api::Uploadable

# Search attributes
search_attributes :name

# Preloads
preloads Resource.attachment_preloads

# Actions
before_action :validate_new_resource, unless: -> { current_user.admin? }, only: :create
before_action :validate_resource, unless: -> { current_user.admin? }, only: [:update, :destroy]

protected

def base_query
subquery = Project.where(Project.arel_table[:id].eq(Resource.arel_table[:project_id]))

if !current_user.admin?
subquery = subquery
.joins(organization: :user_organizations)
.where(user_organizations: { user_id: current_user.id })
end

subquery = subquery.where(id: params[:project_id]) if params[:project_id].present?

Resource.where(subquery.arel.exists)
end

private

def validate_new_resource
project = Project.find(params[:resource][:project_id])
check_authorization project.organization_id
end

def validate_resource
resource = Resource.find(params[:id])
check_authorization resource.project.organization_id
end
end
30 changes: 29 additions & 1 deletion app/models/concerns/attachable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,39 @@ def generate_url_method(name)
url_for(attachment)
end

define_method("#{name}_iiif_url") do |page_number = nil|
attachment = self.send(name)
return nil unless attachment.attached?

"#{self.send("#{name}_base_url")}#{page_number.nil? ? '' : ";#{page_number}"}/full/max/0/default.jpg"
end

define_method("#{name}_base_url") do
attachment = self.send(name)
return nil unless attachment.attached?

"#{ENV['IIIF_HOST']}/iiif/3/#{attachment.key}"
end

define_method("#{name}_download_url") do
attachment = self.send(name)
return nil unless attachment.attached?

attachment.service_url({ disposition: 'attachment' })
attachment.url(disposition: 'attachment')
end

define_method("#{name}_preview_url") do
attachment = self.send(name)
return nil unless attachment.attached?

"#{self.send("#{name}_base_url")}/full/^500,/0/default.jpg"
end

define_method("#{name}_thumbnail_url") do
attachment = self.send(name)
return nil unless attachment.attached?

"#{self.send("#{name}_base_url")}/square/^250,250/0/default.jpg"
end
end

Expand Down
21 changes: 20 additions & 1 deletion app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,27 @@ class Project < ApplicationRecord
belongs_to :organization

# Resourceable attributes
allow_params :organization_id, :name, :description, :api_key, :avatar
allow_params :organization_id, :name, :description, :api_key, :avatar, :metadata

# ActiveStorage
has_one_attached :avatar

# Validations
validate :validate_metadata

private

def validate_metadata
return unless self.metadata?

JSON.parse(self.metadata).each do |item|
errors.add(:metadata, I18n.t('errors.project.metadata.name_empty')) unless item['name'].present?
errors.add(:metadata, I18n.t('errors.project.metadata.type_empty')) unless item['type'].present?

next unless item['type'] == 'dropdown'

errors.add(:metadata, I18n.t('errors.project.metadata.options_empty')) unless item['options'].present?
errors.add(:metadata, I18n.t('errors.project.metadata.options_duplicate')) unless item['options'].size == item['options'].uniq.size
end
end
end
115 changes: 115 additions & 0 deletions app/models/resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
class Resource < ApplicationRecord
# Includes
include Attachable

# Relationships
belongs_to :project

# Resourceable parameters
allow_params :project_id, :name, :metadata, :content

# Callbacks
before_create :set_uuid
after_create_commit :after_create

# ActiveStorage
has_one_attached :content
has_one_attached :content_converted

# Delegates
delegate :audio?, to: :content
delegate :image?, to: :content
delegate :video?, to: :content

# Validations
validate :validate_metadata

# Overload attachable methods
alias_method :attachable_content_base_url, :content_base_url

def content_base_url
return attachable_content_base_url unless content_converted.attached?

"#{ENV['IIIF_HOST']}/iiif/3/#{content_converted.key}"
end

def content_type
return content.content_type unless content_converted.attached?

content_converted.content_type
end

def pdf?
content.content_type == 'application/pdf'
end

protected

def after_create
# Convert the image to a TIFF
convert

# Create the manifest
create_manifest

# Extract EXIF data
extract_exif
end

private

def convert
return unless image? && content.attached?

content.open do |file|
filepath = Images::Convert.to_tiff(file)
filename = Images::Convert.filename(content.filename.to_s, 'tif')

content_converted.attach(
io: File.open(filepath),
filename: filename,
content_type: 'image/tiff'
)
end
end

def create_manifest
self.manifest = Iiif::Manifest.create(self)
save
end


def extract_exif
return unless content.attached? && content.image?

content.open do |file|
data = Images::Exif.extract(file)

unless data.nil?
self.exif = JSON.dump(data)
save
end
end
end

def set_uuid
self.uuid = SecureRandom.uuid
end

def validate_metadata
items = JSON.parse(project.metadata || '[]')
return if items.nil? || items.empty?

values = JSON.parse(metadata || '{}')

items.each do |item|
required = item['required'].to_s.to_bool
name = item['name']
value = values[name]

if required && (value.nil? || value.empty?)
errors.add("metadata[#{name}]", I18n.t('errors.resource.metadata.required', name: name))
end
end
end
end
4 changes: 2 additions & 2 deletions app/serializers/projects_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ProjectsSerializer < BaseSerializer
index_attributes :id, :uid, :name, :description, :avatar_url, :organization_id, organization: OrganizationsSerializer
show_attributes :id, :uid, :name, :description, :api_key, :avatar_url, :organization_id, organization: OrganizationsSerializer
index_attributes :id, :uid, :name, :description, :avatar_thumbnail_url, :organization_id, organization: OrganizationsSerializer
show_attributes :id, :uid, :name, :description, :api_key, :avatar_url, :avatar_preview_url, :metadata, :organization_id, organization: OrganizationsSerializer
end
4 changes: 4 additions & 0 deletions app/serializers/resources_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class ResourcesSerializer < BaseSerializer
index_attributes :id, :name, :metadata, :exif, :project_id, :content_thumbnail_url, :content_type
show_attributes :id, :name, :metadata, :exif, :project_id, :content_url, :content_preview_url, :content_download_url, :manifest, :content_type
end
4 changes: 2 additions & 2 deletions app/serializers/users_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class UsersSerializer < BaseSerializer
index_attributes :id, :name, :email, :admin, :avatar_url
show_attributes :id, :name, :email, :admin, :avatar_url, user_organizations: [:id, :organization_id, organization: OrganizationsSerializer]
index_attributes :id, :name, :email, :admin, :avatar_thumbnail_url
show_attributes :id, :name, :email, :admin, :avatar_url, :avatar_preview_url, user_organizations: [:id, :organization_id, organization: OrganizationsSerializer]
end
Loading

0 comments on commit 77ec856

Please sign in to comment.