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

- + Job system
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.

Job system diff --git a/templates/jobsystem.mustache b/templates/jobsystem.mustache index d932223..219a457 100644 --- a/templates/jobsystem.mustache +++ b/templates/jobsystem.mustache @@ -11,35 +11,29 @@ Back to home page
-
+

Job System Sign-up Form

- Whats your name? * -
- How did you learn about us? * -

- - - -
- -
-
- First Name                  - Last Name - - -
-

We're going to ask a couple of questions to figure out which + What's your name? * +
+ First Name:
+ Last Name: +

+ How did you learn about us? * +
+ + +
+

We're going to ask a couple of questions to figure out which Opportunity Center is the best match for you

Which of these apply to you? *

@@ -80,21 +74,18 @@

Now that you know where to go, add your contact information so that - we can follow up and answer any questions you may have.


- What's the best way to contact you? *
- E-mail
-
- Phone Call
-
- Text Message
-
- If you have a resume, upload it here.
-
-
-
- + we can follow up and answer any questions you may have.
+ What's the best way to contact you? *
+ E-mail
+
+ Phone Call
+
+ Text Message
+
+ If you have a resume, upload it here.
+

+
+

diff --git a/templates/manage.mustache b/templates/manage.mustache index 721fdbb..9886736 100644 --- a/templates/manage.mustache +++ b/templates/manage.mustache @@ -9,6 +9,8 @@

+

Update Opportunity Center Information:

@@ -21,12 +23,8 @@

- -
- -

If you're starting from scratch, use this template. After all your data is in, publish it to the web ("File" > "Publish to Web") and use that url above.

+

If you're starting from scratch, use this as a reference. After all your data is in, publish it to the web ("File" > "Publish to Web") and use that url above.

If you're working with Code for America, use this url.

Trouble saving data? Make sure it adheres to the following: