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

README updates, Job System Form Update: AWS SES, S3 #53

Merged
merged 17 commits into from
May 17, 2018
Merged
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ EMAIL_PORT=587
[email protected]
EMAIL_PASSWORD=chat_this_password
EMAIL_DOMAIN=mynonprofit.org

[email protected]
[email protected]

AWS_ACCESS=aws_access_key
AWS_SECRET=aws_secret_key
AWS_BUCKET=aws_bucket_name
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: ruby
rvm:
- 2.2.5
Copy link
Member

Choose a reason for hiding this comment

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

Travis still needs some config. Likely a dummy .env file that matches what it's supposed to do to setup the database for testing.

4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ gem 'uglifier', '~> 3.0'
gem 'dotenv'
gem 'google_drive'
gem 'pony'
gem 'aws-sdk-ses'
gem 'aws-sdk-s3'
gem 'mime'
#gem 'aws-sdk'

# database things
gem 'pg'
Expand Down
23 changes: 22 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ GEM
specs:
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
aws-partitions (1.40.0)
aws-sdk-core (3.9.0)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
aws-sdk-kms (1.3.0)
aws-sdk-core (~> 3)
aws-sigv4 (~> 1.0)
aws-sdk-s3 (1.8.0)
aws-sdk-core (~> 3)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0)
aws-sdk-ses (1.4.0)
aws-sdk-core (~> 3)
aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.2)
concurrent-ruby (1.0.5)
declarative (0.0.10)
declarative-option (0.1.0)
Expand Down Expand Up @@ -31,6 +47,7 @@ GEM
os (~> 0.9)
signet (~> 0.7)
httpclient (2.8.3)
jmespath (1.3.1)
jwt (2.1.0)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
Expand All @@ -43,6 +60,7 @@ GEM
mail (2.7.0)
mini_mime (>= 0.1.1)
memoist (0.16.0)
mime (0.4.4)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
Expand Down Expand Up @@ -108,8 +126,11 @@ PLATFORMS
ruby

DEPENDENCIES
aws-sdk-s3
aws-sdk-ses
dotenv
google_drive
mime
mustache-sinatra (~> 1)
pg
pony
Expand All @@ -128,4 +149,4 @@ RUBY VERSION
ruby 2.2.5p319

BUNDLED WITH
1.15.4
1.16.0
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ The Code for America NOLA fellows team is no longer actively developing this. We

The "Email to yourself" career assessment feature requires the `EMAIL_xxxx` config variables to be set. We use the [pony](https://github.com/benprew/pony) gem to send emails, please see their documentation for more details.

Some changes have included various AWS services. In addition to SMTP from the pony gem, several config variables must be set for AWS services (S3 and SES). These are new additions added below the `EMAIL_xxxx` config variables.

Setting up SES: `SENDER_EMAIL`, `OWNER_EMAIL`, `AWS_ACCESS`, `AWS_SECRET` must all be configured in `.env` for the SES to work through the job form.

Setting up S3: Configure `AWS_BUCKET` in `.env`

There is additionally an optional feature to write to google sheets using a file `client_secret.json`. An example is included in `client_secret.json.example`. If you do not wish to use this feature, do not create or use `client_secret.json`! You can find information on the [google-drive-ruby](https://github.com/gimite/google-drive-ruby) GitHub.

## Updating content
For details on updating content see other files under the `docs/` folder. Details on updating career info via spreadsheet specifically is in [docs/career_assessment_how_to.md](docs/career_assessment_how_to.md).

Expand All @@ -37,13 +45,13 @@ We are not AWS experts, so if you have recommendations to improve the following,

1. Create an IAM user (as recommended by Amazon) with appropriate permissions for deployment credentials. @antislice has no idea what specific permissions are needed for EB deployment, so we tested with admin.
2. Install the Elastic Beanstalk client.
2. Follow the Create an Application steps in [this documentation](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html#create_deploy_Ruby_eb_init).
3. Follow the Create an Application steps in [this documentation](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html#create_deploy_Ruby_eb_init).
* For steps 5/6, select `Ruby` and `Ruby 2.2 (Puma)`
* SSH login is optional, but convenient
3. At this point, you'll want to set up the DB. We created an integrated Postgres database instance (v. 9.5.2) as described in [here](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.managing.db.html).
4. Walk through [Create an Environment](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html#create_deploy_Ruby_eb_env)
5. ‼️ At this point, stop and check on the instance type. You may need to configure a VPC.
6. Try deploying: `eb deploy`
4. At this point, you'll want to set up the DB. We created an integrated Postgres database instance (v. 9.5.2) as described in [here](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.managing.db.html).
5. Walk through [Create an Environment](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html#create_deploy_Ruby_eb_env)
6. ‼️ At this point, stop and check on the instance type. You may need to configure a VPC.
7. Try deploying: `eb deploy`

Configuring the "email to yourself" feature requires extra configuration on EB.

Expand Down
3 changes: 3 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ namespace :db do

desc 'Migrate & seed DB all in one'
task :setup => [:migrate, :seed]

desc 'Default task: setup'
task :default => [:setup]
end
116 changes: 96 additions & 20 deletions app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
require 'mustache'
require 'dotenv'
require 'pony'
require './emailprovider.rb'
require 'aws-sdk-s3'
require 'json'

module WorkForwardNola
# WFN app
class App < Sinatra::Base
attr_reader :emailer

Dotenv.load

register Sinatra::SequelExtension
Expand Down Expand Up @@ -56,11 +61,11 @@ def authorized?
require './models/contact'
require './models/oppcenter'
end

if File.exist?('./client_secret.json')
def worksheet
@session ||= GoogleDrive::Session.from_service_account_key("client_secret.json")
@spreadsheet ||= @session.spreadsheet_by_title("contact")
@session ||= GoogleDrive::Session.from_service_account_key('client_secret.json')
@spreadsheet ||= @session.spreadsheet_by_title('contact')
@worksheet ||= @spreadsheet.worksheets.first
end
end
Expand Down Expand Up @@ -113,10 +118,9 @@ def worksheet
@title = 'Job System'
mustache :jobsystem
end




post '/contact' do
@resume_name = !params[:resume].nil? ? params[:resume][:filename] : nil
new_form = Contact.create(
first_name: params['first_name'],
last_name: params['last_name'],
Expand All @@ -142,25 +146,35 @@ def worksheet
new_form.save

if File.exist?('./client_secret.json')
new_row = [
params['first_name'], params['last_name'], params['best_way'],
params['email_submission'], params['phone_submission'],
params['text_submission'], params['referral'],
params['neighborhood'], params['young_adult'],
params['veteran'], params['no_transportation'],
params['homeless'], params['no_drivers_license'],
params['no_state_id'], params['disabled'], params['childcare'],
params['criminal'], params['previously_incarcerated'],
params['using_drugs'], params['none'], params['resume']
]
new_row = [params['first_name'], params['last_name'], params['best_way'],
params['email_submission'], params['phone_submission'],
params['text_submission'], params['referral'],
params['neighborhood'], params['young_adult'],
params['veteran'], params['no_transportation'],
params['homeless'], params['no_drivers_license'],
params['no_state_id'], params['disabled'], params['childcare'],
params['criminal'], params['previously_incarcerated'],
params['using_drugs'], params['none'], "#{@resume_name}"]
begin
worksheet.insert_rows(worksheet.num_rows + 1, [new_row])
worksheet.save
end
end
redirect to('/')

if @resume_name
resume = params[:resume][:tempfile]
File.open(@resume_name, 'wb') do |f|
f.write(resume.read)
s3 = Aws::S3::Client.new(access_key_id: ENV['AWS_ACCESS'],
secret_access_key: ENV['AWS_SECRET'],
region: 'us-east-1')
s3.put_object(bucket: ENV['AWS_BUCKET'], key: @resume_name, body: resume)
end
end

send_job_email(params, @resume_name, resume)
redirect to '/'
end


get '/opportunity-center-info' do
@title = 'Opportunity Center Information'
Expand Down Expand Up @@ -202,7 +216,7 @@ def worksheet
@title = 'Manage Content'
mustache :manage
end

post '/manage/update_opp_centers' do
protected!
puts params
Expand All @@ -214,5 +228,67 @@ def worksheet
end
redirect to('/manage')
end

private

def send_job_email(params, resume_name = nil, resume = nil)
# Specify a configuration set. To use a configuration
# set, uncomment the next line and send it to the proper method
# configsetname = "ConfigSet"
subject = 'New Submission: Opportunity Center Sign Up'
attachment = nil
attachment_name = resume_name
attachment = resume.path unless resume.nil?
htmlbody =
"<strong>
Thank you for registering in the New Orleans job system.
</strong>
<p>We are evaluating which opportunity center can best meet your
needs or barriers.
You'll get a reply by email of who to contact.
If you do not have email, someone will call you.</p>
<br>Here are your submissions: </br>
<br>First Name: #{params['first_name']}</br>
<br>Last Name: #{params['last_name']}</br>
<br>Best way to contact: #{params['best_way']}</br>
<br>Email: #{params['email_submission']}</br>
<br>Phone: #{params['phone_submission']}</br>
<br>Text: #{params['text_submission']}</br>
<br>Referred by: #{params['referral']}</br>
<br>Which neighborhood: #{params['neighborhood']}</br>
<br>Are you a young adult? #{params['young_adult']}</br>
<br>Are you a veteran? #{params['veteran']}</br>
<br>Do you have little access to transportation?
#{params['no_transportation']}</br>
<br>Are you homeless or staying with someone temporarily?
#{params['homeless']}</br>
<br>I dont have a drivers license. #{params['no_drivers_license']}</br>
<br>I dont have a state-issued I.D. #{params['no_state_id']}</br>
<br>I am disabled. #{params['disabled']}</br>
<br>I need childcare. #{params['childcare']}</br>
<br>I have an open criminal charge. #{params['criminal']}</br>
<br>I have been previously incarcerated.
#{params['previously_incarcerated']}</br>
<br>I am using drugs and want to get help. #{params['using_drugs']}</br>
<br>None of the above. #{params['none']}</br>"

# The email body for recipients with non-HTML email clients.
textbody = "Thank you for registering in the New Orleans job system.
We are evaluating which opportunity center can best meet your
needs or barriers. You'll get a reply by email of who to
contact. If you do not have email, someone will call you."
emailer = EmailProvider.emailer
owner = EmailProvider.owner
sender = EmailProvider.sender
# puts self.emailer.inspect
recipients = []
recipients.push owner
recipients.push params['email_submission'] if params['email_submission'] != ''
recipients.push params['job1'] if params['job1']
recipients.push params['goodwill'] if params['goodwill']
recipients.push params['tca'] if params['tca']
emailer.send_email(recipients, sender, subject, textbody, htmlbody,
attachment_name, attachment)
end
end
end
79 changes: 79 additions & 0 deletions awsemailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require 'aws-sdk-ses' # v2: require 'aws-sdk'
require './emailer.rb'
require 'dotenv'
require 'mime'

module WorkForwardNola
# Class for sending out emails through AWS
class AwsEmailer < Emailer
Dotenv.load

def initialize(access, secret)
Aws.config.update(credentials: Aws::Credentials.new(access, secret), region: 'us-east-1')
@ses = Aws::SES::Client.new
rescue Aws::SES::Errors::ServiceError => error
throw Aws::SES::Errors::ServiceError.new("Error configuring AWS SES client. Error message: #{error}")
end

def send_email(recipients, sender, subject, text_body, html_body,
attachment_name = nil, attachment_file = nil)
recipients = cc if recipients.nil?
msg_mixed = MIME::Multipart::Mixed.new
unless attachment_file.nil?
attachment = MIME::Application.new(Base64.encode64(open(attachment_file, 'rb').read))
attachment.transfer_encoding = 'base64'
attachment.disposition = 'attachment'
msg_mixed.attach(attachment, 'filename' => attachment_name)
end
msg_body = MIME::Multipart::Alternative.new
msg_body.add(MIME::Text.new(text_body, 'plain'))
msg_body.add(MIME::Text.new(html_body, 'html'))
msg_mixed.add(msg_body)
msg = MIME::Mail.new(msg_mixed)
msg.subject = subject
# Try to send the email.
begin
# Verify the emails, this will add them to the verified emails
# if using sandboxes.

# check_emails sender
# check_emails recipient
# check_emails cc
# check_emails bcc
# check_emails reply_to

# Provide the contents of the email.

resp = @ses.send_raw_email(
source: sender,
destinations: recipients,
raw_message: {
data: msg.to_s
}
)

puts "Email sent to #{recipients.inspect}: #{resp.inspect}"
rescue Aws::SES::Errors::ServiceError => error
# If something goes wrong, display an error message.
throw error
end
end

private

# Used to verify emails for use in AWS sandbox environment
def check_emails(emails)
return if emails.nil?
if emails.respond_to? :each
emails.each { |e| @ses.verify_email_identity email_address: e }
else
@ses.verify_email_identity email_address: emails
end
end

def as_email_array(emails)
return emails if emails.respond_to? :each
[emails]
end
end
end
4 changes: 2 additions & 2 deletions docs/career_assessment_how_to.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Matching an image with a trait can happen three ways:
The career info is managed via a google spreadsheet that is manually synced with the site's database. Updating existing career info or adding a new career doesn't involve any code updates, but adding a new trait or changing the assessment does.

### The google spreadsheet
The current production version of this site uses [this spreadsheet](https://docs.google.com/spreadsheets/d/1lMeZZ_vu-xIVKLgo7obKlF0X4iJ3oHkLKl8uIrPsSt8) (current owners CfA NOLA Fellows).
The current production version of this site uses [this spreadsheet](https://docs.google.com/spreadsheets/d/1cXQCnOpP_IcJS_N8VOPD1z9prYVUPGhtb7rlgvGnJ8E/pubhtml) (current owners Loyola University New Orleans Department of Computer Science).

*When editing the content DO NOT change the column headers or the content will not update properly.* The columns `description`, ` foundational_skills`, and `training` accept HTML formatting. It's recommended to double check your HTML with a tool like https://jsfiddle.net/.

Expand All @@ -29,7 +29,7 @@ After filling in the info, go to 'File' > 'Publish to Web' and publish it as a w
To update the information about a career: edit your spreadsheet, then go to the ["manage" page](http://workforwardnola.com/manage) & enter the username and password. Enter the PUBLIC link to the spreadsheet (it ends in `/pubhtml`) (Like https://docs.google.com/spreadsheets/d/1lMeZZ_vu-xIVKLgo7obKlF0X4iJ3oHkLKl8uIrPsSt8/pubhtml), wait for the link to be validated, then click "Update content". If an error message appears, double check the content format. When the content updates successfully, there will be a message indicating how many careers and traits were updated. You're done!

### Adding new career
Add a new row to the google spreadsheet. The columns `name`, `sector`, and `experienced_wage` are required. The `traits` column is required to have the new career be included in assessment results. After filling in the new row in the spreadsheet, follow the instructions for the 'Updating Career info' step above to load and publish the new information.
Add a new row to the google spreadsheet. The columns `name`, `sector`, and `experienced_range` are required. The `traits` column is required to have the new career be included in assessment results. After filling in the new row in the spreadsheet, follow the instructions for the 'Updating Career info' step above to load and publish the new information.

## Adding new trait
Adding a new trait to the assessment cannot be done without additional coding work.
Expand Down
Loading