Skip to content

Commit

Permalink
image uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrea Rossi committed Sep 2, 2016
1 parent 75eeb26 commit c87bca8
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 15 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ gem 'rails', '4.2.6'

# DB
gem 'pg', '~> 0.18.4'
gem 'paperclip', '~> 5.1.0'

# Frontend
gem 'kaminari', '~> 0.17.0'
Expand Down
12 changes: 12 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ GEM
capistrano
sidekiq (>= 3.4)
choice (0.2.0)
climate_control (0.0.3)
activesupport (>= 3.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
codeclimate-test-reporter (0.6.0)
simplecov (>= 0.7.1, < 1.0.0)
concurrent-ruby (1.0.2)
Expand Down Expand Up @@ -139,6 +143,7 @@ GEM
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.2)
mini_portile2 (2.1.0)
minitest (5.9.0)
mocha (1.1.0)
Expand All @@ -150,6 +155,12 @@ GEM
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
orm_adapter (0.5.0)
paperclip (5.1.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
cocaine (~> 0.5.5)
mime-types
mimemagic (~> 0.3.0)
pg (0.18.4)
pkg-config (1.1.7)
rack (1.6.4)
Expand Down Expand Up @@ -267,6 +278,7 @@ DEPENDENCIES
kaminari (~> 0.17.0)
letter_opener
mocha (~> 1.1.0)
paperclip (~> 5.1.0)
pg (~> 0.18.4)
rails (= 4.2.6)
rails-erd (~> 1.4.7)
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
$(document).ready(function(){
var app = require("app");
app.start();
});
});
29 changes: 22 additions & 7 deletions app/assets/javascripts/components/questions/file_question.cjsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
React = require("react")
Question = require("components/question")
QuestionnaireStore = require("stores/questionnaire_store")
ImageStore = require("stores/image_store")

module.exports = class FileQuestion extends React.Component
render: ->
Expand All @@ -25,11 +26,18 @@ module.exports = class FileQuestion extends React.Component
</div>

renderFiles: =>
@props.answer?.selected?.map (file) =>
<p key={file.name + file.lastModifiedDate} className="file">
{@renderImage(file)} {file.name}
<small style={{color: "red"}} onClick={@deleteFile.bind(@, file)}>x</small>
</p>
@props.answer?.selected?.map((file) =>
if file.url
<p key={file.id} className="file">
<img height=48 src="#{file.url}?size=thumb"></img>
<small style={{color: "red"}} onClick={@deleteFile.bind(@, file)}>x</small>
</p>
else
<p key={file.file.name + file.file.lastModifiedDate} className="file">
{@renderImage(file.file)} {file.file.name}
<small style={{color: "red"}} onClick={@deleteFile.bind(@, file)}>x</small>
</p>
)

renderImage: (file) =>
if(FileReader)
Expand All @@ -40,18 +48,25 @@ module.exports = class FileQuestion extends React.Component

loadThumbnail: (file, imageRef) ->
reader = new FileReader()

reader.onload = ((event) => @refs[imageRef].src = event.target.result)

try
reader.readAsDataURL(file)
catch
null

addFile: (e) =>
QuestionnaireStore.addAnswer(@props.data.id, e.target.files[0])
QuestionnaireStore.addAnswer(@props.data.id, {file: e.target.files[0]})
QuestionnaireStore.saveOrUpdateReport()

deleteFile: (file) =>
ImageStore.deleteImage(file)
QuestionnaireStore.removeAnswer(@props.data.id, file)
QuestionnaireStore.saveOrUpdateReport()

resetFiles: =>
@props.answer?.selected?.map((file) =>
ImageStore.deleteImage(file)
)
QuestionnaireStore.selectAnswer(@props.data.id, [])
QuestionnaireStore.saveOrUpdateReport()
62 changes: 62 additions & 0 deletions app/assets/javascripts/stores/image_store.cjsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
_ = require("underscore")
async = require("async")
{EventEmitter} = require("events")
require("whatwg-fetch")

class ImageStore extends EventEmitter
storeImages: (report, done) =>
@storeImagesFor("live", report, (updatedReport) =>
@storeImagesFor("dead", updatedReport, done)
)

deleteImage: (file) ->
return unless file.url

token = document.getElementsByName("csrf-token")[0].content
fetch(file.url, {
method: "DELETE",
credentials: 'include',
headers: {'X-CSRF-Token': token},
}).then((response) ->
console.log("deleted, #{response.status}")
)


storeImagesFor: (type, report, done) ->
async.map((report.answers?[type] || []), ((ape, next) =>
async.map((ape["photo_#{type}"]?.selected || []), @uploadPhotoFor(report), (err, uploadedPhotos) ->
ape["photo_#{type}"].selected = uploadedPhotos if ape["photo_#{type}"]?.selected
next(null, ape)
)
), (err, updatedApes) ->
report.answers["type"] = updatedApes
done(report)
)

uploadPhotoFor: (report) ->
(photo, done) =>
return done(null, photo) if photo.id

@uploadFile(photo.file, report.id, (response) ->
console.log("called for #{response.headers.get("Location")}")
photo.url = response.headers.get("Location")
photo.id = parseInt(response.headers.get("Image-Id"))

done(null, photo)
)

uploadFile: (file, reportId, done) ->
form = new FormData()
form.append('image', file)

token = document.getElementsByName("csrf-token")[0].content
fetch("/reports/#{reportId}/images", {
method: "POST",
headers: {
'X-CSRF-Token': token
},
body: form,
credentials: 'include',
}).then(done)

module.exports = new ImageStore()
22 changes: 17 additions & 5 deletions app/assets/javascripts/stores/questionnaire_store.cjsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
_ = require("underscore")
async = require("async")
{EventEmitter} = require("events")
NavigationStore = require("stores/navigation_store")
ImageStore = require("stores/image_store")
require("whatwg-fetch")

class QuestionnaireStore extends EventEmitter
Expand All @@ -18,7 +21,8 @@ class QuestionnaireStore extends EventEmitter
id: null,
answers: {},
state: "in_progress",
genera: {}
genera: {},
images: {}
}

constructor: ->
Expand Down Expand Up @@ -168,6 +172,7 @@ class QuestionnaireStore extends EventEmitter
report.answers[key].dna_confirmation = false
@emit(CHANGE_EVENT)


addChangeListener: (callback) => @on(CHANGE_EVENT, callback)
addVisibilityListener: (callback) => @on(VISIBILITY_EVENT, callback)

Expand All @@ -177,14 +182,22 @@ class QuestionnaireStore extends EventEmitter
if report.id?
@reportRequest("/reports/#{report.id}", "PUT", (response) =>
@setNotification("success", "Report updated")
callback?(response.headers.get('Location'))
ImageStore.storeImages(report, (newReport) =>
report = newReport
callback?(response.headers.get('Location'))
)
)
else
@reportRequest("/reports", "POST", (response) =>
@setNotification("success", "Report saved")
response.json().then((json) -> report.id = json.id)

callback?(response.headers.get('Location'))
response.json().then((json) =>
report.id = json.id
ImageStore.storeImages(report, (newReport) =>
report = newReport
callback?(response.headers.get('Location'))
)
)
)

storeGenera: ->
Expand All @@ -204,7 +217,6 @@ class QuestionnaireStore extends EventEmitter

report.genera = genera


reportRequest: (path, method, callback) ->
token = document.getElementsByName("csrf-token")[0].content
fetch(path, {
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class ApplicationController < ActionController::Base
class PageNotFoundError < StandardError; end
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
Expand All @@ -8,13 +9,21 @@ def after_sign_in_path_for(resource)
reports_path
end

def raise_404
raise PageNotFoundError
end

rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { head :forbidden }
format.html { redirect_to :back, alert: exception.message }
end
end

rescue_from PageNotFoundError do |exception|
head 404
end

protected
def configure_permitted_parameters
registration_keys = [:first_name, :last_name, :email, :password, :password_confirmation, :role_id]
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/images_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class ImagesController < ApplicationController
def create
if image = Image.create(report_id: params[:report_id], file: params[:image])
head 201, {
location: report_image_path(report_id: image.report_id, id: image.id),
image_id: image.id
}
else
head 422
end
end

def show
image = Image.where(report_id: params[:report_id], id: params[:id]).first or raise_404
if params[:size]
redirect_to image.file.url(params[:size])
else
redirect_to image.file.url
end
end

def destroy
image = Image.where(report_id: params[:report_id], id: params[:id]).first or raise_404

if image.destroy
head 200
else
head 422
end
end
end
18 changes: 18 additions & 0 deletions app/models/image.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# == Schema Information
#
# Table name: images
#
# id :integer not null, primary key
# file_file_name :string
# file_content_type :string
# file_file_size :integer
# file_updated_at :datetime
# report_id :integer
#

class Image < ActiveRecord::Base
has_attached_file :file, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/

belongs_to :report
end
1 change: 1 addition & 0 deletions app/models/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Report < ActiveRecord::Base

belongs_to :user
has_many :validations
has_many :images

def user_name
"#{user&.first_name} #{user&.last_name}".strip
Expand Down
5 changes: 4 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
end

resources :agencies
resources :reports
resources :reports do
resources :images, only: [:create, :show, :destroy]
end

resources :validations, only: [:new, :create]

namespace :admin do
Expand Down
8 changes: 8 additions & 0 deletions db/migrate/20160902095857_create_images_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class CreateImagesTable < ActiveRecord::Migration
def change
create_table :images do |t|
t.attachment :file
t.references :report
end
end
end
10 changes: 9 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20160831165044) do
ActiveRecord::Schema.define(version: 20160902095857) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -40,6 +40,14 @@
t.integer "user_id", null: false
end

create_table "images", force: :cascade do |t|
t.string "file_file_name"
t.string "file_content_type"
t.integer "file_file_size"
t.datetime "file_updated_at"
t.integer "report_id"
end

create_table "reports", force: :cascade do |t|
t.jsonb "data"
t.datetime "created_at", null: false
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "grasp",
"dependencies": {
"async": "^2.0.1",
"browserify": "~10.2.4",
"browserify-incremental": "^3.0.1",
"coffee-reactify": "^5.0.0",
Expand Down

0 comments on commit c87bca8

Please sign in to comment.