Skip to content
This repository has been archived by the owner on Jun 25, 2020. It is now read-only.

Commit

Permalink
Merge pull request #771 from justarrived/base64-image-upload
Browse files Browse the repository at this point in the history
Convert API multipart image upload to data URI
  • Loading branch information
buren authored Jan 4, 2017
2 parents f4d9c08 + 997ef79 commit 6489dcf
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 77 deletions.
26 changes: 24 additions & 2 deletions app/controllers/api/v1/companies/company_images_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ class CompanyImagesController < BaseController
api :POST, '/companies/images/', 'Company images'
description 'Creates a user image'
error code: 422, desc: 'Unprocessable entity'
param :image, File, desc: 'Image (multipart/form-data)', required: true
param :data, Hash, desc: 'Top level key', required: true do
param :attributes, Hash, desc: 'User image attributes', required: true do
param :image, String, desc: 'Image (data uri, data/image)', required: true
end
end
example Doxxer.read_example(CompanyImage, method: :create)
def create
authorize(CompanyImage)

@company_image = CompanyImage.new(image: params[:image])
data_image = DataUriImage.new(company_image_params[:image])
unless data_image.valid?
respond_with_invalid_image_content_type
return
end

@company_image = CompanyImage.new(image: data_image.image)

if @company_image.save
api_render(@company_image, status: :created)
Expand All @@ -36,6 +46,18 @@ def show

private

def company_image_params
jsonapi_params.permit(:image)
end

def respond_with_invalid_image_content_type
errors = JsonApiErrors.new
message = I18n.t('errors.user.invalid_image_content_type')
errors.add(status: 422, detail: message)

render json: errors, status: :unprocessable_entity
end

def set_company_image
@company_image = CompanyImage.find(params[:id])
end
Expand Down
20 changes: 17 additions & 3 deletions app/controllers/api/v1/users/user_images_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,25 @@ def show
api :POST, '/users/:user_id/images/', 'User images'
description 'Creates a user image'
error code: 422, desc: 'Unprocessable entity'
param :image, File, desc: 'Image (multipart/form-data)', required: true
param :data, Hash, desc: 'Top level key', required: true do
param :attributes, Hash, desc: 'User image attributes', required: true do
param :category, UserImage::CATEGORIES.keys, desc: 'User image category', required: true # rubocop:disable Metrics/LineLength
param :image, String, desc: 'Image (data uri, data/image)', required: true
end
end
example Doxxer.read_example(UserImage, method: :create)
def create
authorize(UserImage)

data_image = DataUriImage.new(user_image_params[:image])
unless data_image.valid?
respond_with_invalid_image_content_type
return
end

attributes = {
user: @user,
image: params[:image],
image: data_image.image,
category: user_image_params[:category]
}
@user_image = UserImage.replace_image(**attributes)
Expand All @@ -46,6 +52,14 @@ def create

private

def respond_with_invalid_image_content_type
errors = JsonApiErrors.new
message = I18n.t('errors.user.invalid_image_content_type')
errors.add(status: 422, detail: message)

render json: errors, status: :unprocessable_entity
end

def set_user
@user = User.find(params[:user_id])
end
Expand All @@ -55,7 +69,7 @@ def set_user_image
end

def user_image_params
jsonapi_params.permit(:category)
jsonapi_params.permit(:category, :image)
end
end
end
Expand Down
26 changes: 23 additions & 3 deletions app/controllers/api/v1/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,29 @@ def destroy
api :POST, '/users/images/', 'User images'
description 'Creates a user image'
error code: 422, desc: 'Unprocessable entity'
param :image, File, desc: 'Image (multipart/form-data)', required: true
param :data, Hash, desc: 'Top level key', required: true do
param :attributes, Hash, desc: 'User image attributes', required: true do
param :category, UserImage::CATEGORIES.keys, desc: 'User image category', required: true # rubocop:disable Metrics/LineLength
param :image, String, desc: 'Image (data uri, data/image)', required: true
end
end
example Doxxer.read_example(UserImage, method: :create)
def images
authorize(UserImage)

@user_image = UserImage.new(image: params[:image])
if params[:image]
@user_image = UserImage.new(image: params[:image])
ActiveSupport::Deprecation.warn('Using multipart to upload an image is deprecated and will soon be removed please consult the documentation at api.justarrived.se to see the new method.') # rubocop:disable Metrics/LineLength
else
data_image = DataUriImage.new(user_image_params[:image])
unless data_image.valid?
respond_with_invalid_image_content_type
return
end

@user_image = UserImage.new(image: data_image.image)
end

@user_image.category = if user_image_params[:category].blank?
ActiveSupport::Deprecation.warn('Not setting an image category has been deprecated, please provide a "category" param.') # rubocop:disable Metrics/LineLength
@user_image.default_category
Expand Down Expand Up @@ -257,12 +269,20 @@ def statuses

private

def respond_with_invalid_image_content_type
errors = JsonApiErrors.new
message = I18n.t('errors.user.invalid_image_content_type')
errors.add(status: 422, detail: message)

render json: errors, status: :unprocessable_entity
end

def set_user
@user = User.find(params[:user_id])
end

def user_image_params
jsonapi_params.permit(:category)
jsonapi_params.permit(:category, :image)
end

def user_params
Expand Down
31 changes: 31 additions & 0 deletions app/support/data_uri_image.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true
class DataUriImage
def initialize(data_uri)
@valid_data_uri = true
@image = Paperclip.io_adapters.for(data_uri)
@content_type = @image.content_type
rescue Paperclip::AdapterRegistry::NoHandlerError => _e
@valid_data_uri = false
end

def image
return unless valid?

content_type = @image.content_type
@image.original_filename = ImageContentTypeHelper.original_filename(content_type)

@image
end

def valid?
valid_data_uri? && valid_content_type?
end

def valid_data_uri?
@valid_data_uri
end

def valid_content_type?
ImageContentTypeHelper.valid?(@content_type)
end
end
19 changes: 0 additions & 19 deletions app/support/file_validator.rb

This file was deleted.

30 changes: 30 additions & 0 deletions app/support/image_content_type_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true
class ImageContentTypeHelper
ALLOWED_IMAGE_CONTENT_TYPES = %w(image/jpeg image/png).freeze
ALLOWED_FILE_EXTENSIONS = %w(jpeg png).freeze

def self.valid?(content_type)
content_type_allowed = ALLOWED_IMAGE_CONTENT_TYPES.include?(content_type)

type = _file_type(content_type)
extension_allowed = ALLOWED_FILE_EXTENSIONS.include?(type)

content_type_allowed && extension_allowed
end

def self.original_filename(content_type)
return unless valid?(content_type)

SecureGenerator.token(length: 64) + file_extension(content_type)
end

def self.file_extension(content_type)
return unless valid?(content_type)

'.' + _file_type(content_type)
end

def self._file_type(content_type)
content_type.split('/').last
end
end
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ en:
banned: an admin has banned you for not following the Terms of Agreement or the Code of Conduct. Please contact [email protected], to resolve this issue.
wrong_email_or_phone_or_password: wrong email, phone or password
user:
invalid_image_content_type: invalid content type for image
wrong_password: wrong password
password_length: password must have at least %{count} characters
must_be_available_locale: must be a supported language
Expand Down
1 change: 1 addition & 0 deletions config/locales/sv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ sv:
banned: en administratör har blockerat dig eftersom du inte följt Användarvillkoren. Vad god kontakta help@justarrived.
wrong_email_or_phone_or_password: fel email, telefonnummer eller lösenord
user:
invalid_image_content_type: ogilltig filtyp
wrong_password: fel lösenord
password_length: lösenordet måste vara minst %{count} tecken
must_be_available_locale: ditt språk stöds tyvärr inte
Expand Down
8 changes: 7 additions & 1 deletion spec/controllers/companies/company_images_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
RSpec.describe Api::V1::Companies::CompanyImagesController, type: :controller do
describe 'POST #create' do
let(:valid_attributes) do
{ image: TestImageFileReader.image }
{
data: {
attributes: {
image: TestImageFileReader.image
}
}
}
end

let(:invalid_attributes) do
Expand Down
6 changes: 4 additions & 2 deletions spec/controllers/users/user_images_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
let(:valid_params) do
{
user_id: user.to_param,
image: TestImageFileReader.image,
data: {
attributes: { category: category }
attributes: {
image: TestImageFileReader.image,
category: category
}
}
}
end
Expand Down
20 changes: 17 additions & 3 deletions spec/controllers/users_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,11 @@
let(:category) { UserImage::CATEGORIES.keys.last }
let(:valid_attributes) do
{
image: TestImageFileReader.image,
data: {
attributes: { category: category }
attributes: {
category: category,
image: TestImageFileReader.image
}
}
}
end
Expand All @@ -389,11 +391,23 @@
end

it 'assigns the default user image category if none given' do
attrs = valid_attributes.slice(:image)
attrs = valid_attributes.dup
attrs[:data][:attributes][:category] = nil

post :images, params: attrs, headers: {}
user_image = assigns(:user_image)
expect(user_image.category).to eq(user_image.default_category)
end

context 'DEPRECATED' do
it 'assigns the default user image category if none given' do
attrs = { image: TestImageFileReader.image_file }

post :images, params: attrs, headers: {}
user_image = assigns(:user_image)
expect(user_image.category).to eq(user_image.default_category)
end
end
end

context 'with invalid params' do
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_support/rails_helpers/test_image_file_reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

module TestImageFileReader
def self.image
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAEElEQVR4AWMQ7d2BjCjlAwA4QCHL+OA2ggAAAABJRU5ErkJggg==' # rubocop:disable Metrics/LineLength
end

def self.image_file
test_file_name = "#{Rails.root}/spec/spec_support/data/1x1.jpg"
Rack::Test::UploadedFile.new(test_file_name, 'image/png')
end
Expand Down
44 changes: 0 additions & 44 deletions spec/support/file_validator_spec.rb

This file was deleted.

Loading

0 comments on commit 6489dcf

Please sign in to comment.