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
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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 gcompat python2"
ARG PACKAGES="vim openssl-dev postgresql-dev build-base curl nodejs yarn less tzdata git postgresql-client bash screen gcompat python2 imagemagick"

RUN apk update \
&& apk upgrade \
&& apk add --update --no-cache $PACKAGES
&& apk upgrade \
&& apk add --update --no-cache $PACKAGES

RUN gem install bundler:2.1.4

Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ gem 'bcrypt', '~> 3.1.7'
gem 'bootsnap', '>= 1.4.2', require: false

gem 'active_model_serializers'
gem 'file_validators'
gem 'js-routes'
gem 'kaminari'
gem 'mini_magick'
gem 'ransack'
gem 'responders'
gem 'sidekiq', '~> 6.5.0'
Expand All @@ -38,6 +40,7 @@ gem 'sidekiq-unique-jobs'
gem 'simple_form'
gem 'slim-rails'
gem 'state_machines-activerecord'
gem 'virtus'
gem 'webpacker-react'

group :development, :test do
Expand Down
23 changes: 23 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ GEM
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.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.18)
bindex (0.8.1)
bootsnap (1.15.0)
Expand All @@ -85,9 +89,13 @@ GEM
case_transform (0.2)
activesupport
childprocess (4.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
crass (1.0.6)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
docile (1.4.0)
erubi (1.11.0)
factory_bot (6.2.1)
Expand All @@ -96,10 +104,14 @@ GEM
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
ffi (1.15.5)
file_validators (3.0.0)
activemodel (>= 3.2)
mime-types (>= 1.0)
globalid (1.0.0)
activesupport (>= 5.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
Expand Down Expand Up @@ -139,6 +151,10 @@ GEM
marcel (1.0.2)
matrix (0.4.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.12.0)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.3)
Expand Down Expand Up @@ -284,6 +300,10 @@ GEM
tzinfo (1.2.10)
thread_safe (~> 0.1)
unicode-display_width (2.3.0)
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -317,12 +337,14 @@ DEPENDENCIES
byebug
capybara (>= 2.15)
factory_bot_rails
file_validators
jbuilder (~> 2.7)
js-routes
kaminari
letter_opener
letter_opener_web
listen (~> 3.2)
mini_magick
pg (>= 0.18, < 2.0)
puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.3)
Expand All @@ -339,6 +361,7 @@ DEPENDENCIES
simplecov
slim-rails
state_machines-activerecord
virtus
web-console (>= 3.3.0)
webdrivers
webpacker (~> 4.0)
Expand Down
34 changes: 33 additions & 1 deletion app/controllers/api/v1/tasks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def show

def index
tasks = Task.all
.with_attached_image
.ransack(ransack_params)
.result
.page(page)
Expand Down Expand Up @@ -37,9 +38,40 @@ def destroy
respond_with(task)
end

def attach_image
task = Task.find(params[:id])
attachment = params[:attachment]
attachment_params = {
image: attachment[:image],
crop_x: attachment[:crop_x],
crop_y: attachment[:crop_y],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

небезопасно использовать параметры напрямую. нужны strong params

crop_width: attachment[:crop_width],
crop_height: attachment[:crop_height]
}
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 task_params
params.require(:task).permit(:id, :name, :description, :author_id, :assignee_id, :state_event, :expired_at)
params.require(:task).permit(:id, :name, :description, :author_id, :assignee_id, :state_event, :expired_at,
:attachment)
end
end
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
1 change: 1 addition & 0 deletions app/models/task.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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
Expand Down
6 changes: 5 additions & 1 deletion app/serializers/task_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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

Expand All @@ -12,4 +12,8 @@ def transitions
}
end
end

def image_url
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFYI если добавляется кастомный атрибут, лучше его объявлять в виде

attribute do 
...
end

object.image.attached? ? AttachmentsService.file_url(object.image) : nil
end
end
7 changes: 7 additions & 0 deletions app/services/attachment_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
2 changes: 1 addition & 1 deletion config/credentials.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
KKKUi8kqBT8qSJ39419AA3r2cQyQGUZLoJL83tOmIjGs5rfE+eNtghuEkO1s9sJ7wzW3mjdydHuRmcfE1Ic+1eidQZ4H/dwfb3EPJ6Ud8nO4Tn1EqAGrkCcZWEgTPRjIlVs+kmhq4VQcwkVHN0bfGXDoTKP5qCiaqdgjOPfnklaJ2jxeUfq1OoXQbwGgrVYB45b+r5RWOOJT+QwG2ddc1cJ7BzcFgipe9/bmVYdw84o4M5EQPr1FBc3eeqjPkMm0/f79R7iSg8F/ZlwUEBBGInrpGVJRffNJ+JDVDKrEXldf4+Lzhd2Hicvy/8cm6IGBEH/j96ksFw+Nv/hYSZ7bfTp046Pu2eU/Z4d0cz8VPGTC756NYecZaaesyHvLnbhBYWepDW8zASbdRMqcrv5zcoQ/Xhl07piPm1WK--eY0CL0IB0ObUsd/B--r2bkypAoIvjzNdkCloiTkQ==
B8MkR7d7vVYwQr6sXYNpq5WLcGREgNVIWI2EjR7Sz9xV6Q04s+v/GQZiKfWgOQuFFOuInVQi94I2c/NV35s+9LIbQqt1gEaeZS8JaP6//RrKf2FJXzIs2pxuYEU/G2nkPTkQnHWaBz1ZJPJImq209bCa7wu57p6mrY+zk4wjn/C89eKiGgtFaw1QAYbWSJe6cNHCQEeAsNZR9whIi73eaPdDAIeLU4FFmGo8zsS087H462xPUm+UWCYMY62oWDEAhN9h2yoksj6DtVeQAZwbyclxK9Fo42wFR0V/b1PXaL++Farjr4Pwhhdf7pkXxLAWYmYCiWbQi81catnbYtXu4oLjml++JjPGajvov/k5ceqnq3muHSTk68IALIKhiwCRWs6mugfCU7m+sRaEMhPRwdLPT7ZAQZFzAeaO--3Xv+VSAo81kv6uFz--Gr4WW6P2xyb2D/WnxKmlyw==
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

креды не хранят в открытом доступе

7 changes: 6 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

namespace :api do
namespace :v1, defaults: { format: 'json' } do
resources :tasks, only: %i[index show create update destroy]
resources :tasks, only: %i[index show create update destroy] do
member do
put :attach_image
delete :remove_image
end
end
resources :users, only: %i[index show]
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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.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
end
end
24 changes: 23 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,32 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2023_01_05_072548) do
ActiveRecord::Schema.define(version: 2023_01_12_070536) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end

create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

create_table "tasks", force: :cascade do |t|
t.string "name"
t.text "description"
Expand All @@ -38,4 +59,5 @@
t.string "reset_password_token_digest"
end

add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
end
55 changes: 55 additions & 0 deletions test/controllers/api/v1/tasks_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,59 @@ class Api::V1::TasksControllerTest < ActionController::TestCase

assert !Task.where(id: task.id).exists?
end

test 'should put attach_image' do
author = create(:user)
task = create(:task, author: author)

image = file_fixture('image.jpg')
attachment_params = {
image: fixture_file_upload(image, 'image/jpeg'),
crop_x: 190,
crop_y: 100,
crop_width: 300,
crop_height: 300,
}

put :attach_image, params: { id: task.id, attachment: attachment_params, format: :json }
assert_response :success

task.reload
assert(task.image.attached?)

put :remove_image, params: { id: task.id, format: :json }
assert_response :success
end

test 'should put remove_image' do
author = create(:user)
task = create(:task, author: author)

image = file_fixture('image.jpg')
attachment_params = {
image: fixture_file_upload(image, 'image/jpeg'),
crop_x: 190,
crop_y: 100,
crop_width: 300,
crop_height: 300,
}

put :attach_image, params: { id: task.id, attachment: attachment_params, format: :json }

put :remove_image, params: { id: task.id, format: :json }
assert_response :success

task.reload
refute(task.image.attached?)
end

def after_teardown
super

remove_uploaded_files
end

def remove_uploaded_files
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
end
Binary file added test/fixtures/files/image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.