diff --git a/.env.example b/.env.example
index 6246b68..f54bf27 100644
--- a/.env.example
+++ b/.env.example
@@ -6,3 +6,10 @@ EMAIL_PORT=587
EMAIL_USER=stacy@mynonprofit.org
EMAIL_PASSWORD=chat_this_password
EMAIL_DOMAIN=mynonprofit.org
+
+SENDER_EMAIL=sendfrom@mynonprofit.org
+OWNER_EMAIL=owner@mynonprofit.org
+
+AWS_ACCESS=aws_access_key
+AWS_SECRET=aws_secret_key
+AWS_BUCKET=aws_bucket_name
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..881c9ba
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: ruby
+rvm:
+ - 2.2.5
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 543bc66..5725f1e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index ffbe50c..0683e7c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
@@ -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)
@@ -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)
@@ -108,8 +126,11 @@ PLATFORMS
ruby
DEPENDENCIES
+ aws-sdk-s3
+ aws-sdk-ses
dotenv
google_drive
+ mime
mustache-sinatra (~> 1)
pg
pony
@@ -128,4 +149,4 @@ RUBY VERSION
ruby 2.2.5p319
BUNDLED WITH
- 1.15.4
+ 1.16.0
diff --git a/README.md b/README.md
index b8f9cef..7805e5d 100644
--- a/README.md
+++ b/README.md
@@ -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).
@@ -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.
diff --git a/Rakefile b/Rakefile
index 51febe9..3a67298 100644
--- a/Rakefile
+++ b/Rakefile
@@ -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
diff --git a/app.rb b/app.rb
index 18b6327..547e7ca 100644
--- a/app.rb
+++ b/app.rb
@@ -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
@@ -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
@@ -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'],
@@ -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'
@@ -202,7 +216,7 @@ def worksheet
@title = 'Manage Content'
mustache :manage
end
-
+
post '/manage/update_opp_centers' do
protected!
puts params
@@ -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 =
+ "
+ 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.
+
Here are your submissions:
+
First Name: #{params['first_name']}
+
Last Name: #{params['last_name']}
+
Best way to contact: #{params['best_way']}
+
Email: #{params['email_submission']}
+
Phone: #{params['phone_submission']}
+
Text: #{params['text_submission']}
+
Referred by: #{params['referral']}
+
Which neighborhood: #{params['neighborhood']}
+
Are you a young adult? #{params['young_adult']}
+
Are you a veteran? #{params['veteran']}
+
Do you have little access to transportation?
+ #{params['no_transportation']}
+
Are you homeless or staying with someone temporarily?
+ #{params['homeless']}
+
I dont have a drivers license. #{params['no_drivers_license']}
+
I dont have a state-issued I.D. #{params['no_state_id']}
+
I am disabled. #{params['disabled']}
+
I need childcare. #{params['childcare']}
+
I have an open criminal charge. #{params['criminal']}
+
I have been previously incarcerated.
+ #{params['previously_incarcerated']}
+
I am using drugs and want to get help. #{params['using_drugs']}
+
None of the above. #{params['none']}"
+
+ # 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
diff --git a/awsemailer.rb b/awsemailer.rb
new file mode 100644
index 0000000..e520122
--- /dev/null
+++ b/awsemailer.rb
@@ -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
diff --git a/docs/career_assessment_how_to.md b/docs/career_assessment_how_to.md
index 13db805..52bb083 100644
--- a/docs/career_assessment_how_to.md
+++ b/docs/career_assessment_how_to.md
@@ -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/.
@@ -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.
diff --git a/docs/updating_content.md b/docs/updating_content.md
index d8083ff..d6f25b5 100644
--- a/docs/updating_content.md
+++ b/docs/updating_content.md
@@ -11,11 +11,11 @@ The basic html for each page can be found in a `*.mustache` file in the `/templa
* Text-only Opportunity Center information from map: `opp_center_info.mustache`
## Career assessment
-The careers are managed via a google spreadsheet (https://docs.google.com/spreadsheets/d/1lMeZZ_vu-xIVKLgo7obKlF0X4iJ3oHkLKl8uIrPsSt8, current owners CfA Fellows).
+The careers are managed via a google spreadsheet (`https://docs.google.com/spreadsheets/d/1cXQCnOpP_IcJS_N8VOPD1z9prYVUPGhtb7rlgvGnJ8E`, current owners Loyola University New Orleans Department of Computer Science).
-To update the information about a career: edit that spreadsheet, then go to the ["manage" page](http://workforwardnola.com/manage) & enter the username and password. Enter the PUBLIC link to the spreadsheet (https://docs.google.com/spreadsheets/d/1lMeZZ_vu-xIVKLgo7obKlF0X4iJ3oHkLKl8uIrPsSt8/pubhtml), wait for the link to be validated, then click "Update content".
+To update the information about a career: edit that spreadsheet, then go to the ["manage" page](http://workforwardnola.com/manage) & enter the username and password. Enter the PUBLIC link to the spreadsheet (`https://docs.google.com/spreadsheets/d/1cXQCnOpP_IcJS_N8VOPD1z9prYVUPGhtb7rlgvGnJ8E/pubhtml`), wait for the link to be validated, then click "Update content".
-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/. You will need to copy and paste your HTML in the tool, and run it to see the changes.
+When editing the content DO NOT change the column headers or the content will not update properly. The columns `description`, `general_duties`, `training`, `career_image`, and `alt_title` accept HTML formatting. It's recommended to double check your HTML with a tool like https://jsfiddle.net/. You will need to copy and paste your HTML in the tool, and run it to see the changes.
The numbers in the `traits` column correspond to the `id` column in the traits spreadsheet tab. To add or remove a trait from a career, add or remove the corrsponding `id` number from the `traits` column (make sure they are comma-separated).
diff --git a/emailer.rb b/emailer.rb
new file mode 100644
index 0000000..29dee6d
--- /dev/null
+++ b/emailer.rb
@@ -0,0 +1,13 @@
+require './awsemailer.rb'
+require './app.rb'
+
+module WorkForwardNola
+ # Abstract class for emailing systems
+ class Emailer
+ def initialize; end
+
+ def send_email
+ throw Error('Emailer is an abstract class. Please use an implementation class.')
+ end
+ end
+end
diff --git a/emailprovider.rb b/emailprovider.rb
new file mode 100644
index 0000000..11f0d57
--- /dev/null
+++ b/emailprovider.rb
@@ -0,0 +1,21 @@
+require './awsemailer.rb'
+module WorkForwardNola
+ # Sets up AWS SES variables through the environment file
+ class EmailProvider
+ @@emailer = AwsEmailer.new ENV['AWS_ACCESS'], ENV['AWS_SECRET']
+ @@owner_email = ENV['OWNER_EMAIL']
+ @@sender_email = ENV['SENDER_EMAIL']
+
+ def self.emailer
+ @@emailer
+ end
+
+ def self.sender
+ @@sender_email
+ end
+
+ def self.owner
+ @@owner_email
+ end
+ end
+end
diff --git a/models/oppcenter.rb b/models/oppcenter.rb
index b27b613..8784022 100644
--- a/models/oppcenter.rb
+++ b/models/oppcenter.rb
@@ -3,7 +3,7 @@ module WorkForwardNola
class OppCenter < Sequel::Model
def self.bulk_create(data)
# Clear oppcenter table
- OppCenter.db.run 'TRUNCATE oppcenters CASCADE'
+ OppCenter.db.run 'TRUNCATE opp_centers CASCADE'
data.each do |opp_c|
OppCenter.create(
center: opp_c['center'],
diff --git a/templates/careers.mustache b/templates/careers.mustache
index 136c8cc..9fdfaf3 100644
--- a/templates/careers.mustache
+++ b/templates/careers.mustache
@@ -42,7 +42,7 @@
Sign up for the Opportunity Center job system and receive a personalized recommendation for next steps and a follow-up from a career advisor.
Find information on certification and training programs
diff --git a/templates/index.mustache b/templates/index.mustache
index de03dc3..dada82f 100644
--- a/templates/index.mustache
+++ b/templates/index.mustache
@@ -46,7 +46,7 @@
Visit the location that's right for you
Click on a pin below to see the open hours, address, phone number and website for each Opportunity Center.
-
You can also click here to see a text-only version of this information.
+
You can also click here to see a text-only version of this information.
Sign up for the Opportunity Center job system and receive a personalized recommendation for next steps and a follow-up from a career advisor.
-