AWS | File upload | Ember/Rails app |CSV

Hello👋 Everyone,

The next few blog posts will be more of a brief overview of features I’ve built. If you have any questions just leave me a comment below. This was a very challenging feature to engineer, but I really enjoyed the learning that it provided.

In this post, you’ll see how I built out a CSV File Upload feature inside my existing Ember/Rails app. This blog post assumes you have previous experience in both Emberjs & Rails. This post will not show you how to set up an ember/rails app. It also assumes that your app is built using the JSON API Gem in rails. So moving forward everything I reveal will be under the umbrella of the JSON API Specs. For more information  on JSON API RESOURCES http://jsonapi-resources.com

Also, if you see something that I left out, please leave a comment and I can update this post.

In Emberjs

Ember CLI Version 2.16.2 | Ember Data 2.16.2

Setup/Installs

• npm install –save ember-cli-uploader

• Ember generate ember-cli-uploader

• ember g component s3-upload

 

Build Out Component

app/components/csv-upload.js

import EmberUploader from ’ember-uploader’;
import config from ‘../config/environment’;
export default EmberUploader.FileField.extend({
onComplete: ‘onComplete’,
onProgress: ‘onProgress’,
didError: ‘didError’,
url: config.DS.host + “/post/sign/csv”,
// video tutorial for connecting s3
filesDidChange: function(filesDidChange) {
let uploadUrl = this.get(‘url’),
_this = this,
uploader = EmberUploader.S3Uploader.create({
url: uploadUrl,
});
uploader.on(‘didUpload’, function(response) {
let res =$(response),
fullUrl = decodeURIComponent(res.find(‘Location’)[0].textContent),
key = decodeURIComponent(res.find(‘Key’)[0].textContent)
_this.sendAction(‘onComplete’, {fullUrl: fullUrl, key: key});
});
uploader.on(‘progress’, function(e) {
_this.sendAction(‘onProgress’, e.percent);
})
uploader.on(‘didError’, function(e) {
_this.sendAction(‘didError’, e.responseJSON.errors[0].detail);
})
if (!Ember.isEmpty(filesDidChange))
uploader.upload(filesDidChange[0]);
}.observes(‘filesDidChange’)
});

 

configure upload

/controller.js

import Ember from ’ember’;
export default Ember.Controller.extend({
csvUploadPercentage: null,
errorMessage: null,
actions: {
csvUploadComplete(details) {
let extension = details.key.split(‘.’)[1];
let profile_pic = details.key;
let newData = {
profilePicExtension: extension,
profilePicUrl: profile_pic,
};
let csv_upload = this.store.createRecord(‘DATA_MODEL_GOES_HERE‘, newData);
csv_upload.save();
this.transitionToRoute(‘ROUTE.TO.CERTAIN.PAGE‘);
},
csvUploadProgress(e) {
Ember.run.once(this, function() {
this.set(“csvUploadPercentage”, Math.round(e));
});
},
errorMessage(e) {
alert(e);
this.transitionToRoute(‘profile.details’, this.model.id);
}
}
});

/template.hbs

Upload Enterprise Records

{{csv-uploadid=”new-pic”onComplete=”csvUploadComplete”       onProgress=”csvUploadProgress” didError=”errorMessage”}}
{{#if csvUploadPercentage}}

{{csvUploadPercentage}}%
</div>
{{/if}}
</div>
</div>

Style it however you want.

Also, make sure you have a data model created inside the /models directory.

Connect config/environment.js file

/* eslint-env node */
‘use strict’;
module.exports = function(environment) {
let ENV = {
DS: {
// localhost
},
modulePrefix: ’ember-folder’,
environment,
rootURL: ‘/’,
locationType: ‘auto’,
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. ‘with-controller’: true
},
EXTEND_PROTOTYPES: {
// Prevent Ember Data from overriding Date.parse.
Date: false
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
},
’ember-simple-auth’: {
authorizer: ‘authorization:token’
},
’ember-simple-auth-token’: {
identificationField: ’email’,
passwordField: ‘password’,
headers: {
‘Content-Type’: ‘application/vnd.api+json’,
‘Accept’: ‘application./vnd.api+json’
},
refreshAccessTokens: false,
}
};
if (environment ===’development’) {
// ENV.APP.LOG_RESOLVER = true;
// ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
// ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment ===’test’) {
// Testem prefers this…
ENV.locationType = ‘none’;
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = ‘#ember-testing’;
}
if (environment ===’production’) {
// here you can enable a production-specific feature
}
ENV[’ember-simple-auth-token’].serverTokenEndpoint = `${ENV.DS.host}/session`;
return ENV;
};

 

In Rails

Rails Version 5.1.6 | Ruby Version 2.5.3

Setup/Installs

• add gem ‘aws-sdk’, ‘~> 0.1’

Add S3 Variables to env file

/.env-example

AWS_ACCESS_KEY_ID= ‘ADD_YOUR_ACCESS_KEY_HERE’
AWS_SECRET_ACCESS_KEY= ‘ADD_YOUR_SECRET_KEY_HERE’
AWS_REGION= ‘ADD_YOUR_REGION’
AWS_BUCKET= ‘ADD_YOUR_BUCKET’

Configure backend

/controllers/

require ‘aws-sdk’
require’csv’
class NAMEOFYOURController < ApplicationController
def csv
# Step 1 – Connect to s3
s3 = Aws::S3::Client.new
# Step 2 – copy file into backend
File.open(‘FILE_NAME_GOES_HERE.csv’, ‘wb’) do |file|
reap = s3.get_object({ bucket:ENV[‘AWS_BUCKET’], key:’BUCKET_DIRECTORY/FILE_NAME_GOES_HERE.csv’ }, target: file)
end
# Step 4 – upload csv file into db
CSV.foreach(‘FILE_NAME_GOES_HERE.csv’, :headers => true) do |row|
DATABASE_TABLE_NAME_GOES_HERE.create(row.to_hash)
end
# Step 5 – Delete existing csv file from backend
File.delete(‘FILE_NAME_GOES_HERE.csv’)
# Step 6 – Send an email to development notifiying them of the file uploaded
mail = FILE_NAME_GOES_HEREMailer.Notify_developers(params)
mail.deliver_now
render json: {“data”: {“status”: “Finished!”}}
end
def sign
size = params[:size].to_i
max_size = size.between?(0,2000000)
extension = params[:type].split(“/”).last
valid_extensions = [‘csv’, ‘vnd.ms-excel’]
match = extension.end_with?(valid_extensions[0], valid_extensions[1])
@expires = 10.hours.from_now.utc if match && max_size
render json: signature if match && max_size
render :status => :not_acceptable, json: {
errors: [
{
status:406,
title:”Not Acceptable”,
detail:”Warning! Image needs to be a valid ‘csv’ format.”
}
]
} if !match
render :status => :not_acceptable, json: {
errors: [
{
status:406,
title:”Not Acceptable”,
detail:”File size is more than 2MB.”
}
]
} if !max_size
end
def signature
client = Aws::S3::Client.new
resource = Aws::S3::Resource.new(client: client)
bucket = resource.bucket ENV[‘AWS_BUCKET’]
upload_path = ‘BUCKET_DIRECTORY/’ + params[:name]
presigned_post = bucket.presigned_post(
acl:’public-read’,
expires: @expires,
key: upload_path,
policy: policy,
success_action_status:’201′,
content_disposition:’attachment’, # indicate this should be downloaded no opened in the browser
content_type: params[:type],
cache_control:’max-age=630720000, public’
)
fields = presigned_post.fields
fields[‘bucket’] = ENV[‘AWS_BUCKET’]
fields
end
def policy(options = {})
Base64.strict_encode64(
{
expiration: @expires,
conditions: [
{ bucket: ENV[‘AWS_BUCKET’] },
{ acl: ‘public-read’ },
{ expires: @expires },
{ success_action_status: ‘201’ },
[ ‘starts-with’, ‘$key’, ” ],
[ ‘starts-with’, ‘$Content-Type’, ” ],
[ ‘starts-with’, ‘$Cache-Control’, ” ],
[ ‘content-length-range’, 0, 524288000 ]
]
}.to_json
)
end
end

 

config/routes.rb

# CSV Upload
post “/MODEL_NAME_GOES_HERE“, to: “CONTROLLER_NAME_GOES_HERE#csv”
get “/post/sign/csv”, to: “CONTROLLER_NAME_GOES_HERE#sign”

 

 

 

Credits:

Rails Resources
S3 Resources
Thanks again for reading,
Shaun Willis

Leave a comment