Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM ruby:2.7.1-alpine

ARG RAILS_ROOT=/task_manager
ARG PACKAGES="vim openssl-dev postgresql-dev build-base curl nodejs yarn less tzdata git postgresql-client bash screen"
ARG PACKAGES="vim openssl-dev postgresql-dev build-base curl nodejs yarn less tzdata git postgresql-client bash screen imagemagick"

RUN apk update \
&& apk upgrade \
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ gem 'sidekiq'
gem 'sidekiq-failures'
gem 'sidekiq-throttled'
gem 'sidekiq-unique-jobs'
gem 'mini_magick'
gem 'virtus'
gem 'file_validators'
21 changes: 21 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ GEM
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.9.1)
Expand All @@ -101,6 +105,8 @@ GEM
case_transform (0.2)
activesupport
childprocess (4.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
coveralls (0.7.1)
Expand All @@ -110,6 +116,8 @@ GEM
term-ansicolor
thor
crass (1.0.6)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
docile (1.4.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
Expand All @@ -120,13 +128,17 @@ GEM
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
ffi (1.15.4)
file_validators (3.0.0)
activemodel (>= 3.2)
mime-types (>= 1.0)
globalid (0.5.2)
activesupport (>= 5.0)
http-accept (1.7.0)
http-cookie (1.0.4)
domain_name (~> 0.5)
i18n (1.8.11)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
jbuilder (2.11.2)
activesupport (>= 5.0.0)
js-routes (2.1.2)
Expand Down Expand Up @@ -167,6 +179,7 @@ GEM
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.1115)
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.6.1)
minitest (5.14.4)
Expand Down Expand Up @@ -316,6 +329,7 @@ GEM
term-ansicolor (1.7.1)
tins (~> 1.0)
thor (1.1.0)
thread_safe (0.3.6)
tilt (2.0.10)
tins (1.29.1)
sync
Expand All @@ -326,6 +340,10 @@ GEM
unf_ext (0.0.8)
unicode-display_width (2.1.0)
uniform_notifier (1.14.2)
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
web-console (4.1.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -361,12 +379,14 @@ DEPENDENCIES
capybara (>= 3.26)
coveralls
factory_bot_rails
file_validators
jbuilder (~> 2.7)
js-routes
kaminari
letter_opener
letter_opener_web
listen (~> 3.3)
mini_magick
newrelic_rpm
pg (~> 1.1)
puma (~> 5.0)
Expand All @@ -389,6 +409,7 @@ DEPENDENCIES
state_machines
state_machines-activerecord
tzinfo-data
virtus
web-console (>= 4.1.0)
webdrivers
webpacker (~> 5.0)
Expand Down
31 changes: 29 additions & 2 deletions app/controllers/api/v1/tasks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
class Api::V1::TasksController < Api::V1::ApplicationController
def index
tasks = Task.all.
tasks = Task.
ransack(ransack_params).
result.
page(page).
per(per_page)
per(per_page).
with_attached_image

respond_with(tasks, each_serializer: TaskSerializer, root: 'items', meta: build_meta(tasks))
end
Expand Down Expand Up @@ -45,8 +46,34 @@ def destroy
respond_with(task)
end

def attach_image
task = Task.find(params[:id])
task_attach_image_form = TaskAttachImageForm.new(attachment_params)

if task_attach_image_form.invalid?
respond_with(task_attach_image_form)
return
end

image = task_attach_image_form.processed_image
task.image.attach(image)

respond_with(task, serializer: TaskSerializer)
end

def remove_image
task = Task.find(params[:id])
task.image.purge

respond_with(task, serializer: TaskSerializer)
end

private

def attachment_params
params.require(:attachment).permit(:image, :crop_width, :crop_height, :crop_x, :crop_y)
end

def task_params
params.require(:task).permit(:name, :description, :author_id, :assignee_id, :expired_at, :state_event)
end
Expand Down
31 changes: 31 additions & 0 deletions app/forms/task_attach_image_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class TaskAttachImageForm
include ActiveModel::Validations
include Virtus.model

attribute :image, ActionDispatch::Http::UploadedFile
attribute :crop_width, Integer
attribute :crop_height, Integer
attribute :crop_x, Integer
attribute :crop_y, Integer

with_options numericality: { only_integer: true, greater_than_or_equal_to: 0 } do
validates :crop_width, if: -> { crop_width.present? }
validates :crop_height, if: -> { crop_height.present? }
validates :crop_x, if: -> { crop_x.present? }
validates :crop_y, if: -> { crop_y.present? }
end

validates :image, presence: true,
file_size: { less_than_or_equal_to: 2.megabytes },
file_content_type: { allow: ['image/jpeg', 'image/png'] }

def processed_image
ImageProcessingService.crop!(image.path, crop_width, crop_height, crop_x, crop_y) if cropping?

image
end

def cropping?
[crop_width, crop_height, crop_x, crop_y].all?(&:present?)
end
end
2 changes: 2 additions & 0 deletions app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class Task < ApplicationRecord
belongs_to :author, class_name: 'User'
belongs_to :assignee, class_name: 'User', optional: true

has_one_attached :image

validates :name, presence: true
validates :description, presence: true
validates :author, presence: true
Expand Down
6 changes: 5 additions & 1 deletion app/serializers/task_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
class TaskSerializer < ApplicationSerializer
attributes :id, :name, :description, :state, :expired_at, :transitions
attributes :id, :name, :description, :state, :expired_at, :transitions, :image_url
belongs_to :author
belongs_to :assignee

def image_url
object.image.attached? ? AttachmentsService.file_url(object.image) : nil
end

def transitions
object.state_transitions.map do |transition|
{
Expand Down
7 changes: 7 additions & 0 deletions app/services/attachments_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module AttachmentsService
class << self
def file_url(file)
Rails.application.routes.url_helpers.rails_blob_url(file)
end
end
end
8 changes: 8 additions & 0 deletions app/services/image_processing_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module ImageProcessingService
class << self
def crop!(path_to_image, crop_width, crop_height, crop_x, crop_y)
image = MiniMagick::Image.new(path_to_image)
image.crop("#{crop_width}x#{crop_height}+#{crop_x}+#{crop_y}")
end
end
end
1 change: 1 addition & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "active_support/core_ext/integer/time"

Rails.application.configure do
routes.default_url_options[:host] = 'localhost:3000'
# Settings specified here will take precedence over those in config/application.rb.

# In the development environment your application's code is reloaded any time
Expand Down
2 changes: 1 addition & 1 deletion config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
routes.default_url_options[:host] = 'localhost:3000'
# Settings specified here will take precedence over those in config/application.rb.

config.cache_classes = true

# Do not eager load code on boot. This avoids loading your whole application
Expand Down
7 changes: 6 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@

namespace :api do
namespace :v1, defaults: {format: :json} do
resources :tasks, only: [:index, :show, :create, :update, :destroy]
resources :tasks, only: [:index, :show, :create, :update, :destroy] do
member do
put 'attach_image'
put 'remove_image'
end
end
resources :users, only: [:index, :show]
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
def change
create_table :active_storage_blobs do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.string :service_name, null: false
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false

t.index [ :key ], unique: true
end

create_table :active_storage_attachments do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false
t.references :blob, null: false

t.datetime :created_at, null: false

t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end

create_table :active_storage_variant_records do |t|
t.belongs_to :blob, null: false, index: false
t.string :variation_digest, null: false

t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
32 changes: 31 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading