From d6a1c62d2a9b521267018f664c97eb466be06031 Mon Sep 17 00:00:00 2001 From: lettergram Date: Thu, 2 Dec 2021 22:03:16 -0600 Subject: [PATCH] free the code --- .browserslistrc | 1 + .gitignore | 49 + Gemfile | 58 + Guardfile | 72 + Procfile | 2 + README.md | 5 + Rakefile | 6 + app/assets/config/manifest.js | 2 + app/assets/images/.keep | 0 .../vocalvoters_logos/vocalvoters-beta.png | Bin 0 -> 70791 bytes .../vocalvoters-favicon-256.png | Bin 0 -> 19020 bytes .../images/vocalvoters_logos/vocalvoters.png | Bin 0 -> 57004 bytes .../stylesheets/account_activations.scss | 3 + app/assets/stylesheets/application.css | 15 + app/assets/stylesheets/custom.css.scss | 1619 +++++++++++++++++ app/assets/stylesheets/emails.scss | 3 + app/assets/stylesheets/faxes.scss | 3 + app/assets/stylesheets/letters.scss | 3 + app/assets/stylesheets/password_resets.scss | 3 + app/assets/stylesheets/payments.scss | 3 + app/assets/stylesheets/pdf.scss | 1 + app/assets/stylesheets/posts.scss | 3 + app/assets/stylesheets/recipients.scss | 3 + app/assets/stylesheets/scaffolds.scss | 65 + .../stylesheets/send_communication.scss | 3 + app/assets/stylesheets/senders.scss | 3 + app/assets/stylesheets/sessions.scss | 3 + app/assets/stylesheets/static_pages.scss | 3 + app/assets/stylesheets/users.scss | 3 + app/channels/application_cable/channel.rb | 4 + app/channels/application_cable/connection.rb | 4 + .../account_activations_controller.rb | 16 + app/controllers/application_controller.rb | 14 + app/controllers/concerns/.keep | 0 app/controllers/emails_controller.rb | 76 + app/controllers/faxes_controller.rb | 76 + app/controllers/letters_controller.rb | 232 +++ app/controllers/password_resets_controller.rb | 68 + app/controllers/payments_controller.rb | 42 + app/controllers/posts_controller.rb | 76 + app/controllers/recipients_controller.rb | 229 +++ .../send_communication_controller.rb | 261 +++ app/controllers/senders_controller.rb | 91 + app/controllers/sessions_controller.rb | 32 + app/controllers/static_pages_controller.rb | 132 ++ app/controllers/users_controller.rb | 91 + app/helpers/account_activations_helper.rb | 2 + app/helpers/application_helper.rb | 13 + app/helpers/emails_helper.rb | 2 + app/helpers/faxes_helper.rb | 2 + app/helpers/letters_helper.rb | 2 + app/helpers/password_resets_helper.rb | 2 + app/helpers/payments_helper.rb | 2 + app/helpers/posts_helper.rb | 2 + app/helpers/recipients_helper.rb | 2 + app/helpers/send_communication_helper.rb | 2 + app/helpers/senders_helper.rb | 2 + app/helpers/sessions_helper.rb | 64 + app/helpers/static_pages_helper.rb | 2 + app/helpers/users_helper.rb | 42 + app/javascript/channels/consumer.js | 6 + app/javascript/channels/index.js | 5 + app/javascript/packs/application.js | 15 + app/javascript/packs/landing_page.js | 650 +++++++ app/jobs/application_job.rb | 7 + app/mailers/application_mailer.rb | 4 + app/mailers/user_mailer.rb | 13 + app/models/application_record.rb | 3 + app/models/concerns/.keep | 0 app/models/email.rb | 6 + app/models/fax.rb | 5 + app/models/letter.rb | 11 + app/models/post.rb | 5 + app/models/recipient.rb | 2 + app/models/sender.rb | 2 + app/models/user.rb | 94 + app/views/emails/_email.json.jbuilder | 2 + app/views/emails/_form.html.erb | 42 + app/views/emails/edit.html.erb | 6 + app/views/emails/index.html.erb | 37 + app/views/emails/index.json.jbuilder | 1 + app/views/emails/new.html.erb | 5 + app/views/emails/show.html.erb | 29 + app/views/emails/show.json.jbuilder | 1 + app/views/faxes/_fax.json.jbuilder | 2 + app/views/faxes/_form.html.erb | 42 + app/views/faxes/edit.html.erb | 6 + app/views/faxes/index.html.erb | 37 + app/views/faxes/index.json.jbuilder | 1 + app/views/faxes/new.html.erb | 5 + app/views/faxes/show.html.erb | 29 + app/views/faxes/show.json.jbuilder | 1 + app/views/layouts/_footer.html.erb | 12 + app/views/layouts/_header.html.erb | 65 + app/views/layouts/_shim.html.erb | 4 + app/views/layouts/application.html.erb | 39 + app/views/layouts/mailer.html.erb | 13 + app/views/layouts/mailer.text.erb | 1 + app/views/layouts/pdf.html.erb | 15 + app/views/letters/_form.html.erb | 114 ++ app/views/letters/_letter.json.jbuilder | 2 + app/views/letters/edit.html.erb | 12 + app/views/letters/find_policy.json.jbuilder | 1 + app/views/letters/index.html.erb | 35 + app/views/letters/index.json.jbuilder | 1 + app/views/letters/letter.html.erb | 78 + app/views/letters/new.html.erb | 11 + app/views/letters/show.html.erb | 35 + app/views/letters/show.json.jbuilder | 1 + app/views/password_resets/edit.html.erb | 21 + app/views/password_resets/new.html.erb | 14 + app/views/posts/_form.html.erb | 62 + app/views/posts/_post.json.jbuilder | 2 + app/views/posts/edit.html.erb | 6 + app/views/posts/index.html.erb | 47 + app/views/posts/index.json.jbuilder | 1 + app/views/posts/new.html.erb | 5 + app/views/posts/show.html.erb | 49 + app/views/posts/show.json.jbuilder | 1 + app/views/recipients/_form.html.erb | 202 ++ app/views/recipients/_recipient.html.erb | 65 + app/views/recipients/_recipient.json.jbuilder | 2 + app/views/recipients/edit.html.erb | 12 + app/views/recipients/index.html.erb | 65 + app/views/recipients/index.json.jbuilder | 1 + app/views/recipients/lookup.html.erb | 57 + app/views/recipients/lookup.json.jbuilder | 11 + app/views/recipients/new.html.erb | 11 + app/views/recipients/show.html.erb | 80 + app/views/recipients/show.json.jbuilder | 1 + app/views/senders/_form.html.erb | 66 + app/views/senders/_sender.json.jbuilder | 2 + app/views/senders/edit.html.erb | 14 + app/views/senders/index.html.erb | 37 + app/views/senders/index.json.jbuilder | 1 + app/views/senders/new.html.erb | 11 + app/views/senders/show.html.erb | 34 + app/views/senders/show.json.jbuilder | 1 + app/views/sessions/new.html.erb | 25 + app/views/shared/_error_messages.html.erb | 12 + app/views/static_pages/about.html.erb | 28 + app/views/static_pages/contact.html.erb | 29 + .../create_payment_intent.json.jbuilder | 1 + app/views/static_pages/help.html.erb | 3 + app/views/static_pages/home.html.erb | 235 +++ .../user_mailer/account_activation.html.erb | 12 + .../user_mailer/account_activation.text.erb | 8 + app/views/user_mailer/password_reset.html.erb | 21 + app/views/user_mailer/password_reset.text.erb | 10 + app/views/users/_form.html.erb | 17 + app/views/users/_user.html.erb | 7 + app/views/users/edit.html.erb | 8 + app/views/users/index.html.erb | 10 + app/views/users/new.html.erb | 8 + app/views/users/show.html.erb | 11 + babel.config.js | 70 + bin/bundle | 114 ++ bin/rails | 5 + bin/rake | 5 + bin/setup | 36 + bin/spring | 14 + bin/webpack | 18 + bin/webpack-dev-server | 18 + bin/yarn | 17 + config.ru | 6 + config/application.rb | 22 + config/boot.rb | 4 + config/cable.yml | 10 + config/credentials.yml.enc | 1 + config/database.yml | 30 + config/environment.rb | 5 + config/environments/development.rb | 92 + config/environments/production.rb | 137 ++ config/environments/test.rb | 61 + .../application_controller_renderer.rb | 8 + config/initializers/assets.rb | 14 + config/initializers/backtrace_silencers.rb | 8 + .../initializers/content_security_policy.rb | 30 + config/initializers/cookies_serializer.rb | 5 + .../initializers/filter_parameter_logging.rb | 6 + config/initializers/inflections.rb | 16 + config/initializers/mime_types.rb | 5 + config/initializers/permissions_policy.rb | 11 + config/initializers/wicked_pdf.rb | 36 + config/initializers/wrap_parameters.rb | 14 + config/locales/en.yml | 33 + config/puma.rb | 43 + config/routes.rb | 38 + config/spring.rb | 6 + config/storage.yml | 34 + config/webpack/development.js | 5 + config/webpack/environment.js | 11 + config/webpack/production.js | 5 + config/webpack/test.js | 5 + config/webpacker.yml | 92 + db/migrate/20210603041537_create_users.rb | 10 + ...20210603042417_add_index_to_users_email.rb | 5 + ...0603042940_add_password_digest_to_users.rb | 5 + ...0603185100_add_remember_digest_to_users.rb | 5 + .../20210604190943_add_admin_to_users.rb | 5 + .../20210604195135_add_activation_to_users.rb | 7 + .../20210605041256_add_reset_to_users.rb | 6 + ...0609044142_create_pay_subscriptions.pay.rb | 18 + .../20210609044143_create_pay_charges.pay.rb | 19 + ...144_add_status_to_pay_subscriptions.pay.rb | 15 + ...210609044145_add_data_to_pay_models.pay.rb | 16 + ...0609044146_add_data_to_pay_billable.pay.rb | 20 + ...9044147_add_currency_to_pay_charges.pay.rb | 6 + ...8_add_application_fee_to_pay_models.pay.rb | 8 + db/migrate/20210610040024_create_letters.rb | 14 + db/migrate/20210629195032_create_senders.rb | 14 + .../20210629200322_create_recipients.rb | 21 + db/migrate/20210629202832_create_faxes.rb | 14 + db/migrate/20210629203005_create_posts.rb | 19 + db/migrate/20210629203017_create_emails.rb | 14 + ...30181252_add_contact_form_to_recipients.rb | 5 + .../20210630184151_add_email_to_senders.rb | 5 + ...6_change_number_to_bigint_on_recipients.rb | 6 + ...0915042754_add_retirement_to_recipients.rb | 5 + ...arget_level_and_target_state_to_letters.rb | 6 + db/schema.rb | 172 ++ db/seeds.rb | 28 + lib/assets/.keep | 0 lib/tasks/.keep | 0 log/.keep | 0 package.json | 19 + public/404.html | 67 + public/422.html | 67 + public/500.html | 66 + public/apple-touch-icon-precomposed.png | 0 public/apple-touch-icon.png | 0 public/favicon.ico | 0 public/robots.txt | 1 + storage/.keep | 0 test/application_system_test_case.rb | 5 + .../application_cable/connection_test.rb | 11 + test/controllers/.keep | 0 .../account_activations_controller_test.rb | 7 + test/controllers/emails_controller_test.rb | 48 + test/controllers/faxes_controller_test.rb | 48 + test/controllers/letters_controller_test.rb | 48 + test/controllers/payments_controller_test.rb | 7 + test/controllers/posts_controller_test.rb | 48 + .../controllers/recipients_controller_test.rb | 48 + .../send_communication_controller_test.rb | 7 + test/controllers/senders_controller_test.rb | 48 + test/controllers/sessions_controller_test.rb | 8 + .../static_pages_controller_test.rb | 33 + test/controllers/users_controller_test.rb | 73 + test/fixtures/emails.yml | 15 + test/fixtures/faxes.yml | 15 + test/fixtures/files/.keep | 0 test/fixtures/letters.yml | 26 + test/fixtures/posts.yml | 23 + test/fixtures/recipients.yml | 31 + test/fixtures/senders.yml | 17 + test/fixtures/users.yml | 44 + test/helpers/.keep | 0 test/helpers/application_helper_test.rb | 8 + test/integration/.keep | 0 test/integration/password_resets_test.rb | 80 + test/integration/site_layout_test.rb | 16 + test/integration/users_edit_test.rb | 55 + test/integration/users_index_test.rb | 35 + test/integration/users_login_test.rb | 56 + test/integration/users_show_test.rb | 21 + test/integration/users_signup_test.rb | 51 + test/mailers/.keep | 0 test/mailers/previews/user_mailer_preview.rb | 20 + test/mailers/user_mailer_test.rb | 28 + test/models/.keep | 0 test/models/email_test.rb | 7 + test/models/fax_test.rb | 7 + test/models/letter_test.rb | 7 + test/models/post_test.rb | 7 + test/models/recipient_test.rb | 7 + test/models/sender_test.rb | 7 + test/models/user_test.rb | 79 + test/system/.keep | 0 test/system/emails_test.rb | 51 + test/system/faxes_test.rb | 51 + test/system/letters_test.rb | 51 + test/system/posts_test.rb | 59 + test/system/recipients_test.rb | 67 + test/system/senders_test.rb | 53 + test/test_helper.rb | 35 + tmp/.keep | 0 tmp/pids/.keep | 0 vendor/.keep | 0 289 files changed, 9604 insertions(+) create mode 100644 .browserslistrc create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Guardfile create mode 100644 Procfile create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/.keep create mode 100644 app/assets/images/vocalvoters_logos/vocalvoters-beta.png create mode 100644 app/assets/images/vocalvoters_logos/vocalvoters-favicon-256.png create mode 100644 app/assets/images/vocalvoters_logos/vocalvoters.png create mode 100644 app/assets/stylesheets/account_activations.scss create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/assets/stylesheets/custom.css.scss create mode 100644 app/assets/stylesheets/emails.scss create mode 100644 app/assets/stylesheets/faxes.scss create mode 100644 app/assets/stylesheets/letters.scss create mode 100644 app/assets/stylesheets/password_resets.scss create mode 100644 app/assets/stylesheets/payments.scss create mode 100644 app/assets/stylesheets/pdf.scss create mode 100644 app/assets/stylesheets/posts.scss create mode 100644 app/assets/stylesheets/recipients.scss create mode 100644 app/assets/stylesheets/scaffolds.scss create mode 100644 app/assets/stylesheets/send_communication.scss create mode 100644 app/assets/stylesheets/senders.scss create mode 100644 app/assets/stylesheets/sessions.scss create mode 100644 app/assets/stylesheets/static_pages.scss create mode 100644 app/assets/stylesheets/users.scss create mode 100644 app/channels/application_cable/channel.rb create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/controllers/account_activations_controller.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/controllers/emails_controller.rb create mode 100644 app/controllers/faxes_controller.rb create mode 100644 app/controllers/letters_controller.rb create mode 100644 app/controllers/password_resets_controller.rb create mode 100644 app/controllers/payments_controller.rb create mode 100644 app/controllers/posts_controller.rb create mode 100644 app/controllers/recipients_controller.rb create mode 100644 app/controllers/send_communication_controller.rb create mode 100644 app/controllers/senders_controller.rb create mode 100644 app/controllers/sessions_controller.rb create mode 100644 app/controllers/static_pages_controller.rb create mode 100644 app/controllers/users_controller.rb create mode 100644 app/helpers/account_activations_helper.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/emails_helper.rb create mode 100644 app/helpers/faxes_helper.rb create mode 100644 app/helpers/letters_helper.rb create mode 100644 app/helpers/password_resets_helper.rb create mode 100644 app/helpers/payments_helper.rb create mode 100644 app/helpers/posts_helper.rb create mode 100644 app/helpers/recipients_helper.rb create mode 100644 app/helpers/send_communication_helper.rb create mode 100644 app/helpers/senders_helper.rb create mode 100644 app/helpers/sessions_helper.rb create mode 100644 app/helpers/static_pages_helper.rb create mode 100644 app/helpers/users_helper.rb create mode 100644 app/javascript/channels/consumer.js create mode 100644 app/javascript/channels/index.js create mode 100644 app/javascript/packs/application.js create mode 100644 app/javascript/packs/landing_page.js create mode 100644 app/jobs/application_job.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/mailers/user_mailer.rb create mode 100644 app/models/application_record.rb create mode 100644 app/models/concerns/.keep create mode 100644 app/models/email.rb create mode 100644 app/models/fax.rb create mode 100644 app/models/letter.rb create mode 100644 app/models/post.rb create mode 100644 app/models/recipient.rb create mode 100644 app/models/sender.rb create mode 100644 app/models/user.rb create mode 100644 app/views/emails/_email.json.jbuilder create mode 100644 app/views/emails/_form.html.erb create mode 100644 app/views/emails/edit.html.erb create mode 100644 app/views/emails/index.html.erb create mode 100644 app/views/emails/index.json.jbuilder create mode 100644 app/views/emails/new.html.erb create mode 100644 app/views/emails/show.html.erb create mode 100644 app/views/emails/show.json.jbuilder create mode 100644 app/views/faxes/_fax.json.jbuilder create mode 100644 app/views/faxes/_form.html.erb create mode 100644 app/views/faxes/edit.html.erb create mode 100644 app/views/faxes/index.html.erb create mode 100644 app/views/faxes/index.json.jbuilder create mode 100644 app/views/faxes/new.html.erb create mode 100644 app/views/faxes/show.html.erb create mode 100644 app/views/faxes/show.json.jbuilder create mode 100644 app/views/layouts/_footer.html.erb create mode 100644 app/views/layouts/_header.html.erb create mode 100644 app/views/layouts/_shim.html.erb create mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100644 app/views/layouts/pdf.html.erb create mode 100644 app/views/letters/_form.html.erb create mode 100644 app/views/letters/_letter.json.jbuilder create mode 100644 app/views/letters/edit.html.erb create mode 100644 app/views/letters/find_policy.json.jbuilder create mode 100644 app/views/letters/index.html.erb create mode 100644 app/views/letters/index.json.jbuilder create mode 100644 app/views/letters/letter.html.erb create mode 100644 app/views/letters/new.html.erb create mode 100644 app/views/letters/show.html.erb create mode 100644 app/views/letters/show.json.jbuilder create mode 100644 app/views/password_resets/edit.html.erb create mode 100644 app/views/password_resets/new.html.erb create mode 100644 app/views/posts/_form.html.erb create mode 100644 app/views/posts/_post.json.jbuilder create mode 100644 app/views/posts/edit.html.erb create mode 100644 app/views/posts/index.html.erb create mode 100644 app/views/posts/index.json.jbuilder create mode 100644 app/views/posts/new.html.erb create mode 100644 app/views/posts/show.html.erb create mode 100644 app/views/posts/show.json.jbuilder create mode 100644 app/views/recipients/_form.html.erb create mode 100644 app/views/recipients/_recipient.html.erb create mode 100644 app/views/recipients/_recipient.json.jbuilder create mode 100644 app/views/recipients/edit.html.erb create mode 100644 app/views/recipients/index.html.erb create mode 100644 app/views/recipients/index.json.jbuilder create mode 100644 app/views/recipients/lookup.html.erb create mode 100644 app/views/recipients/lookup.json.jbuilder create mode 100644 app/views/recipients/new.html.erb create mode 100644 app/views/recipients/show.html.erb create mode 100644 app/views/recipients/show.json.jbuilder create mode 100644 app/views/senders/_form.html.erb create mode 100644 app/views/senders/_sender.json.jbuilder create mode 100644 app/views/senders/edit.html.erb create mode 100644 app/views/senders/index.html.erb create mode 100644 app/views/senders/index.json.jbuilder create mode 100644 app/views/senders/new.html.erb create mode 100644 app/views/senders/show.html.erb create mode 100644 app/views/senders/show.json.jbuilder create mode 100644 app/views/sessions/new.html.erb create mode 100644 app/views/shared/_error_messages.html.erb create mode 100644 app/views/static_pages/about.html.erb create mode 100644 app/views/static_pages/contact.html.erb create mode 100644 app/views/static_pages/create_payment_intent.json.jbuilder create mode 100644 app/views/static_pages/help.html.erb create mode 100644 app/views/static_pages/home.html.erb create mode 100644 app/views/user_mailer/account_activation.html.erb create mode 100644 app/views/user_mailer/account_activation.text.erb create mode 100644 app/views/user_mailer/password_reset.html.erb create mode 100644 app/views/user_mailer/password_reset.text.erb create mode 100644 app/views/users/_form.html.erb create mode 100644 app/views/users/_user.html.erb create mode 100644 app/views/users/edit.html.erb create mode 100644 app/views/users/index.html.erb create mode 100644 app/views/users/new.html.erb create mode 100644 app/views/users/show.html.erb create mode 100644 babel.config.js create mode 100755 bin/bundle create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/setup create mode 100755 bin/spring create mode 100755 bin/webpack create mode 100755 bin/webpack-dev-server create mode 100755 bin/yarn create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/cable.yml create mode 100644 config/credentials.yml.enc create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/initializers/application_controller_renderer.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/cookies_serializer.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/permissions_policy.rb create mode 100644 config/initializers/wicked_pdf.rb create mode 100644 config/initializers/wrap_parameters.rb create mode 100644 config/locales/en.yml create mode 100644 config/puma.rb create mode 100644 config/routes.rb create mode 100644 config/spring.rb create mode 100644 config/storage.yml create mode 100644 config/webpack/development.js create mode 100644 config/webpack/environment.js create mode 100644 config/webpack/production.js create mode 100644 config/webpack/test.js create mode 100644 config/webpacker.yml create mode 100644 db/migrate/20210603041537_create_users.rb create mode 100644 db/migrate/20210603042417_add_index_to_users_email.rb create mode 100644 db/migrate/20210603042940_add_password_digest_to_users.rb create mode 100644 db/migrate/20210603185100_add_remember_digest_to_users.rb create mode 100644 db/migrate/20210604190943_add_admin_to_users.rb create mode 100644 db/migrate/20210604195135_add_activation_to_users.rb create mode 100644 db/migrate/20210605041256_add_reset_to_users.rb create mode 100644 db/migrate/20210609044142_create_pay_subscriptions.pay.rb create mode 100644 db/migrate/20210609044143_create_pay_charges.pay.rb create mode 100644 db/migrate/20210609044144_add_status_to_pay_subscriptions.pay.rb create mode 100644 db/migrate/20210609044145_add_data_to_pay_models.pay.rb create mode 100644 db/migrate/20210609044146_add_data_to_pay_billable.pay.rb create mode 100644 db/migrate/20210609044147_add_currency_to_pay_charges.pay.rb create mode 100644 db/migrate/20210609044148_add_application_fee_to_pay_models.pay.rb create mode 100644 db/migrate/20210610040024_create_letters.rb create mode 100644 db/migrate/20210629195032_create_senders.rb create mode 100644 db/migrate/20210629200322_create_recipients.rb create mode 100644 db/migrate/20210629202832_create_faxes.rb create mode 100644 db/migrate/20210629203005_create_posts.rb create mode 100644 db/migrate/20210629203017_create_emails.rb create mode 100644 db/migrate/20210630181252_add_contact_form_to_recipients.rb create mode 100644 db/migrate/20210630184151_add_email_to_senders.rb create mode 100644 db/migrate/20210910044626_change_number_to_bigint_on_recipients.rb create mode 100644 db/migrate/20210915042754_add_retirement_to_recipients.rb create mode 100644 db/migrate/20211021051914_add_target_level_and_target_state_to_letters.rb create mode 100644 db/schema.rb create mode 100644 db/seeds.rb create mode 100644 lib/assets/.keep create mode 100644 lib/tasks/.keep create mode 100644 log/.keep create mode 100644 package.json create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/apple-touch-icon-precomposed.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon.ico create mode 100644 public/robots.txt create mode 100644 storage/.keep create mode 100644 test/application_system_test_case.rb create mode 100644 test/channels/application_cable/connection_test.rb create mode 100644 test/controllers/.keep create mode 100644 test/controllers/account_activations_controller_test.rb create mode 100644 test/controllers/emails_controller_test.rb create mode 100644 test/controllers/faxes_controller_test.rb create mode 100644 test/controllers/letters_controller_test.rb create mode 100644 test/controllers/payments_controller_test.rb create mode 100644 test/controllers/posts_controller_test.rb create mode 100644 test/controllers/recipients_controller_test.rb create mode 100644 test/controllers/send_communication_controller_test.rb create mode 100644 test/controllers/senders_controller_test.rb create mode 100644 test/controllers/sessions_controller_test.rb create mode 100644 test/controllers/static_pages_controller_test.rb create mode 100644 test/controllers/users_controller_test.rb create mode 100644 test/fixtures/emails.yml create mode 100644 test/fixtures/faxes.yml create mode 100644 test/fixtures/files/.keep create mode 100644 test/fixtures/letters.yml create mode 100644 test/fixtures/posts.yml create mode 100644 test/fixtures/recipients.yml create mode 100644 test/fixtures/senders.yml create mode 100644 test/fixtures/users.yml create mode 100644 test/helpers/.keep create mode 100644 test/helpers/application_helper_test.rb create mode 100644 test/integration/.keep create mode 100644 test/integration/password_resets_test.rb create mode 100644 test/integration/site_layout_test.rb create mode 100644 test/integration/users_edit_test.rb create mode 100644 test/integration/users_index_test.rb create mode 100644 test/integration/users_login_test.rb create mode 100644 test/integration/users_show_test.rb create mode 100644 test/integration/users_signup_test.rb create mode 100644 test/mailers/.keep create mode 100644 test/mailers/previews/user_mailer_preview.rb create mode 100644 test/mailers/user_mailer_test.rb create mode 100644 test/models/.keep create mode 100644 test/models/email_test.rb create mode 100644 test/models/fax_test.rb create mode 100644 test/models/letter_test.rb create mode 100644 test/models/post_test.rb create mode 100644 test/models/recipient_test.rb create mode 100644 test/models/sender_test.rb create mode 100644 test/models/user_test.rb create mode 100644 test/system/.keep create mode 100644 test/system/emails_test.rb create mode 100644 test/system/faxes_test.rb create mode 100644 test/system/letters_test.rb create mode 100644 test/system/posts_test.rb create mode 100644 test/system/recipients_test.rb create mode 100644 test/system/senders_test.rb create mode 100644 test/test_helper.rb create mode 100644 tmp/.keep create mode 100644 tmp/pids/.keep create mode 100644 vendor/.keep diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..e94f814 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1 @@ +defaults diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fc96f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep + +/public/assets +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key + +/public/packs +/public/packs-test +/node_modules +/yarn-error.log +yarn-debug.log* +.yarn-integrity + + +## Emacs +**~ +**# + +*.lock +.ruby-version +postcss.config.js \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..40c9759 --- /dev/null +++ b/Gemfile @@ -0,0 +1,58 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '3.0.2' + +gem 'rails', '>=6.1.3.2' +gem 'image_processing', '1.9.3' +gem 'mini_magick', '4.9.5' +gem 'active_storage_validations', '0.8.9' +gem 'bcrypt', '3.1.13' +gem 'faker', '2.11.0' +gem 'will_paginate', '3.3.0' +gem 'bootstrap-will_paginate', '1.0.0' +gem 'bootstrap-sass', '3.4.1' +gem 'puma', '5.3.1' +gem 'sass-rails', '6.0.0' +gem 'webpacker', '>=5.2.1' +gem 'turbolinks', '5.2.1' +gem 'jbuilder', '2.10.0' +gem 'bootsnap', '1.7.2', require: false + +gem 'wicked_pdf' +gem 'wkhtmltopdf-binary' +gem 'wkhtmltopdf-heroku' + +gem 'pay', '~> 2.0' +gem 'stripe', '< 6.0', '>= 2.8' +# gem 'clicksend_client', '>=5.0.0' +gem 'clicksend_client', :git => 'https://github.com/ClickSend/clicksend-ruby.git' + +group :development, :test do + gem 'sqlite3', '1.4.2' + gem 'byebug', '11.1.3', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + gem 'web-console', '4.1.0' + gem 'rack-mini-profiler', '2.3.1' + gem 'listen', '3.4.1' + gem 'spring', '2.1.1' +end + +group :test do + gem 'capybara', '3.35.3' + gem 'selenium-webdriver', '3.142.7' + gem 'webdrivers', '4.6.0' + gem 'rails-controller-testing', '1.0.5' + gem 'minitest', '>=5.11.3' + gem 'minitest-reporters', '1.3.8' + gem 'guard', '2.16.2' + gem 'guard-minitest', '2.4.6' + gem 'rexml' +end + +group :production do + gem 'pg', '1.2.3' + gem 'aws-sdk-s3', '1.87.0', require: false +end diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..c63d1ec --- /dev/null +++ b/Guardfile @@ -0,0 +1,72 @@ +require 'active_support/core_ext/string' +# Defines the matching rules for Guard. +guard :minitest, spring: "bin/rails test", all_on_start: false do + watch(%r{^test/(.*)/?(.*)_test\.rb$}) + watch('test/test_helper.rb') { 'test' } + watch('config/routes.rb') { interface_tests } + watch(%r{app/views/layouts/*}) { interface_tests } + watch(%r{^app/models/(.*?)\.rb$}) do |matches| + ["test/models/#{matches[1]}_test.rb", + "test/integration/microposts_interface_test.rb"] + end + watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches| + "test/models/#{matches[1].singularize}_test.rb" + end + watch(%r{^app/mailers/(.*?)\.rb$}) do |matches| + "test/mailers/#{matches[1]}_test.rb" + end + watch(%r{^app/views/(.*)_mailer/.*$}) do |matches| + "test/mailers/#{matches[1]}_mailer_test.rb" + end + watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| + resource_tests(matches[1]) + end + watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| + ["test/controllers/#{matches[1]}_controller_test.rb"] + + integration_tests(matches[1]) + end + watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| + integration_tests(matches[1]) + end + watch('app/views/layouts/application.html.erb') do + 'test/integration/site_layout_test.rb' + end + watch('app/helpers/sessions_helper.rb') do + integration_tests << 'test/helpers/sessions_helper_test.rb' + end + watch('app/controllers/sessions_controller.rb') do + ['test/controllers/sessions_controller_test.rb', + 'test/integration/users_login_test.rb'] + end + watch('app/controllers/account_activations_controller.rb') do + 'test/integration/users_signup_test.rb' + end + watch(%r{app/views/users/*}) do + resource_tests('users') + + ['test/integration/microposts_interface_test.rb'] + end +end + +# Returns the integration tests corresponding to the given resource. +def integration_tests(resource = :all) + if resource == :all + Dir["test/integration/*"] + else + Dir["test/integration/#{resource}_*.rb"] + end +end + +# Returns all tests that hit the interface. +def interface_tests + integration_tests << "test/controllers" +end + +# Returns the controller tests corresponding to the given resource. +def controller_test(resource) + "test/controllers/#{resource}_controller_test.rb" +end + +# Returns all tests for the given resource. +def resource_tests(resource) + integration_tests(resource) << controller_test(resource) +end diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..474e28f --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +release: rake db:migrate +web: bundle exec puma -C config/puma.rb \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc494a8 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +VocalVoters.com +========== + + Fax, Mail, E-Mail Your Government Representatives + in 30 Seconds or Less. \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..9a5ea73 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..5918193 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,2 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/images/vocalvoters_logos/vocalvoters-beta.png b/app/assets/images/vocalvoters_logos/vocalvoters-beta.png new file mode 100644 index 0000000000000000000000000000000000000000..9f18713c35f7ac886a9e898f5be9abab5c6d7d9a GIT binary patch literal 70791 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 D>d552 literal 0 HcmV?d00001 diff --git a/app/assets/images/vocalvoters_logos/vocalvoters-favicon-256.png b/app/assets/images/vocalvoters_logos/vocalvoters-favicon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..590e69d6146df3046ac903affab741e4953bf249 GIT binary patch literal 19020 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/vocalvoters_logos/vocalvoters.png b/app/assets/images/vocalvoters_logos/vocalvoters.png new file mode 100644 index 0000000000000000000000000000000000000000..3622efa1f6df190c950994a7b6ebb4a6dd46deae GIT binary patch literal 57004 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 Fe*tV@?eqWu literal 0 HcmV?d00001 diff --git a/app/assets/stylesheets/account_activations.scss b/app/assets/stylesheets/account_activations.scss new file mode 100644 index 0000000..65f3ebe --- /dev/null +++ b/app/assets/stylesheets/account_activations.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the AccountActivations controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000..fe30bc2 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_self + *= require_tree . + */ diff --git a/app/assets/stylesheets/custom.css.scss b/app/assets/stylesheets/custom.css.scss new file mode 100644 index 0000000..23c1d7b --- /dev/null +++ b/app/assets/stylesheets/custom.css.scss @@ -0,0 +1,1619 @@ + +@import "bootstrap-sprockets"; +@import "bootstrap"; + +/* mixins, variables, etc. */ + +$gray-medium-light: #eaeaea; + +@mixin box_sizing { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/* universal */ +html { + height: 100vh; + min-height: 100vh; +} + + +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding-top: 50px; + margin: 50px; + @media (max-width: 900px){ + padding-top: 40px; + margin: 10px; + } + height: 100vh; + min-height: 100vh; +} + +.underline-on-hover:hover { + text-decoration: underline; +} + +textarea { + resize: vertical; +} + +.center { + text-align: center; + h1 { + margin-bottom: 10px; + } +} + +/* typography */ + +h1, h2, h3, h4, h5, h6, h1_search { + line-height: 1; +} + +h1_search { + font-size: 3em; + letter-spacing: -2px; + font-weight: bold; + margin-bottom: 30px; + // color: $gray-light; +} + +h1-landing { + font-size: 6em; + letter-spacing: 2px; + margin-bottom: 30px; + text-align: left; +} + +h2-landing { + font-size: 2em; + letter-spacing: -1px; + text-align: left; + font-weight: strong; + color: $gray-light; +} + +h1 { + font-size: 3em; + letter-spacing: -2px; + margin-bottom: 30px; + text-align: center; +} + +h2 { + font-size: 2em; + letter-spacing: -1px; + text-align: center; + font-weight: normal; + color: $gray-light; +} + +p { + font-size: 1.1em; + line-height: 1.7em; +} + +a { + color: #00CCFF; +} + +.information { + p { + color: #606060; + font-size: 1.3em; + a { + color: #66CCCC; + } + } +} + +blockquote { + background: #f9f9f9; + border-left: 10px solid #ccc; + margin: 1.5em 10px; + padding: 0.5em 10px; + quotes: "\201C""\201D""\2018""\2019"; +} +blockquote:before { + color: #ccc; + content: open-quote; + font-size: 2em; + line-height: 0.1em; + margin-right: 0.25em; + vertical-align: -0.4em; +} +blockquote p { + display: inline; +} + +/* header */ +#logo { + float: left; + margin-right: 10px; + font-size: 1.7em; + color: white; + text-transform: uppercase; + letter-spacing: -1px; + padding-top: 0.25em; + padding-bottom: 0.25em; + font-weight: bold; + &:hover { + opacity: 0.7; + color: white; + text-decoration: none; + } +} + +/* footer */ + +footer { + margin-top: 45px; + padding-top: 5px; + border-top: 1px solid $gray-medium-light; + color: $gray-light; + a { + color: $gray; + &:hover { + color: $gray-darker; + } + } + small { + float: left; + } + ul { + float: right; + list-style: none; + li { + float: left; + margin-left: 15px; + } + } +} + +/* sidebar */ + +aside { + section.user_info { + margin-top: 20px; + } + section { + padding: 10px 0; + margin-top: 20px; + &:first-child { + border: 0; + padding-top: 0; + } + span { + display: block; + margin-bottom: 3px; + line-height: 1; + } + h1 { + font-size: 1.4em; + text-align: left; + letter-spacing: -1px; + margin-bottom: 3px; + margin-top: 0px; + } + } +} + +.plan { + border: 1px solid black; +} + +.selectable { + &:hover { + cursor: pointer; + } +} +.selected { + background-color: lightgray; +} +.disabled { + color: gray; +} + +.gravatar { + float: left; + margin-right: 10px; +} + +.gravatar_edit { + margin-top: 15px; +} + +/* miscellaneous */ + +.debug_dump { + clear: both; + float: left; + width: 100%; + margin-top: 45px; + @include box_sizing; +} + +/* forms */ + +input, textarea, .uneditable-input { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +select { + border: 1px solid #bbb; + width: 100%; + margin-bottom: 15px; + @include box_sizing; +} + +input { + height: auto !important; +} + +#error_explanation { + color: red; + ul { + color: red; + margin: 0 0 30px 0; + } +} + +.field_with_errors { + @extend .has-error; + .form-control { + color: $state-danger-text; + } +} + +.checkbox { + margin-top: -10px; + margin-bottom: 10px; + span { + margin-left: 20px; + font-weight: normal; + } +} + +#session_remember_me { + width: auto; + margin-left: 0; +} + +/* Users index */ + +.users { + list-style: none; + margin: 0; + li { + overflow: auto; + /* padding: 10px; */ + border-bottom: 1px solid $gray-lighter; + margin-top: 10px; + } + details { + cursor:pointer; + font-size: 65%; + color: #DFDFDF; + } + .title { + color: #000000; + } + .listings { + color: #58EAFF; + } +} + +.assets-list { + list-style: none; + margin: 0; + padding: 0; + li { + overflow: auto; + padding: 0; + border-bottom: 1px solid $gray-lighter; + } + details { + cursor:pointer; + font-size: 65%; + color: #DFDFDF; + } + .title { + color: #000000; + } + .listings { + color: #58EAFF; + } +} + +/* Buttons */ + +.round-btn { + display: inline-block; + margin-top: 10px; + padding: 15px 30px; + border-radius: 50px; + font-weight: 700; + + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -ms-transition: all 0.3s; + -o-transition: all 0.3s; + transition: all 0.3s; +} + +.btn-dyn-eeg { + border: 2px solid rgb(219,34,120); + color: rgb(219,34,120); + background-image: image-url("eeg.gif"); +} +.btn-dyn-eeg:hover { + border: 2px solid #fff; + color: #fff; + background: rgb(225,45,130); +} + + +.btn-flat-white { + border: 2px solid #fff; + color: #fff; + background: rgba(255,255,255, .10); +} +.btn-flat-white:hover { + color: #fff; +} + +.btn-flat-color { + border: 2px solid rgb(255,45,130); + color: #666; +} +.btn-flat-color:hover { + color: #fff; + background: rgb(225,45,130); +} + +#changeDateButton, #start_date, #end_date, +#btnSearch, #btnClear, #btnRandom { + display: inline-block; + vertical-align: top; +} + +/*========== Loading ==========*/ + +#preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #fff; + z-index: 999; +} + +#loading-img { + position: absolute; + top: 50%; + left: 50%; + width: 128px; + height: 53px; + margin: -21px 0 0 -64px; +} + +/* Jumbotron */ +.jumbotron { + + min-height: 4vh; + height: 4vh; + line-height: 5em; + text-align: center; + vertical-align: middle; + margin-bottom: 0em; + + //background-color: #FFEDFA; + background-color: white; + + background-size: 100% 100%; + height: 100%; + -moz-background-size: 100% 100%; + -webkit-background-size: 100% 100%; + -o-background-size: 100% 100%; +} + +@media (max-width:629px) { + .spashlogo { + display: none; + } +} + +.landing { + min-height: 60vh; +} + +/*============ Images =========*/ +img { + max-width: 100%; + max-height: 100%; +} + +/*============ Loader ===========*/ +.loader, +.loader:before, +.loader:after { + /* background: #f50ca7; */ + background: #ffa3d0; + -webkit-animation: load1 1s infinite ease-in-out; + animation: load1 1s infinite ease-in-out; + width: 1em; + height: 4em; +} +.loader { + /* color: #f50ca7; */ + color: #ffa3d0; + vertical-align: middle; + text-indent: -9999em; + margin: 88px auto; + position: relative; + font-size: 11px; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} +.loader:before, +.loader:after { + position: absolute; + top: 0; + content: ''; +} +.loader:before { + left: -1.5em; + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} +.loader:after { + left: 1.5em; +} +@-webkit-keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 4em; + } + 40% { + box-shadow: 0 -2em; + height: 5em; + } +} +@keyframes load1 { + 0%, + 80%, + 100% { + box-shadow: 0 0; + height: 4em; + } + 40% { + box-shadow: 0 -2em; + height: 5em; + } +} + + +/* ====== Search Form ====== */ + +input.search-query:before { + display: block; + width: 2em; + height: 2em; + content: "\e003"; + font-family: 'Glyphicons Halflings'; + background-position: -6em 0; + position: absolute; + top:0.5em; + left:1.75em; + opacity: .5; + z-index: 1000; +} + + +input.search-query { + padding-left:2em; +} + +form.search-form { + position: relative; +} + +form.search-form:before { + display: block; + width: 2em; + height: 2em; + content: "\e003"; + font-family: 'Glyphicons Halflings'; + background-position: -6em 0; + position: absolute; + top:0.5em; + left:0.25em; + opacity: .5; + z-index: 1000; +} + +form.search-form-nav { + position: relative; +} +form.search-form-nav:before { + display: block; + content: "\e003"; + font-family: 'Glyphicons Halflings'; + background-position: -6em 0; + position: absolute; + top:0.5em; + left:1.75em; + opacity: .5; + z-index: 1000; +} + +.search-field { + box-sizing: border-box; + border: 0px solid #fff; + border-bottom: 2px solid #ccc; + display:inline-block; + background-color: white; + padding: 8px 3px 2px 3px; + + /* Remove form-control formatting */ + box-shadow: none; + -webkit-box-shadow: none; + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} + +.search-field:focus { + box-shadow: none; + -webkit-box-shadow: none; + border: 0px solid #fff; + padding: 8px 3px 1px 3px; + border-bottom: 3px solid #888; +} + +.btn-search { + color: #616161; + min-width: 100%; + background: #e3e3e3; + padding: 8px 25px 2px 25px; + border: 1px solid transparent; + display:inline-block; +} + +.btn-search:hover { + background: #c9c9c9; +} + +/* ===== navbar ====== */ +/* .navbar-nav { margin-bottom: -1em; } */ + +/* social nav */ +.social-nav { text-align: center;} +.social-nav a { display: inline-block; width: 40px; height: 40px; border-radius: 50%; border: 2px solid #666; margin: 0 5px; color: #666; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; transition: all 0.3s;} +.social-nav a:hover { color: rgb(219,34,120); border: 2px solid rgb(219,34,120);} +.social-nav span { line-height: 36px; font-size: 18px;} +.footer-nav { margin-top: 75px;} +.footer-nav a { color: #666; width: 50%; display: inline-block; margin-bottom: 5px; padding: 5px;} + +/* highcharts issue */ +svg{ + overflow:visible !important; +} +.highcharts-container{ + overflow:visible !important; +} + + +input[type="radio"]{ + vertical-align: baseline; +} + +/* help box */ +.helpbox { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; +} + +.helpbox .helpboxtext { + visibility: hidden; + width: 30vh; + word-wrap: break-word; + background-color: black; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px 5px 5px 5px; + position: absolute; + z-index: 1; + top: 150%; + left: 50%; + margin-left: -15vh; +} + +.helpbox .helpboxtext::after { + content: ""; + position: absolute; + bottom: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent transparent black transparent; +} +.helpbox:hover .helpboxtext { + visibility: visible; + opacity: 0.75 +} + + +.form-signin +{ + max-width: 30em; + padding: 1em; + margin: 0 auto; +} +.form-signin .form-signin-heading, .form-signin .checkbox +{ + margin-bottom: 10px; +} +.form-signin .checkbox +{ + font-weight: normal; +} +.form-signin .form-control +{ + position: relative; + font-size: 16px; + height: auto; + padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.form-signin .form-control:focus +{ + z-index: 2; +} +.form-signin input[type="text"] +{ + margin-bottom: -1px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} +.form-signin input[type="password"] +{ + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.account-wall +{ + margin-top: 10px; + padding: 10px 0px 10px 0px; + background-color: #f7f7f7; + -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); +} +.login-title +{ + color: #555; + font-size: 18px; + font-weight: 400; + display: block; +} +.profile-img +{ + width: 96px; + height: 96px; + margin: 0 auto 10px; + display: block; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + border-radius: 50%; +} +.need-help +{ + margin-top: 10px; +} +.new-account +{ + display: block; + margin-top: 10px; +} + + +/* KEY FEATURES SECTION STYLES +-------------------------------------------------*/ +.key-features-section { + padding: 140px 0 90px; + background: #eee; +} + +.single-key-feature { + padding: 0 70px; +} + +.single-key-feature:hover i { + -webkit-transform: scale(1.2); + cursor: pointer; + transform: scale(1.2); +} + +.key-features-section .col-md-4 { + border-right: 1px solid #fff; +} + +.key-features-section .col-md-4:last-child { + border-right: none; +} + +.single-key-feature > i { + font-size: 50px; + background: #fff; + height: 110px; + width: 110px; + border-radius: 100%; + line-height: 110px; + -webkit-transition: all .3s; + transition: all .3s; + -webkit-transform: scale(1); + transform: scale(1); +} + +.single-key-feature-details { +} + +.single-key-feature-details > h3 { + font-size: 20px; + font-weight: 600; + color: #222; + line-height: 32px; + margin-top: 30px; + margin-bottom: 7px; +} + +.single-key-feature-details > p { + color: #777; + font-size: 16px; + font-weight: 500; + line-height: 24px; + margin: 0; +} + +.features-section { + padding: 140px 0 90px; +} + +.demo-section { + padding: 5em 0 5em; +} + +.section-title { + margin-bottom: 50px; +} + +.section-title > h2 { + font-size: 40px; + font-weight: 300; + color: #222; + text-transform: capitalize; + margin-bottom: 17px; + margin-top: 0; +} + +.section-title > p { + font-size: 16px; + line-height: 26px; + font-weight: 400; + color: #8a8a8a; +} + +.section-description { + font-size: 16px; + line-height: 26px; + font-weight: 400; + color: #8a8a8a; +} + +.single-feature { + margin-bottom: 50px; +} + +.single-feature:hover i { + -webkit-transform: scale(1.2); + transform: scale(1.2); +} + +.single-feature > i { + font-size: 30px; + -webkit-transition: all .3s; + transition: all .3s; + -webkit-transform: scale(1); + transform: scale(1); +} + +.single-feature > h4 { + margin-left: 55px; + font-size: 22px; + font-weight: 500; + color: #484848; + margin-top: 0; +} + +.single-feature > p { + margin-left: 55px; + font-size: 15px; + line-height: 24px; + font-weight: 500; + color: #929292; +} + + +.single-key-feature > i { + color: #0cb4ce; +} + +.single-feature > i { + color: #0cb4ce; +} + + +/* . SERVICE SECTION STYLES +-------------------------------------------------*/ +.service-section { + + background-size: cover; + padding: 140px 0 70px; +} + +.single-service { + background: rgba(33,39,49,0.7); + // background: rgba(0,69,90,0.7); + padding: 40px 30px 10px; + margin-bottom: 50px; +} + +.single-service > i { + font-size: 36px; + height: 80px; + width: 80px; + color: #fff; + border-radius: 50%; + line-height: 80px; + -webkit-transition: all .3s; + transition: all .3s; + position: relative; + top: 0; + opacity: 1; +} + +.single-service:hover i { + top: -40px; + opacity: 0; +} + +.single-service:hover h4 { + top: -90px; +} + +.single-service:hover p { + top: -90px; +} + +.single-service:hover a { + top: -50px; + opacity: 1; +} + +.single-service > h4 { + color: #fff; + font-size: 20px; + text-transform: capitalize; + margin-top: 40px; + font-weight: 500; + -webkit-transition: all .3s; + transition: all .3s; + position: relative; + top: 0; +} + +.single-service > p { + font-size: 15px; + line-height: 24px; + color: #b2b8bc; + -webkit-transition: all .3s; + transition: all .3s; + position: relative; + top: 0; +} + +.single-service > a { + font-size: 14px; + text-transform: uppercase; + font-weight: 700; + background: #fff; + color: #4e4e4e; + padding: 6px 40px; + border-radius: 50px; + -webkit-transition: all .3s; + transition: all .3s; + position: relative; + top: -10px; + opacity: 0; +} + + +.single-service > i { + background: #0cb4ce; +} + +.single-service > a:hover { + background: #0cb4ce; +} + + +/* ====== Switch for Subscriptions ====== */ +.switch-field { + font-family: "Lucida Grande", Tahoma, Verdana, sans-serif; + padding: 0px; + overflow: hidden; +} + +.switch-title { + margin-bottom: 6px; +} + +.switch-field input { + position: absolute !important; + clip: rect(0, 0, 0, 0); + height: 1px; + width: 1px; + border: 0; + overflow: hidden; +} + +.switch-field label { + float: left; +} + +.switch-field label { + display: inline-block; + width: 50%; + background-color: #e4e4e4; + color: rgba(0, 0, 0, 0.6); + font-size: 14px; + font-weight: normal; + text-align: center; + text-shadow: none; + padding: 6px 6px; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px rgba(255, 255, 255, 0.1); + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -ms-transition: all 0.1s ease-in-out; + -o-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; +} + +.switch-field label:hover { + cursor: pointer; +} + +.switch-field input:checked + label { + /* background-color: #A5DC86; */ + color: white; + background-color: #5CB85C; + -webkit-box-shadow: none; + box-shadow: none; +} + +.switch-field label:first-of-type { + border-radius: 4px 0 0 4px; +} + +.switch-field label:last-of-type { + border-radius: 0 4px 4px 0; +} + +@media screen and (max-width: 600px) { + #tip_message { + visibility: hidden; + clear: both; + display: none; + } +} + + +/* Right align on larger screens */ +.right-align-on-sm { + text-align: left; +} +@media screen and (min-width: 768px){ + .right-align-on-sm { + text-align: right; + } +} + + +/* Landing page, Sale sign */ +#sale { + font-size: 1em; + font-weight:bold; + text-align: center; + /* Rotate div */ + /* + -ms-transform: rotate(-35deg); + -webkit-transform: rotate(-35deg); + transform: rotate(-35deg); + */ +} + +/* Right align on non-mobile */ +.full-screen-right-alignment { + text-align: right; +} +/* Any time we go below a reasonable size */ +@media (max-width: 767px) { + .full-screen-right-alignment { + text-align: center; + } +} + +/* Rotating words */ +#sentence-wrapper{ + width: 80%; + position: relative; + margin: 110px auto 0 auto; + font-family: Georgia, serif; + padding: 10px; +} +.sentence{ + margin: 0; + text-align: center; + text-shadow: 1px 1px 1px rgba(255,255,255,0.8); +} +.sentence span{ + text-align:center; + font-size: 100%; + font-weight: normal; +} +.words{ + text-align:center; + display: inline-block; + text-indent: 0px; +} +.words-rotate #words-rotate-placeholder { + position: relative; + opacity:0; + font-size:100%; +} +.words-rotate span { + position: absolute; + text-align:left; + opacity: 0; + overflow: none; + + color: #ff817a; + -webkit-animation: rotateWord 18s linear infinite 0s; + -moz-animation: rotateWord 18s linear infinite 0s; + -o-animation: rotateWord 18s linear infinite 0s; + -ms-animation: rotateWord 18s linear infinite 0s; + animation: rotateWord 18s linear infinite 0s; +} +.words-rotate span:nth-child(2) { + -webkit-animation-delay: 3s; + -moz-animation-delay: 3s; + -o-animation-delay: 3s; + -ms-animation-delay: 3s; + animation-delay: 3s; + color: #05b264; +} +.words-rotate span:nth-child(3) { + -webkit-animation-delay: 6s; + -moz-animation-delay: 6s; + -o-animation-delay: 6s; + -ms-animation-delay: 6s; + animation-delay: 6s; + color: #45d1d8; +} +.words-rotate span:nth-child(4) { + -webkit-animation-delay: 9s; + -moz-animation-delay: 9s; + -o-animation-delay: 9s; + -ms-animation-delay: 9s; + animation-delay: 9s; + color: #ff7bb6; +} +.words-rotate span:nth-child(5) { + -webkit-animation-delay: 12s; + -moz-animation-delay: 12s; + -o-animation-delay: 12s; + -ms-animation-delay: 12s; + animation-delay: 12s; + color: #d39641; +} +.words-rotate span:nth-child(6) { + -webkit-animation-delay: 15s; + -moz-animation-delay: 15s; + -o-animation-delay: 15s; + -ms-animation-delay: 15s; + animation-delay: 15s; + color: #9b6b9d; +} +@-webkit-keyframes rotateWord { + 0% { opacity: 0; } + 2% { opacity: 0; -webkit-transform: translateY(-30px); } + 5% { opacity: 1; -webkit-transform: translateY(0px);} + 17% { opacity: 1; -webkit-transform: translateY(0px); } + 20% { opacity: 0; -webkit-transform: translateY(30px); } + 80% { opacity: 0; } + 100% { opacity: 0; } +} +@-moz-keyframes rotateWord { + 0% { opacity: 0; } + 2% { opacity: 0; -moz-transform: translateY(-30px); } + 5% { opacity: 1; -moz-transform: translateY(0px);} + 17% { opacity: 1; -moz-transform: translateY(0px); } + 20% { opacity: 0; -moz-transform: translateY(30px); } + 80% { opacity: 0; } + 100% { opacity: 0; } +} +@-o-keyframes rotateWord { + 0% { opacity: 0; } + 2% { opacity: 0; -o-transform: translateY(-30px); } + 5% { opacity: 1; -o-transform: translateY(0px);} + 17% { opacity: 1; -o-transform: translateY(0px); } + 20% { opacity: 0; -o-transform: translateY(30px); } + 80% { opacity: 0; } + 100% { opacity: 0; } +} +@-ms-keyframes rotateWord { + 0% { opacity: 0; } + 2% { opacity: 0; -ms-transform: translateY(-30px); } + 5% { opacity: 1; -ms-transform: translateY(0px);} + 17% { opacity: 1; -ms-transform: translateY(0px); } + 20% { opacity: 0; -ms-transform: translateY(30px); } + 80% { opacity: 0; } + 100% { opacity: 0; } +} +@keyframes rotateWord { + 0% { opacity: 0; } + 2% { opacity: 0; transform: translateY(-30px); } + 5% { opacity: 1; transform: translateY(0px);} + 17% { opacity: 1; transform: translateY(0px); } + 20% { opacity: 0; transform: translateY(30px); } + 80% { opacity: 0; } + 100% { opacity: 0; } +} + + +/* ==== BUTTONS for SELECTION */ +button:focus { outline: none;} +::-webkit-input-placeholder { color: #fff;} +:-moz-placeholder { color: #fff;} +::-moz-placeholder { color: #fff;} +:-ms-input-placeholder { color: #fff;} + + +/*========== Buttons ==========*/ + +.background-color, +.select-icon { + background: #58e5ff; + background: -moz-linear-gradient(45deg, #027C93 0%, #2ed6f5 100%); + background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#027C93), color-stop(100%,#2ed6f5)); + background: -webkit-linear-gradient(45deg, #027C93 0%, #2ed6f5 100%); + background: -o-linear-gradient(45deg, #027C93 0%,#2ed6f5 100%); + background: -ms-linear-gradient(45deg, #027C93 0%,#2ed6f5 100%); + background: linear-gradient(45deg, #027C93 0%,#2ed6f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#027C93', endColorstr='#2ed6f5',GradientType=1 ); +} + +.background-color input[type="submit"] { + color: #92278f; +} +.background-color { + color: #fff; +} + +.select-icon { + width: 126px; + height: 126px; + margin: 0 auto 20px auto; + padding: 5px; + border-radius: 100%; +} + +.select-icon span { + width: 100%; + height: 100%; + border: 2px solid #fff; + color: #fff; + border-radius: 100%; + font-size: 40px; + line-height: 112px; +} +.select-icon:hover { + background: #2ed6f5; + cursor: pointer; +} + +.select-icon .selected { + background: #2ed6f5; +} + + +/* Test Navbar Styles */ + +.navbar { + border: none; + background: #fff; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + -webkit-transition: all 400ms ease-in-out; + -moz-transition: all 400ms ease-in-out; + -o-transition: all 400ms ease-in-out; + -ms-transition: all 400ms ease-in-out; + transition: all 400ms ease-in-out; + padding: 20px 0 5px; + margin-bottom: 25px !important; +} + +.scroll-fixed-nav { + padding: 15px 0 12px; + box-shadow: 0 2px 30px rgba(0,0,0,0.2); +} + +.navbar-brand { + padding: 7px 15px; +} + +.navbar-brand-logo { + padding: 7px 15px; + height: auto; + width: auto; + max-width: 225px; + max-height: 50px; +} + +.navbar-brand-logo:hover { + background: rgba(0,0,0,0); +} + +.navbar-default .navbar-nav>li>a { + font-size: 15px; + font-weight: 700; + text-transform: uppercase; + color: #4C4C4C; + padding: 15px 20px; +} + +#featured-sign-up { + margin-left: 20px; + margin-right: 15px; +} + +#featured-sign-up > a { + padding: 11px 25px !important; + color: #fff !important; + -moz-border-radius: 50px; + -webkit-border-radius: 50px; + border-radius: 50px; + margin-top: 4px; +} + +a:hover,a:focus { + text-decoration: none; + outline: none; +} + +#featured-sign-up > a { + background: #0cb4ce; +} + +#featured-sign-up > a:hover { + background: #56D5E8; +} + +/* iFrame mobile */ +/* FROM: http://benmarshall.me/responsive-iframes/ */ +.iframe-container { + position: relative; + height: 0; + overflow: hidden; +} + +/* 16x9 Aspect Ratio */ +.iframe-container-16x9 { + padding-bottom: 56.25%; +} + +/* 4x3 Aspect Ratio */ +.iframe-container-4x3 { + padding-bottom: 75%; +} + +.iframe-container iframe { + position: absolute; + top:0; + left: 0; + width: 100%; + height: 100%; +} + +.recipient_card { + cursor: pointer; + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); + transition: 0.3s; + width: 100%; + border-radius: 5px; + padding: 7px 7px 7px 7px; + margin-bottom: 25px; +} + +.recipient_card.senator_card { + box-shadow: 0 4px 8px 0 rgba(0, 40, 209, 0.2); +} + +.recipient_card.representative_card { + box-shadow: 0 4px 8px 0 rgba(209, 0, 0, 0.2); +} + +.recipient_card.governor_card { + box-shadow: 0 4px 8px 0 rgba(218, 165, 35, 0.5) +} + +.recipient_card:hover { + box-shadow: 0 10px 16px 0 rgba(0,0,0,0.2); +} + +.recipient_card.senator_card:hover { + box-shadow: 0 6px 10px 2px rgba(0, 40, 209, 0.2); +} + +.recipient_card.representative_card:hover { + box-shadow: 0 6px 10px 2px rgba(209, 0, 0, 0.2); +} + +.recipient_card.governor_card:hover { + box-shadow: 0 6px 10px 2px rgba(218, 165, 35, 0.2); +} + +.recipient_card.selected_card { + box-shadow: 0px 6px 10px 10px rgba(0,0,0,0.5); + border-style: solid; + border-width: 1px; + border-color: rgba(128, 204, 171, 1); +} + +.recipient_card.senator_card.selected_card { + border-color: rgba(0, 40, 209, 0.4); + box-shadow: 4px 8px 10px 8px rgba(0, 40, 209, 0.2); +} + +.recipient_card.representative_card.selected_card { + border-color: rgba(209, 0, 0, 0.4); + box-shadow: 4px 8px 10px 8px rgba(209, 0, 0, 0.2); +} + +.recipient_card.governor_card.selected_card { + border-color: rgba(218, 165, 32, 0.8); + box-shadow: 4px 8px 10px 8px rgba(218, 165, 32, 0.5); +} + + +.recipient_card.senator_card.selected_card:hover { + box-shadow: 2px 6px 8px 6px rgba(0, 40, 209, 0.2); +} + +.recipient_card.representative_card.selected_card:hover { + box-shadow: 2px 6px 8px 6px rgba(209, 0, 0, 0.2); +} + +.recipient_card.governor_card.selected_card:hover { + box-shadow: 2px 6px 8px 6px rgba(218, 165, 32, 0.5); +} + + +.btn.btn-info.send_button.selected_card { + box-shadow: 0px 2px 4px 4px rgba(0,0,0,0.4); + border-width: 2px; +} + + +/* Variables */ +* { + box-sizing: border-box; +} + +.payment-form { + width: 30vw; + min-width: 500px; + align-self: center; + box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), + 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); + border-radius: 7px; + padding: 40px; +} + +input { + border-radius: 6px; + margin-bottom: 6px; + padding: 12px; + border: 1px solid rgba(50, 50, 93, 0.1); + height: 44px; + font-size: 16px; + width: 100%; + background: white; +} + +.result-message { + line-height: 22px; + font-size: 16px; +} + +.result-message a { + color: rgb(89, 111, 214); + font-weight: 600; + text-decoration: none; +} + +.hidden { + display: none; +} + +#card-error { + color: rgb(105, 115, 134); + text-align: left; + font-size: 13px; + line-height: 17px; + margin-top: 12px; +} + +#card-element { + border-radius: 4px 4px 0 0 ; + padding: 12px; + border: 1px solid rgba(50, 50, 93, 0.1); + height: 44px; + width: 100%; + background: white; +} + +#payment-request-button { + margin-bottom: 32px; +} + +/* Buttons and links */ +#submit { + background: #5cb85c; + color: #ffffff; + font-family: Arial, sans-serif; + border-radius: 0 0 4px 4px; + border: 0; + padding: 12px 16px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + display: block; + transition: all 0.2s ease; + box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); + width: 100%; +} +#submit:hover { + filter: contrast(115%); +} +#submit:disabled { + opacity: 0.5; + cursor: default; +} + + +/* spinner/processing state, errors */ +.spinner, +.spinner:before, +.spinner:after { + border-radius: 50%; +} +.spinner { + color: #ffffff; + font-size: 22px; + text-indent: -99999px; + margin: 0px auto; + position: relative; + width: 20px; + height: 20px; + box-shadow: inset 0 0 0 2px; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); +} +.spinner:before, +.spinner:after { + position: absolute; + content: ""; +} +.spinner:before { + width: 10.4px; + height: 20.4px; + background: #5cb85c; + border-radius: 20.4px 0 0 20.4px; + top: -0.2px; + left: -0.2px; + -webkit-transform-origin: 10.4px 10.2px; + transform-origin: 10.4px 10.2px; + -webkit-animation: loading 2s infinite ease 1.5s; + animation: loading 2s infinite ease 1.5s; +} +.spinner:after { + width: 10.4px; + height: 10.2px; + background: #5cb85c; + border-radius: 0 10.2px 10.2px 0; + top: -0.1px; + left: 10.2px; + -webkit-transform-origin: 0px 10.2px; + transform-origin: 0px 10.2px; + -webkit-animation: loading 2s infinite ease; + animation: loading 2s infinite ease; +} + +@-webkit-keyframes loading { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes loading { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@media only screen and (max-width: 600px) { + form { + width: 80vw; + } +} + +.moderate-risk-highlight span{ + background-color: #F6E7A3; + padding: 0.2em 0.2em; +} +.moderate-risk-highlight small{ + color: #A28910; + font-weight: bold; +} + +.high-risk-highlight span{ + background-color: #F8C5D0; + padding: 0.2em 0.2em; +} +.high-risk-highlight small{ + color: #F08199; + font-weight: bold; +} diff --git a/app/assets/stylesheets/emails.scss b/app/assets/stylesheets/emails.scss new file mode 100644 index 0000000..c3ccff7 --- /dev/null +++ b/app/assets/stylesheets/emails.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Emails controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/faxes.scss b/app/assets/stylesheets/faxes.scss new file mode 100644 index 0000000..fcc604a --- /dev/null +++ b/app/assets/stylesheets/faxes.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Faxes controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/letters.scss b/app/assets/stylesheets/letters.scss new file mode 100644 index 0000000..511c658 --- /dev/null +++ b/app/assets/stylesheets/letters.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Letters controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/password_resets.scss b/app/assets/stylesheets/password_resets.scss new file mode 100644 index 0000000..9d6c8b6 --- /dev/null +++ b/app/assets/stylesheets/password_resets.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the PasswordResets controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/payments.scss b/app/assets/stylesheets/payments.scss new file mode 100644 index 0000000..6d026a7 --- /dev/null +++ b/app/assets/stylesheets/payments.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the payments controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/pdf.scss b/app/assets/stylesheets/pdf.scss new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/assets/stylesheets/pdf.scss @@ -0,0 +1 @@ + diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss new file mode 100644 index 0000000..6907cec --- /dev/null +++ b/app/assets/stylesheets/posts.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Posts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/recipients.scss b/app/assets/stylesheets/recipients.scss new file mode 100644 index 0000000..4a3b0d0 --- /dev/null +++ b/app/assets/stylesheets/recipients.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Recipients controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/scaffolds.scss b/app/assets/stylesheets/scaffolds.scss new file mode 100644 index 0000000..bb2597f --- /dev/null +++ b/app/assets/stylesheets/scaffolds.scss @@ -0,0 +1,65 @@ +body { + background-color: #fff; + color: #333; + margin: 33px; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; } + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; } + +a { + color: #000; } + +a:visited { + color: #666; } + +a:hover { + color: #fff; + background-color: #000; } + +th { + padding-bottom: 5px; } + +td { + padding: 0 5px 7px; } + +div.field, +div.actions { + margin-bottom: 10px; } + +#notice { + color: green; } + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; } + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px 7px 0; + margin-bottom: 20px; + background-color: #f0f0f0; } + +#error_explanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px -7px 0; + background-color: #c00; + color: #fff; } + +#error_explanation ul li { + font-size: 12px; + list-style: square; } + +label { + display: block; } diff --git a/app/assets/stylesheets/send_communication.scss b/app/assets/stylesheets/send_communication.scss new file mode 100644 index 0000000..a6d11b5 --- /dev/null +++ b/app/assets/stylesheets/send_communication.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the SendCommunication controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/senders.scss b/app/assets/stylesheets/senders.scss new file mode 100644 index 0000000..6f0c212 --- /dev/null +++ b/app/assets/stylesheets/senders.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Senders controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000..2ee3060 --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/static_pages.scss b/app/assets/stylesheets/static_pages.scss new file mode 100644 index 0000000..2e073c0 --- /dev/null +++ b/app/assets/stylesheets/static_pages.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the StaticPages controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss new file mode 100644 index 0000000..efc2526 --- /dev/null +++ b/app/assets/stylesheets/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..0ff5442 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/account_activations_controller.rb b/app/controllers/account_activations_controller.rb new file mode 100644 index 0000000..0806ef6 --- /dev/null +++ b/app/controllers/account_activations_controller.rb @@ -0,0 +1,16 @@ +class AccountActivationsController < ApplicationController + + def edit + user = User.find_by(email: params[:email]) + if user && !user.activated? && user.authenticated?(:activation, params[:id]) + user.activate + log_in user + flash[:success] = "Account activated!" + redirect_to user + else + flash[:danger] = "Invalid activation link" + redirect_to root_url + end + end + +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..ce04ed1 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,14 @@ +class ApplicationController < ActionController::Base + include SessionsHelper + include UsersHelper + + # Confirms a logged-in user. + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url + end + end + +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/emails_controller.rb b/app/controllers/emails_controller.rb new file mode 100644 index 0000000..e322789 --- /dev/null +++ b/app/controllers/emails_controller.rb @@ -0,0 +1,76 @@ +class EmailsController < ApplicationController + before_action :logged_in_user, except: [:new, :create] + before_action :logged_in_admin, except: [:new, :create] + before_action :set_email, only: [:show, :edit, :update, :destroy] + + # GET /emails + # GET /emails.json + def index + @emails = Email.all + end + + # GET /emails/1 + # GET /emails/1.json + def show + end + + # GET /emails/new + def new + @email = Email.new + end + + # GET /emails/1/edit + def edit + end + + # POST /emails + # POST /emails.json + def create + @email = Email.new(email_params) + + respond_to do |format| + if @email.save + format.html { redirect_to @email, notice: 'Email was successfully created.' } + format.json { render :show, status: :created, location: @email } + else + format.html { render :new } + format.json { render json: @email.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /emails/1 + # PATCH/PUT /emails/1.json + def update + respond_to do |format| + if @email.update(email_params) + format.html { redirect_to @email, notice: 'Email was successfully updated.' } + format.json { render :show, status: :ok, location: @email } + else + format.html { render :edit } + format.json { render json: @email.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /emails/1 + # DELETE /emails/1.json + def destroy + @email.destroy + respond_to do |format| + format.html { redirect_to emails_url, notice: 'Email was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_email + @email = Email.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def email_params + params.require(:email).permit(:email_address, :sender_id, :recipient_id, :letter_id, :payment_id) + end +end diff --git a/app/controllers/faxes_controller.rb b/app/controllers/faxes_controller.rb new file mode 100644 index 0000000..07e0704 --- /dev/null +++ b/app/controllers/faxes_controller.rb @@ -0,0 +1,76 @@ +class FaxesController < ApplicationController + before_action :logged_in_user, except: [:new, :create] + before_action :logged_in_admin, except: [:new, :create] + before_action :set_fax, only: [:show, :edit, :update, :destroy] + + # GET /faxes + # GET /faxes.json + def index + @faxes = Fax.all + end + + # GET /faxes/1 + # GET /faxes/1.json + def show + end + + # GET /faxes/new + def new + @fax = Fax.new + end + + # GET /faxes/1/edit + def edit + end + + # POST /faxes + # POST /faxes.json + def create + @fax = Fax.new(fax_params) + + respond_to do |format| + if @fax.save + format.html { redirect_to @fax, notice: 'Fax was successfully created.' } + format.json { render :show, status: :created, location: @fax } + else + format.html { render :new } + format.json { render json: @fax.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /faxes/1 + # PATCH/PUT /faxes/1.json + def update + respond_to do |format| + if @fax.update(fax_params) + format.html { redirect_to @fax, notice: 'Fax was successfully updated.' } + format.json { render :show, status: :ok, location: @fax } + else + format.html { render :edit } + format.json { render json: @fax.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /faxes/1 + # DELETE /faxes/1.json + def destroy + @fax.destroy + respond_to do |format| + format.html { redirect_to faxes_url, notice: 'Fax was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_fax + @fax = Fax.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def fax_params + params.require(:fax).permit(:number_fax, :sender_id, :recipient_id, :letter_id, :payment_id) + end +end diff --git a/app/controllers/letters_controller.rb b/app/controllers/letters_controller.rb new file mode 100644 index 0000000..bf4acdc --- /dev/null +++ b/app/controllers/letters_controller.rb @@ -0,0 +1,232 @@ +class LettersController < ApplicationController + before_action :logged_in_user, except: [:show, :find_policy] + before_action :logged_in_admin, except: [:show, :find_policy] + before_action :set_letter, only: [:show, :edit, :update, :destroy] + + # GET /letters + # GET /letters.json + def index + @letters = Letter.all + end + + # GET /letters/1 + # GET /letters/1.json + def show + + @template = false + if params.has_key?(:template) + @template = true + end + + if @letter.sentiment > 0.5 + @sentiment = 'strongly support' + elsif @letter.sentiment > 0 + @sentiment = 'support' + elsif @letter.sentiment == 0 + @sentiment = 'am indifferent to' + elsif @letter.sentiment < -0.5 + @sentiment = 'strongly oppose' + elsif @letter.sentiment < 0 + @sentiment = 'oppose' + end + + @recipient_name = "[[ insert government official name ]]" + recipient_name = nil + if params.has_key?(:recipient_name) + @recipient_name = params[:recipient_name] + recipient_name = Recipient.where(name: @recipient_name) + end + + @sender_name = "[[ insert senders name ]]" + if params.has_key?(:sender_name) + @sender_name = params[:sender_name] + end + + @sender_region = "" + + if params.has_key?(:sender_county) + @sender_region += params[:sender_county].downcase.sub(/county/, '').titleize + @sender_region += ' County' + if params.has_key?(:sender_state) + @sender_region += ", "+params[:sender_state] + end + @sender_region += '
' + end + + @recipient_position = "" + if params.has_key?(:recipient_position) + @recipient_position = params[:recipient_position].titleize + end + + recipient_district = nil + recipient_level = nil + if params.has_key?(:sender_district) + @sender_region += params[:sender_district].to_i.ordinalize + + recipient_district = Recipient.where(district: params[:sender_district]) + + region = " Congressional District" + level = "United States" + if params.has_key?(:recipient_level) \ + and params[:recipient_level] == "state" + if params.has_key?(:recipient_position) + region = " " + @recipient_position + " District" + end + level = "State" + + recipient_level = Recipient.where(level: params[:recipient_level]) + end + + @sender_region += region + end + + recipient_state = nil + if params.has_key?(:sender_state) + + recipient_state = Recipient.where(state: params[:sender_state]) + + @sender_region += ", " + params[:sender_state] + if level == "State" + level = params[:sender_state] + " " + level + end + end + + @recipient_location = "" + if @recipient_position.present? + if @recipient_position == "Senator" + @recipient_location = level + " Senate" + elsif @recipient_position == "Representative" + @recipient_location = level + " House" + end + end + + # Select recipient, if possible + recipient = Recipient + if recipient_name.present? + recipient = recipient.and(recipient_name) + end + if recipient_district.present? + recipient = recipient.and(recipient_district) + end + if recipient_level.present? + recipient = recipient.and(recipient_level) + end + if recipient_state.present? + recipient = recipient.and(recipient_state) + end + + # Ensure only one recipient for accuracy + @recipient = nil + if (not recipient.is_a?(Class)) and recipient.length == 1 + @recipient = recipient.first + end + + + @sender_region_verified = "" + if params.has_key?(:sender_verified) + @sender_region_verified = "
" + if ActiveModel::Type::Boolean.new.cast(params[:sender_verified]) + @sender_region_verified += "✔ Verified " + else + @sender_region_verified += "✖ Not Verifiably " + end + @sender_region_verified += "in District via Billing Address" + @sender_region_verified += "" + end + + respond_to do |format| + format.html + format.pdf do + render template: "letters/letter.html.erb", + pdf: "Letter ID: #{@letter.id}" + end + end + end + + # GET /letters/new + def new + @letter = Letter.new + end + + # GET /letters/1/edit + def edit + end + + # POST /letters + # POST /letters.json + def create + @letter = Letter.new(letter_params) + + respond_to do |format| + if @letter.save + format.html { redirect_to @letter, notice: 'Letter was successfully created.' } + format.json { render :show, status: :created, location: @letter } + else + format.html { render :new } + format.json { render json: @letter.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /letters/1 + # PATCH/PUT /letters/1.json + def update + respond_to do |format| + if @letter.update(letter_params) + format.html { redirect_to @letter, notice: 'Letter was successfully updated.' } + format.json { render :show, status: :ok, location: @letter } + else + format.html { render :edit } + format.json { render json: @letter.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /letters/1 + # DELETE /letters/1.json + def destroy + @letter.destroy + respond_to do |format| + format.html { redirect_to letters_url, notice: 'Letter was successfully destroyed.' } + format.json { head :no_content } + end + end + + def find_policy + category = params[:category] + sentiment = params[:sentiment] + policy_or_law = params[:policy_or_law] + + @policy_or_laws = Letter + + if category.present? + @policy_or_laws = @policy_or_laws.where( + "category LIKE lower(?)", "%#{category}%") + end + if policy_or_law.present? + @policy_or_laws = @policy_or_laws.where( + "policy_or_law LIKE lower(?)", "%#{policy_or_law}%") + end + if sentiment.present? + @policy_or_laws = @policy_or_laws.where(sentiment: sentiment.to_f) + end + + @policy_or_laws = @policy_or_laws.distinct.pluck( + :id, :sentiment, :category, :policy_or_law) + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_letter + @letter = Letter.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def letter_params + params.require(:letter) + .permit(:category, :policy_or_law, :tags, :sentiment, :body, + :target_level, :target_state) + .merge(user_id: current_user.id) + end + +end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb new file mode 100644 index 0000000..6f94bc3 --- /dev/null +++ b/app/controllers/password_resets_controller.rb @@ -0,0 +1,68 @@ +class PasswordResetsController < ApplicationController + before_action :get_user, only: [:edit, :update] + before_action :valid_user, only: [:edit, :update] + before_action :check_expiration, only: [:edit, :update] + + def new + end + + def create + @user = User.find_by(email: params[:password_reset][:email].downcase) + if @user + @user.create_reset_digest + @user.send_password_reset_email + flash[:info] = "Email sent with password reset instructions" + redirect_to root_url + else + flash.now[:danger] = "Email address not found" + render 'new' + end + end + + def edit + end + + def update + if params[:user][:password].empty? + @user.errors.add(:password, "can't be empty") + render 'edit' + elsif @user.update(user_params) + reset_session + log_in @user + @user.update_attribute(:reset_digest, nil) + flash[:success] = "Password has been reset." + redirect_to @user + else + render 'edit' + end + end + + private + + def user_params + params.require(:user).permit(:password, :password_confirmation) + end + + # Before filters + + def get_user + @user = User.find_by(email: params[:email]) + end + + # Confirms a valid user. + def valid_user + unless (@user && @user.activated? && + @user.authenticated?(:reset, params[:id])) + redirect_to root_url + end + end + + # Checks expiration of reset token. + def check_expiration + if @user.password_reset_expired? + flash[:danger] = "Password reset has expired." + redirect_to new_password_reset_url + end + end + +end diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb new file mode 100644 index 0000000..e25a8c3 --- /dev/null +++ b/app/controllers/payments_controller.rb @@ -0,0 +1,42 @@ +class PaymentsController < ApplicationController + + # POST endpoint for stripe webhook + # If your controller accepts requests other than Stripe webhooks, + # you'll probably want to use `protect_from_forgery` to add CSRF + # protection for your application. But don't forget to exempt + # your webhook route! + protect_from_forgery except: :webhook + def webhook + payload = request.body.read + event = nil + + begin + event = Stripe::Event.construct_from( + JSON.parse(payload, symbolize_names: true) + ) + rescue JSON::ParserError => e + # Invalid payload + render status: :bad_request, nothing: true + return + end + + # Handle the event + case event.type + when 'payment_intent.succeeded' + payment_intent = event.data.object # contains a Stripe::PaymentIntent + # Then define and call a method to handle the successful payment intent. + # handle_payment_intent_succeeded(payment_intent) + require 'json' + when 'payment_method.attached' + payment_method = event.data.object # contains a Stripe::PaymentMethod + # Then define and call a method to handle the successful attachment of a PaymentMethod. + # handle_payment_method_attached(payment_method) + # ... handle other event types + else + puts "Unhandled event type: #{event.type}" + end + + render status: :ok, json: "Success: No Failures" + end + +end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 0000000..180cb32 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,76 @@ +class PostsController < ApplicationController + before_action :logged_in_user, except: [:new, :create] + before_action :logged_in_admin, except: [:new, :create] + before_action :set_post, only: [:show, :edit, :update, :destroy] + + # GET /posts + # GET /posts.json + def index + @posts = Post.all + end + + # GET /posts/1 + # GET /posts/1.json + def show + end + + # GET /posts/new + def new + @post = Post.new + end + + # GET /posts/1/edit + def edit + end + + # POST /posts + # POST /posts.json + def create + @post = Post.new(post_params) + + respond_to do |format| + if @post.save + format.html { redirect_to @post, notice: 'Post was successfully created.' } + format.json { render :show, status: :created, location: @post } + else + format.html { render :new } + format.json { render json: @post.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /posts/1 + # PATCH/PUT /posts/1.json + def update + respond_to do |format| + if @post.update(post_params) + format.html { redirect_to @post, notice: 'Post was successfully updated.' } + format.json { render :show, status: :ok, location: @post } + else + format.html { render :edit } + format.json { render json: @post.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /posts/1 + # DELETE /posts/1.json + def destroy + @post.destroy + respond_to do |format| + format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_post + @post = Post.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def post_params + params.require(:post).permit(:address_line_1, :address_line_2, :address_city, :address_state, :address_zipcode, :sender_id, :recipient_id, :letter_id, :payment_id) + end +end diff --git a/app/controllers/recipients_controller.rb b/app/controllers/recipients_controller.rb new file mode 100644 index 0000000..89f8cd7 --- /dev/null +++ b/app/controllers/recipients_controller.rb @@ -0,0 +1,229 @@ +require 'net/http' +class RecipientsController < ApplicationController + before_action :logged_in_user, except: [:show, :lookup] + before_action :logged_in_admin, except: [:show, :lookup] + before_action :set_recipient, only: [:show, :edit, :update, :destroy] + + # GET /recipients + # GET /recipients.json + def index + @recipients = Recipient.all + end + + # GET /recipients/1 + # GET /recipients/1.json + def show + end + + # GET /recipients/new + def new + @recipient = Recipient.new + end + + # GET /recipients/1/edit + def edit + end + + # POST /recipients + # POST /recipients.json + def create + @recipient = Recipient.new(recipient_params) + + respond_to do |format| + if @recipient.save + format.html { redirect_to @recipient, notice: 'Recipient was successfully created.' } + format.json { render :show, status: :created, location: @recipient } + else + format.html { render :new } + format.json { render json: @recipient.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /recipients/1 + # PATCH/PUT /recipients/1.json + def update + respond_to do |format| + if @recipient.update(recipient_params) + format.html { redirect_to @recipient, notice: 'Recipient was successfully updated.' } + format.json { render :show, status: :ok, location: @recipient } + else + format.html { render :edit } + format.json { render json: @recipient.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /recipients/1 + # DELETE /recipients/1.json + def destroy + @recipient.destroy + respond_to do |format| + format.html { redirect_to recipients_url, notice: 'Recipient was successfully destroyed.' } + format.json { head :no_content } + end + end + + # GET /govlookup + # GET /govlookup.json + def lookup + + obj = get_congressional_districts() + + @address_accuracy = obj['results'][0]['accuracy'] + + if @address_accuracy > 0.7 + @accuracy_class = "" + elsif @address_accuracy > 0.5 + @accuracy_class = "moderate-risk-highlight" + else + @accuracy_class = "high-risk-highlight" + end + + district_info = obj['results'][0]['fields']['congressional_districts'][0] + district_number = district_info['district_number'] + + @sender_line_1 = '' + if obj['results'][0]['address_components'].has_key? 'number' + @sender_line_1 = obj['results'][0]['address_components']['number'] + end + if obj['results'][0]['address_components'].has_key? 'formatted_street' + @sender_line_1 += ' ' + obj['results'][0]['address_components']['formatted_street'] + end + + @address_line_2 = '' + if obj['results'][0]['address_components'].has_key? 'secondaryunit' + @address_line_2 += obj['results'][0]['address_components']['secondaryunit'] + end + if obj['results'][0]['address_components'].has_key? 'secondarynumber' + @address_line_2 += ' ' + obj['results'][0]['address_components']['secondarynumber'] + end + + @sender_city = obj['results'][0]['address_components']['city'] + @sender_state = obj['results'][0]['address_components']['state'] + @sender_zipcode = obj['results'][0]['address_components']['zip'] + @sender_country = obj['results'][0]['address_components']['country'] + + @sender_address = obj['input']['formatted_address'] + @sender_county = obj['results'][0]['address_components']['county'].sub(/ County/, '') + @sender_district_federal = district_number + + level = 'federal' + legislators = district_info['current_legislators'] + + @recipients = [] + for legislator in legislators + + position = legislator['type'] + name = legislator['bio']['first_name'] + ' ' + legislator['bio']['last_name'] + number_phone = legislator['contact']['phone'].gsub(/-/, '').to_i + contact_form = legislator['contact']['url'] + if legislator['contact']['contact_form'].present? + contact_form = legislator['contact']['contact_form'] + end + + address_line_1 = legislator['contact']['address'].split(' Washington')[0] + address_city = 'Washington' + address_state = 'DC' + address_zipcode = legislator['contact']['address'].split(' DC ')[1] + + # Find and use + recipient = Recipient.find_by( + :name => name, :position => position, :level => level, :state => @sender_state) + + # If cannot find matching record, create new record + if not recipient.present? + recipient = Recipient.create( + :name => name, + :position => position, + :level => level, + :district => district_number, + :state => @sender_state, + :number_phone => number_phone, + :contact_form => contact_form, + :address_line_1 => address_line_1, + :address_city => address_city, + :address_state => address_state, + :address_zipcode => address_zipcode) + end + + if not recipient.retired + @recipients.push(recipient) + end + end + + level = 'state' + state_district = obj['results'][0]['fields']['state_legislative_districts'] + + # Find state govenor + recipient = Recipient.find_by( + :position => 'governor', :state => @sender_state, :retired => false) + if recipient.present? + @recipients.push(recipient) + end + + if state_district.has_key? 'house' + district = state_district['house']['district_number'] + @sender_district_representative = district + recipient = Recipient.find_by( + :position => 'representative', :district => district, + :level => level, :state => @sender_state, :retired => false) + if recipient.present? + @recipients.push(recipient) + end + end + + if state_district.has_key? 'senate' + district = state_district['senate']['district_number'] + @sender_district_senate = district + recipient = Recipient.find_by( + :position => 'senator', :district => district, + :level => level, :state => @sender_state, :retired => false) + if recipient.present? + @recipients.push(recipient) + end + end + + respond_to do |format| + format.html { + if params[:layout] == 'false' + render :layout => false + end + } + format.json + end + + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_recipient + @recipient = Recipient.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def recipient_params + params.require(:recipient).permit(:name, :position, :level, :district, :state, :number_fax, :number_phone, :email_address, :contact_form, :address_line_1, :address_line_2, :address_city, :address_state, :address_zipcode, :retired) + end + + def get_congressional_districts + url = 'https://api.geocod.io/v1.6/geocode' + + address = "" + fields = "cd" # congressional district + fields += ",stateleg" # state congressional district + api_key = "" + if ENV.has_key?("GEOCODIO_API_KEY") + api_key = ENV["GEOCODIO_API_KEY"] + end + + url += '?q=' + params[:address] + url += '&fields=' + fields + url += '&api_key=' + api_key + + uri = URI(url) + obj = Net::HTTP.get(uri) + + return ActiveSupport::JSON.decode(obj) + end +end diff --git a/app/controllers/send_communication_controller.rb b/app/controllers/send_communication_controller.rb new file mode 100644 index 0000000..136b426 --- /dev/null +++ b/app/controllers/send_communication_controller.rb @@ -0,0 +1,261 @@ +require 'stripe' +require 'clicksend_client' +require 'json' + +class SendCommunicationController < ApplicationController + skip_before_action :verify_authenticity_token + + def send_communication + + Stripe.api_key = ENV['STRIPE_SECRET_KEY_VOCALVOTERS'] + + method = params['method'] + payment_id = params['payment']['id'] + payment = Stripe::PaymentIntent.retrieve(payment_id) + + # clicksend setup authorization + ClickSendClient.configure do |config| + # Configure HTTP basic authorization: BasicAuth + config.username = ENV['CLICKSEND_USERNAME'] + config.password = ENV['CLICKSEND_PASSWORD'] + end + + # If paid, then register communication + if payment['charges']['data'][0]['paid'] + billing_zipcode = payment['charges']['data'][0]['billing_details']['address']['postal_code'] + recipients = params['recipients'] # should be list of id's + + sender_line_1 = params['sender']['line_1'].presence || '' + sender_line_2 = params['sender']['line_2'].presence || '' + sender_city = params['sender']['city'].presence || '' + sender_state = params['sender']['state'].presence || '' + sender_zipcode = params['sender']['zipcode'].presence || '' + + # Create sender + sender = Sender.find_by(email: sender_params[:email]) + if not sender.present? + sender = Sender.create!(sender_params) + end + + letter_id = letter_params[:id] + sender_id = sender[:id] + verified = billing_zipcode.to_i == sender.zipcode.to_i + + # Find recipients + recipients = Recipient.find(params['recipients']) + + recipients.each { |recipient| + + # Fill letter template + letter_url = '/letters/'+letter_id.to_s+'.pdf?' + letter_url += 'recipient_name='+recipient.name+'&' + letter_url += 'recipient_position='+recipient.position+'&' + letter_url += 'recipient_level='+recipient.level+'&' + letter_url += 'sender_name='+sender.name+'&' + letter_url += 'sender_state='+sender.state+'&' + letter_url += 'sender_district='+recipient.district+'&' + letter_url += 'sender_verified='+verified.to_s + + address_zipcode = recipient[:address_zipcode] + to_fax_number = recipient[:number_fax] + + # For development, utilize the free options + if Rails.env.development? + letter_url = "https://vocalvoters.com" + letter_url + address_zipcode = "11111" + to_fax_number = "61261111111" + end + + case method + when "priority" + + letter_url += "&template=true" + return_address_id = create_return_address(sender.name, sender_line_1, + sender_line_2, sender_city, + sender_state, sender_zipcode) + + success_flag = send_post( + letter_url, recipient[:name], return_address_id, + recipient[:address_line_1], recipient[:address_line_2], + recipient[:address_city], recipient[:address_state], + address_zipcode, priority_flag=1) + + Post.create!(address_line_1: recipient[:address_line_1], + address_line_2: recipient[:address_line_2], + address_city: recipient[:address_city], + address_state: recipient[:address_state], + address_zipcode: address_zipcode, + priority: true, + sender: sender, + recipient: recipient, + letter: Letter.find_by(id: letter_id), + payment: payment_id, + success: success_flag) + + when "letter" + + letter_url += "&template=true" + return_address_id = create_return_address(sender.name, sender_line_1, + sender_line_2, sender_city, + sender_state, sender_zipcode) + + success_flag = send_post( + letter_url, recipient[:name], return_address_id, + recipient[:address_line_1], recipient[:address_line_2], + recipient[:address_city], recipient[:address_state], + address_zipcode, priority_flag=0) + + Post.create!(address_line_1: recipient[:address_line_1], + address_line_2: recipient[:address_line_2], + address_city: recipient[:address_city], + address_state: recipient[:address_state], + address_zipcode: address_zipcode, + priority: false, + sender: sender, + recipient: recipient, + letter: Letter.find_by(id: letter_id), + payment: payment_id, + success: success_flag) + + when "fax" + + from = "VocalVoters" + source_method = "rails" + from_email = sender.email + + success_flag = send_fax(letter_url, to_fax_number, from, + source_method, from_email) + + Fax.create!(number_fax: to_fax_number, + sender: sender, + recipient: recipient, + letter: Letter.find_by(id: letter_id), + payment: payment_id, + success: success_flag) + + when "email" + # Do nothing ATM + # send_email(name, email) + end + + } + + end + + render status: :ok, json: "Success: No Failures" + end + + private + + def sender_params + params.require(:sender).permit(:name, :email, :zipcode, + :county, :district, :state) + end + + def letter_params + + # "id": "1", + # "category": "bakeries", + # "sentiment": "Very Supportive", + # "policy_or_law": "deserts" + + params.require(:letter).permit(:id, :category, + :sentiment, :policy_or_law) + end + + def send_email(name, email) + # https://developers.clicksend.com/docs/rest/v3/#send-email + return false + end + + + def send_fax(letter_url, to_fax_number, from, source_method, from_email) + api_instance = ClickSendClient::FAXApi.new + + # FaxMessageCollection | FaxMessageCollection model + fax_messages = ClickSendClient::FaxMessageCollection.new( + "file_url": letter_url, + messages: [ + ClickSendClient::FaxMessage.new( + "to": to_fax_number, + "source": source_method, + "from": from, + "country": 'US', + "from_email": from_email, + "source": source_method + ) + ] + ) + + begin + # Send a fax using supplied supported file-types. + result = api_instance.fax_send_post(fax_messages) + return true + rescue ClickSendClient::ApiError => e + puts "Exception when calling FAXApi->fax_send_post: #{e.response_body}" + end + return false + end + + def create_return_address(name, line_1, line_2, city, state, zipcode) + api_instance = ClickSendClient::PostReturnAddressApi.new + + # Address | Address model + return_address = ClickSendClient::Address.new( + "address_postal_code": zipcode, + "address_country": "US", + "address_line_1": line_1, + "address_state": state, + "address_name": name, + "address_line_2": line_2, + "address_city": city + ) + + begin + # Create post return address + result = api_instance.post_return_addresses_post(return_address) + result = JSON.parse(result) + return result['data']['return_address_id'] + rescue ClickSendClient::ApiError => e + puts "Exception calling PostReturnAddressApi->post_return_addresses_post: #{e.response_body}" + end + return false + end + + def send_post(letter_url, name, return_address_id, address_line_1, address_line_2, + address_city, address_state, address_zipcode, priority_flag=0) + + # PostLetter | PostLetter model + api_instance = ClickSendClient::PostLetterApi.new + post_letter = ClickSendClient::PostLetter.new( + "file_url": letter_url, + "recipients": [ + { + "return_address_id": return_address_id, + "schedule": 0, + "address_name": name, + "address_line_1": address_line_1, + "address_line_2": address_line_2, + "address_city": address_city, + "address_state": address_state, + "address_postal_code": address_zipcode, + "address_country": "US" + } + ], + "priority_post": priority_flag, + "template_used": 1, + "duplex": 0, + "colour": 0 + ) + + begin + # Send post letter + result = api_instance.post_letters_send_post(post_letter) + return true + rescue ClickSendClient::ApiError => e + puts "Exception calling PostLetterApi->post_letters_send_post: #{e.response_body}" + end + return false + end + +end diff --git a/app/controllers/senders_controller.rb b/app/controllers/senders_controller.rb new file mode 100644 index 0000000..2bd9035 --- /dev/null +++ b/app/controllers/senders_controller.rb @@ -0,0 +1,91 @@ +class SendersController < ApplicationController + before_action :logged_in_user, except: [:new, :create] + before_action :logged_in_admin, except: [:new, :create] + before_action :set_sender, only: [:show, :edit, :update, :destroy] + + # GET /senders + # GET /senders.json + def index + @senders = Sender.all + end + + # GET /senders/1 + # GET /senders/1.json + def show + end + + # GET /senders/new + def new + @sender = Sender.new + end + + # GET /senders/1/edit + def edit + end + + # POST /senders + # POST /senders.json + def create + @sender = Sender.find_by( + :name => sender_params[:name], + :email => sender_params[:email], + :zipcode => sender_params[:zipcode], + :county => sender_params[:county], + :district => sender_params[:district], + :state => sender_params[:state]) + + if not @sender.present? + @sender = Sender.new(sender_params) + respond_to do |format| + if @sender.save + format.html { redirect_to @sender, notice: 'Sender was successfully created.' } + format.json { render :json => @sender.id } + else + format.html { render :new } + format.json { render json: @sender.errors, status: :unprocessable_entity } + end + end + else + respond_to do |format| + format.html { redirect_to @sender, notice: 'Sender already existed.' } + format.json { render :json => @sender.id } + end + end + end + + # PATCH/PUT /senders/1 + # PATCH/PUT /senders/1.json + def update + respond_to do |format| + if @sender.update(sender_params) + format.html { redirect_to @sender, notice: 'Sender was successfully updated.' } + format.json { render :show, status: :ok, location: @sender } + else + format.html { render :edit } + format.json { render json: @sender.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /senders/1 + # DELETE /senders/1.json + def destroy + @sender.destroy + respond_to do |format| + format.html { redirect_to senders_url, notice: 'Sender was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_sender + @sender = Sender.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def sender_params + params.require(:sender) + .permit(:name, :email, :zipcode, :county, :district, :state) + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..c6664e8 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,32 @@ +class SessionsController < ApplicationController + + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + if user.activated? + forwarding_url = session[:forwarding_url] + reset_session + params[:session][:remember_me] == '1' ? remember(user) : forget(user) + log_in user + redirect_to forwarding_url || user + else + message = "Account not activated. " + message += "Check your email for the activation link." + flash[:warning] = message + redirect_to root_url + end + else + flash.now[:danger] = 'Invalid email/password combination' + render 'new' + end + end + + def destroy + log_out if logged_in? + redirect_to root_url + end + +end diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb new file mode 100644 index 0000000..3f42354 --- /dev/null +++ b/app/controllers/static_pages_controller.rb @@ -0,0 +1,132 @@ +require 'stripe' + +class StaticPagesController < ApplicationController + skip_before_action :verify_authenticity_token, :only => [:create_payment_intent] + + def home + @stripe_pk = ENV['STRIPE_PUBLISHABLE_KEY_VOCALVOTERS'] + + @sentiment_val_map = { + -1.0 => ["Very Opposed", -1.0], + -0.5 => ["Opposed", -0.5], + 0.0 => ["Neutral", 0.0], + 0.5 => ["Support", 0.5], + 1.0 => ["Very Supportive", 1.0] + } + @sentiment_phrase_map = { + "Very Opposed" => ["Very Opposed", -1.0], + "Opposed" => ["Opposed", -0.5], + "Neutral" => ["Neutral", 0.0], + "Support" => ["Support", 0.5], + "Very Supportive" => ["Very Supportive", 1.0] + } + + @selected_category = params[:category] + @options_category = []+Letter.distinct.pluck('lower(category)') + + @selected_sentiment = nil + @options_sentiment = [] + + @selected_policy_or_law = nil + @options_policy_or_law = [] + + + # Don't bother preloading unless category selected + if @selected_category.present? + + #### DETERMINE THE SENTIMENT IF AVAILABLE ##### + + available_sentiment = Letter.where( + "category LIKE lower(?)", "%#{@selected_category}%" + ).distinct.order(sentiment: :desc).pluck(:sentiment) + available_sentiment.each { |sentiment| + @options_sentiment.append(@sentiment_val_map[sentiment]) + } + + if params.has_key? :sentiment + if is_number?(params[:sentiment]) + @selected_sentiment = params[:sentiment].to_f + elsif @sentiment_phrase_map.has_key? params[:sentiment] + @selected_sentiment = @sentiment_phrase_map[params[:sentiment]][1] + end + end + + + #### DETERMINE THE POLICY_OR_LAW IF AVAILABLE ##### + if @selected_sentiment.present? + @options_policy_or_law = Letter.where( + sentiment: @selected_sentiment + ).where( + "category LIKE lower(?)", "%#{@selected_category}%" + ).pluck(:policy_or_law, :id) + + if params.has_key? :policy_or_law + if is_number? params[:policy_or_law] + @selected_policy_or_law = params[:policy_or_law].to_i + else + @selected_policy_or_law = []+Letter.where( + sentiment: @selected_sentiment + ).where( + "category LIKE lower(?)", "%#{@selected_category}%" + ).where( + "policy_or_law LIKE lower(?)", "%#{params[:policy_or_law]}%" + ).pluck(:id) + + end + end + end + end + + end + + def help + end + + def about + end + + def contact + end + + def create_payment_intent + Stripe.api_key = ENV['STRIPE_SECRET_KEY_VOCALVOTERS'] + + # Create a PaymentIntent with amount and currency + # TODO: Make more robust + payment_intent = Stripe::PaymentIntent.create({ + amount: calculate_order_amount(params['item'], params['count']), + currency: 'usd', + payment_method_types: ['card'], + receipt_email: params['email'] + }) + + @payment_info = payment_intent['client_secret'] + + respond_to do |format| + format.json { @payment_info } + end + end + + private + + def calculate_order_amount(item, count) + + if not count.present? or not item.present? + return 0 + end + + if ['Priority Mail'].include? item + return 500 * count + elsif ['Letter', 'Letters'].include? item + return 300 * count + elsif ['Fax', 'Faxes'].include? item + return 200 * count + elsif ['Email', 'Emails'].include? item + return 100 * count + end + end + + def is_number? string + true if Float(string) rescue false + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..252a096 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,91 @@ +class UsersController < ApplicationController + before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :correct_user, only: [:edit, :update] + before_action :logged_in_admin, only: [:index, :destroy] + before_action :admin_user, only: :destroy + + + def index + @users = User.where(activated: true).paginate(page: params[:page]) + end + + def show + @user = User.find(params[:id]) + redirect_to root_url and return unless @user.activated? + + respond_to do |format| + format.html + format.pdf do + render template: "users/show.html.erb", + pdf: "User ID: #{@user.id}" + end + end + + end + + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + @user.send_activation_email + flash[:info] = "Please check your email to activate your account." + redirect_to root_url + else + render 'new' + end + end + + def edit + @user = User.find(params[:id]) + end + + def update + @user = User.find(params[:id]) + if @user.update(user_params) + flash[:success] = "Profile updated" + redirect_to @user + else + render 'edit' + end + end + + def destroy + User.find(params[:id]).destroy + flash[:success] = "User deleted" + redirect_to users_url + end + + + private + + def user_params + params.require(:user).permit(:name, :email, :password, + :password_confirmation) + end + + # Before filters + + # Confirms a logged-in user. + def logged_in_user + unless logged_in? + store_location + flash[:danger] = "Please log in." + redirect_to login_url + end + end + + # Confirms the correct user. + def correct_user + @user = User.find(params[:id]) + redirect_to(root_url) unless current_user?(@user) + end + + # Confirms an admin user. + def admin_user + redirect_to(root_url) unless current_user.admin? + end + +end diff --git a/app/helpers/account_activations_helper.rb b/app/helpers/account_activations_helper.rb new file mode 100644 index 0000000..c4d5ac7 --- /dev/null +++ b/app/helpers/account_activations_helper.rb @@ -0,0 +1,2 @@ +module AccountActivationsHelper +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..24950b7 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,13 @@ +module ApplicationHelper + + # Returns the full title on a per-page basis. + def full_title(page_title = '') + base_title = "VocalVoters" + if page_title.empty? + base_title + else + page_title + " | " + base_title + end + end + +end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb new file mode 100644 index 0000000..b4dc6ec --- /dev/null +++ b/app/helpers/emails_helper.rb @@ -0,0 +1,2 @@ +module EmailsHelper +end diff --git a/app/helpers/faxes_helper.rb b/app/helpers/faxes_helper.rb new file mode 100644 index 0000000..415e266 --- /dev/null +++ b/app/helpers/faxes_helper.rb @@ -0,0 +1,2 @@ +module FaxesHelper +end diff --git a/app/helpers/letters_helper.rb b/app/helpers/letters_helper.rb new file mode 100644 index 0000000..cad8d62 --- /dev/null +++ b/app/helpers/letters_helper.rb @@ -0,0 +1,2 @@ +module LettersHelper +end diff --git a/app/helpers/password_resets_helper.rb b/app/helpers/password_resets_helper.rb new file mode 100644 index 0000000..0c9d96e --- /dev/null +++ b/app/helpers/password_resets_helper.rb @@ -0,0 +1,2 @@ +module PasswordResetsHelper +end diff --git a/app/helpers/payments_helper.rb b/app/helpers/payments_helper.rb new file mode 100644 index 0000000..c1b884f --- /dev/null +++ b/app/helpers/payments_helper.rb @@ -0,0 +1,2 @@ +module PaymentsHelper +end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb new file mode 100644 index 0000000..a7b8cec --- /dev/null +++ b/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/app/helpers/recipients_helper.rb b/app/helpers/recipients_helper.rb new file mode 100644 index 0000000..0595f08 --- /dev/null +++ b/app/helpers/recipients_helper.rb @@ -0,0 +1,2 @@ +module RecipientsHelper +end diff --git a/app/helpers/send_communication_helper.rb b/app/helpers/send_communication_helper.rb new file mode 100644 index 0000000..fe9efdd --- /dev/null +++ b/app/helpers/send_communication_helper.rb @@ -0,0 +1,2 @@ +module SendCommunicationHelper +end diff --git a/app/helpers/senders_helper.rb b/app/helpers/senders_helper.rb new file mode 100644 index 0000000..3a64dfb --- /dev/null +++ b/app/helpers/senders_helper.rb @@ -0,0 +1,2 @@ +module SendersHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..023d70b --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,64 @@ +module SessionsHelper + + # Logs in the given user. + def log_in(user) + session[:user_id] = user.id + # Guard against session replay attacks. + # https://binarysolo.chapter24.blog/avoiding-session-replay-attacks-in-rails/ + session[:session_token] = user.session_token + end + + # Remembers a user in a persistent session. + def remember(user) + user.remember + cookies.permanent.encrypted[:user_id] = user.id + cookies.permanent[:remember_token] = user.remember_token + end + + + # Returns the user corresponding to the remember token cookie. + def current_user + if (user_id = session[:user_id]) + user = User.find_by(id: user_id) + if user && session[:session_token] == user.session_token + @current_user = user + end + elsif (user_id = cookies.encrypted[:user_id]) + user = User.find_by(id: user_id) + if user && user.authenticated?(:remember, cookies[:remember_token]) + log_in user + @current_user = user + end + end + end + + # Returns true if the given user is the current user. + def current_user?(user) + user && user == current_user + end + + # Returns true if the user is logged in, false otherwise. + def logged_in? + !current_user.nil? + end + + # Forgets a persistent session. + def forget(user) + user.forget + cookies.delete(:user_id) + cookies.delete(:remember_token) + end + + # Logs out the current user. + def log_out + forget(current_user) + reset_session + @current_user = nil + end + + # Stores the URL trying to be accessed. + def store_location + session[:forwarding_url] = request.original_url if request.get? + end + +end diff --git a/app/helpers/static_pages_helper.rb b/app/helpers/static_pages_helper.rb new file mode 100644 index 0000000..2d63e79 --- /dev/null +++ b/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..449f533 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,42 @@ +module UsersHelper + + # Confirms an admin user. + def admin_user + unless current_user.admin? + store_location + flash[:danger] = "Inappropriate permissions." + redirect_to root_url + end + end + + # Disables access to other users, unless site admin + # Fail with no error message + # TODO: Send to 404 page + def logged_in_admin + if params.has_key?(:id) and params[:controller] == "user" + # If has id tag, check current user or admin user, if not -> redirect + if (current_user != User.find(params[:id]) && !current_user.admin?) + redirect_to root_url + end + else + # If no id tag, check if admin, if not -> redirect + if !current_user or !current_user.admin? + flash[:warning] = "Only administrators can view that information, contact us." + redirect_back(fallback_location: root_path) + end + end + end + + # Find user by token + def find_user_from_auth_token + authenticate_with_http_token do |token, options| + api_key = ApiKey.find_by_access_token(token) + if api_key.present? + @user = User.find_by_id(api_key.user_id) + return true + end + end + return false + end + +end diff --git a/app/javascript/channels/consumer.js b/app/javascript/channels/consumer.js new file mode 100644 index 0000000..8ec3aad --- /dev/null +++ b/app/javascript/channels/consumer.js @@ -0,0 +1,6 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command. + +import { createConsumer } from "@rails/actioncable" + +export default createConsumer() diff --git a/app/javascript/channels/index.js b/app/javascript/channels/index.js new file mode 100644 index 0000000..0cfcf74 --- /dev/null +++ b/app/javascript/channels/index.js @@ -0,0 +1,5 @@ +// Load all the channels within this directory and all subdirectories. +// Channel files must be named *_channel.js. + +const channels = require.context('.', true, /_channel\.js$/) +channels.keys().forEach(channels) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js new file mode 100644 index 0000000..db8541c --- /dev/null +++ b/app/javascript/packs/application.js @@ -0,0 +1,15 @@ +// This file is automatically compiled by Webpack, along with any other files +// present in this directory. You're encouraged to place your actual application logic in +// a relevant structure within app/javascript and only use these pack files to reference +// that code so it'll be compiled. + +import Rails from "@rails/ujs" +import Turbolinks from "turbolinks" +import * as ActiveStorage from "@rails/activestorage" +import "channels" +import "jquery" +import "bootstrap" + +Rails.start() +Turbolinks.start() +ActiveStorage.start() diff --git a/app/javascript/packs/landing_page.js b/app/javascript/packs/landing_page.js new file mode 100644 index 0000000..2adbf7e --- /dev/null +++ b/app/javascript/packs/landing_page.js @@ -0,0 +1,650 @@ +function validate_email(email) { + var re = /\S+@\S+\.\S+/; + return re.test(email); +} + +function validate_form(ids_to_validate){ + safe_flag = true + for (const element of ids_to_validate){ + + containerElement = document.getElementById(element+'_container'); + containerValue = document.getElementById(element).value; + + // Check if there's no data in given field + if ((containerValue.length==0) + || (element=='email' && !validate_email(containerValue))) { + containerElement.classList.add('has-error'); + safe_flag = false + } else { + containerElement.classList.add('has-success'); + } + + containerElement.addEventListener('change', function() { + this.classList.remove('has-error'); + this.classList.remove('has-success'); + }) + + containerElement.onselect = function() { + this.classList.remove('has-error'); + this.classList.remove('has-success'); + } + } + + return safe_flag +} + +/* If enter, update */ +$('#address,#name').keyup(function(e) { + if (e.which == 13) { + form_element_ids = ['name', 'email', 'address'] + if(validate_form(form_element_ids)){ + find_legislators() + } + } +}) + +$('#legislator_button').click(function(e) { + // 'category', 'sentiment', 'policy_or_law', + form_element_ids = ['name', 'email', 'address'] + if(validate_form(form_element_ids)){ + find_legislators() + } +}) + +$("#category").change(function(e) { + + policy_or_law = $("#policy_or_law"); + if (policy_or_law.prop("selectedIndex", 0).val() != '') { + policy_or_law.empty() + }else{ + policy_or_law.find('option').not(':first').remove(); + } + + sentiment = $("#sentiment"); + if (sentiment.prop("selectedIndex", 0).val() != '') { + sentiment.empty() + }else{ + sentiment.find('option').not(':first').remove(); + } + + category = $("#category").find(":selected").text() + + update_options(category); + +}) + +$("#policy_or_law").change(function(e) { + + /* + category_val = $("#category").find(":selected").text() + policy_or_law = $("#policy_or_law").find(":selected").text() + + sentiment = $("#sentiment"); + if (sentiment.prop("selectedIndex", 0).val() != '') { + sentiment.empty() + }else{ + sentiment.find('option').not(':first').remove(); + } + + update_options(category_val, sentiment_val=null, policy_or_law); + */ + + if(generate_concerns()) { + + // Display pdf & communication section + window.location.hash = "#communications_selection"; + } + +}) + +function generate_concerns() { + + form_element_ids = ['name', 'email', 'address'] + if(validate_form(form_element_ids)){ + + category = $('#category').find(":selected").attr('value'); + sentiment = $('#sentiment').find(":selected").val(); + policy_or_law = $('#policy_or_law').find(":selected").val(); + + if(!category){ return false } + if(!sentiment){ return false } + if(!policy_or_law){ return false } + + // Potentially error + letter_id = $('#policy_or_law').find(":selected").attr('value') + if (letter_id == '') { + letter_id = $('#category').find(":selected").attr('value') + } + + sender_name = document.getElementById('name').value; + sender_state = document.querySelector('#sender_state').getAttribute('value'); + selected_cards = document.getElementsByClassName('recipient_card selected_card'); + + recipient_id = selected_cards[0].getAttribute('value'); + + district = document.querySelector('#recipient_district_'+recipient_id).getAttribute('value'); + name = document.querySelector('#recipient_name_'+recipient_id).getAttribute('value'); + position = document.querySelector('#recipient_position_'+recipient_id).getAttribute('value'); + level = document.querySelector('#recipient_level_'+recipient_id).getAttribute('value'); + update_pdf(letter_id, sender_name, sender_state, + district, name, position, level); + + // Hide bottom buffer once communications_selection displayed + $("#concerns_selection_bottom_buffer").attr('style', 'display:none'); + $('#communications_selection').attr('style', 'display:block'); + + document.getElementById("share_button").onclick = function(){ + copyLetterToClipboard(); + } + + return true + } + + return false +} + +$("#sentiment").change(function(e) { + category_val = $("#category").find(":selected").text() + sentiment_val = $("#sentiment").find(":selected").val() + + policy_or_law = $("#policy_or_law"); + if (policy_or_law.prop("selectedIndex", 0).val() != '') { + policy_or_law.empty() + }else{ + policy_or_law.find('option').not(':first').remove(); + } + + update_options(category_val, sentiment_val, policy_or_law=null); +}) + +function create_sender(){ + sender_name = document.getElementById('name').value; + sender_email = document.getElementById('email').value; + sender_zipcode = document.querySelector('#sender_zipcode').getAttribute('value'); + sender_state = document.querySelector('#sender_state').getAttribute('value'); + sender_county = document.querySelector('#sender_county').getAttribute('value'); + sender_district_federal = document.querySelector('#sender_district_federal').getAttribute('value'); + + create_sender = '/sender' + $.post({ + url: create_sender, + data: { + 'name': sender_name, + 'email': sender_email, + 'zipcode': sender_zipcode, + 'state': sender_state, + 'county': sender_county, + 'district': sender_district_federal + }, + success: function(response) { + console.log(response) + } + }) + +} + +function find_legislators(){ + sender_name = document.getElementById('name').value; + sender_email = document.getElementById('email').value; + sender_address = document.getElementById('address').value; + lookup_url = '/govlookup?'+'name='+sender_name+'&address='+sender_address + lookup_url+= '&layout=false' + + $.ajax({ + url: lookup_url, + cache: false, + success: function(html){ + + $("#legislator_selection").html(html); + document.getElementById('legislator_button').disabled = false; + document.getElementById('legislator_button').innerText = "Lookup Legislators" + + attach_stripe_checkout_on_click(); + + sender_address = document.getElementById('sender_address').getAttribute('value') + sender_state = document.getElementById('sender_state').getAttribute('value') + sender_district_federal = document.getElementById('sender_district_federal').getAttribute('value') + window.location.hash = "#legislator_selection"; + + recipient_cards = document.getElementsByClassName('recipient_card'); + for (var i=0; i < recipient_cards.length; i++) { + if (i == 0) { + id = recipient_cards[i].getAttribute('value') + recipient_name = document.getElementById('recipient_name_'+id).getAttribute('value') + recipient_position = document.getElementById('recipient_position_'+id).getAttribute('value') + recipient_level = document.getElementById('recipient_level_'+id).getAttribute('value') + } + + recipient_cards[i].onclick = function(card) { + + id = document.getElementById(event.srcElement.id).getAttribute('value') + recipient_name = document.getElementById('recipient_name_'+id).getAttribute('value') + recipient_position = document.getElementById('recipient_position_'+id).getAttribute('value') + recipient_level = document.getElementById('recipient_level_'+id).getAttribute('value') + + /* Select the recipient cards */ + if (event.srcElement.classList.contains('selected_card')) { + event.srcElement.classList.remove('selected_card') + }else{ + event.srcElement.classList.add('selected_card') + } + + disableCommunications(); + + update_prices(); + + update_checkout_price(); + + $('#concerns_selection').attr('style', 'display:block'); + + generate_concerns(); + + } + } + } + }); + + document.getElementById('legislator_button').disabled = true; + document.getElementById('legislator_button').innerText = "Please wait..." +} + +function update_pdf(letter_id, sender_name, sender_state, sender_district, + recipient_name, recipient_position, recipient_level) { + + src_url = '/letters/' + letter_id + '.pdf?sender_name=' + sender_name; + src_url += '&sender_state='+sender_state; + src_url += '&sender_district='+sender_district; + + src_url += '&recipient_name='+recipient_name; + src_url += '&recipient_level='+recipient_level; + src_url += '&recipient_position='+recipient_position; + + src_url += '&sender_verified=true' + + $('#pdf_view').attr('src', src_url); + $('#pdf_view').attr('width', '100%'); + $('#pdf_view').attr('height', '780'); + $('#pdf_view').attr('value', letter_id); + $('#iframe_container').attr('style', 'display:block'); +} + +function update_options(category=null, sentiment=null, policy_or_law=null) { + + policy_url = '/find_policy.json?' + if (!!category) { + policy_url += 'category=' + category + } + if (!!sentiment) { + policy_url += '&sentiment=' + sentiment + } + if (!!policy_or_law) { + policy_url += '&policy_or_law=' + policy_or_law + } + + sentiment_map = {} + sentiment_map[-1] = "Very Opposed" + sentiment_map[-0.5] = "Opposed" + sentiment_map[0] = "Indifferent" + sentiment_map[0.5] = "Supportive" + sentiment_map[1] = "Very Supportive" + + $.ajax({ + url: policy_url, + cache: false, + success: function(policy_list){ + + policy_or_law_options = $("#policy_or_law"); + sentiment_options = $("#sentiment"); + + if (policy_or_law === null) { + options = policy_list.map(x => [x[0], x[3]]); + $.each(options, function(key, value) { + policy_or_law_options.append( + $('').val(value[0]).html(value[1]) + ); + }); + } + + if (sentiment === null) { + options = {} + for (var i in policy_list) { + options[policy_list[i][1]] = policy_list[i] + } + + $.each(options, function(key, value) { + sentiment_options.append( + $('').val(key).html( + sentiment_map[key] + ) + ); + }); + } + } + }); +} + +function update_prices() { + recipient_count = $('.recipient_card.selected_card').length; + if(recipient_count == 0){ + document.getElementById('payment_container').style.display = 'none'; + } + + document.getElementById('email_price').innerText= recipient_count * 1; + document.getElementById('fax_price').innerText= recipient_count * 2; + document.getElementById('letter_price').innerText= recipient_count * 3; + document.getElementById('priority_price').innerText= recipient_count * 5; +} + +function attach_stripe_checkout_on_click() { + $('.send_button').click(function(e) { + + send_buttons = document.getElementsByClassName("send_button"); + for (var i = 0; i < send_buttons.length; i++) { + send_buttons[i].classList.remove('selected_card'); + } + + id = e.currentTarget.id; + e.currentTarget.classList.add('selected_card'); + count = update_checkout_price(id); + load_stripe_checkout( + document.getElementById('communication_mode').innerText, + document.getElementById('email').value, + $('.recipient_card.selected_card').length + ); + + recipient_count = $('.recipient_card.selected_card').length; + if(recipient_count == 0){ + document.getElementById('payment_container').style.display = 'none'; + } + }) +} + +function update_checkout_price(id=null) { + + if (id == null) { + id = document.getElementById('communication_mode').getAttribute('value') + } + + recipient_count = $('.recipient_card.selected_card').length; + price = 0; + single_phrasing = ""; + multiple_phrasing = ""; + + if (id == 'email') { + price = document.getElementById('email_price').innerText; + single_phrasing = "Email"; + multiple_phrasing = "Emails"; + } else if (id == 'fax') { + price = document.getElementById('fax_price').innerText; + single_phrasing = "Fax"; + multiple_phrasing = "Faxes"; + } else if (id == 'letter') { + price = document.getElementById('letter_price').innerText; + single_phrasing = "Letter"; + multiple_phrasing = "Letters"; + } else if (id == 'priority') { + price = document.getElementById('priority_price').innerText; + single_phrasing = "Priority Mail"; + multiple_phrasing = "Priority Mail"; + } + + document.getElementById('price_to_send').innerText = '$'+price; + + if (recipient_count > 1) { + document.getElementById('communication_mode').innerText = multiple_phrasing; + } else { + document.getElementById('communication_mode').innerText = single_phrasing; + } + + document.getElementById('communication_mode').setAttribute("value", id) +} + +// Disable communication modes based on the available options +var disableCommunications = function() { + selected_cards = document.getElementsByClassName('recipient_card selected_card'); + + // communication_option = ['email', 'fax', 'letter', 'priority']; + communication_option = ['fax', 'letter', 'priority']; + + for (var j = 0; j < communication_option.length; j++) { + c_option = communication_option[j]; + document.querySelector('button#'+c_option).disabled = false; + } + + + for (var i = 0; i < selected_cards.length; i++) { + id = selected_cards[i].getAttribute('value'); + + for (var j = 0; j < communication_option.length; j++) { + c_option = communication_option[j]; + if(document.getElementById( + c_option+'_'+id).getAttribute('value').length == 0) { + document.querySelector('button#'+c_option).disabled = true; + if (document.querySelector('button#'+c_option) + .classList.contains("selected_card")){ + document.querySelector('button#'+c_option) + .classList.remove("selected_card"); + document.getElementById('payment_container').style.display = 'none'; + } + } + } + } + + // Always disable email for the moment + document.querySelector('button#email').disabled = true; +} + +function load_stripe_checkout(id=null, email=null, count=null) { + + document.getElementById('payment_container').style.display = 'block'; + window.location.hash = "#payment_container"; + + // A reference to Stripe.js initialized with your real test publishable API key. + stripe_pk = document.getElementById('stripe_pk').getAttribute('value'); + var stripe = Stripe(stripe_pk); + + // The items the customer wants to buy + // TODO: Confirm email as required + var purchase = { + item: id, + email: email, + count: count + }; + + // Disable the button until we have Stripe set up on the page + document.querySelector("#submit").disabled = true; + fetch("/create-payment-intent.json", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(purchase) + }) + .then(function(result) { + return result.json(); + }) + .then(function(data) { + + var elements = stripe.elements(); + + var style = { + base: { + color: "#32325d", + fontFamily: 'Arial, sans-serif', + fontSmoothing: "antialiased", + fontSize: "16px", + "::placeholder": { + color: "#32325d" + } + }, + invalid: { + fontFamily: 'Arial, sans-serif', + color: "#fa755a", + iconColor: "#fa755a" + } + }; + + var card = elements.create("card", { style: style }); + + // Stripe injects an iframe into the DOM + card.mount("#card-element"); + + card.on("change", function (event) { + // Disable the Pay button if there are no card details in the Element + document.querySelector("#submit").disabled = event.empty; + document.querySelector("#card-error").textContent = event.error ? event.error.message : ""; + }); + + var form = document.getElementById("payment-form"); + form.addEventListener("submit", function(event) { + event.preventDefault(); + + // Complete payment when the submit button is clicked + payWithCard(stripe, card, data.clientSecret); + }); + }); + + // Calls stripe.confirmCardPayment + // If the card requires authentication Stripe shows a pop-up modal to + // prompt the user to enter authentication details without leaving your page. + var payWithCard = function(stripe, card, clientSecret) { + method = document.querySelector('.send_button.selected_card').getAttribute('id'); + loading(true); + stripe + .confirmCardPayment(clientSecret, { + payment_method: { + card: card, + billing_details: { + name: document.querySelector('#name').value, + email: document.querySelector('#email').value, + address: { + postal_code: document.querySelector('#sender_zipcode').getAttribute('value') + } + }, + metadata: { + method: method, + letter_id: document.querySelector('#pdf_view').getAttribute('value') + } + }, + receipt_email: document.querySelector('#email').value + }) + .then(function(result) { + if (result.error) { + // Show error to your customer + showError(result.error.message); + } else { + // The payment succeeded! + sendCommunication(result.paymentIntent.id, method); + orderComplete(result.paymentIntent.id, method); + } + }); + }; + + /* ------- UI helpers ------- */ + + // Shows a success message when the payment is complete + var orderComplete = function(paymentIntentId) { + loading(false); + document.querySelector(".result-message").classList.remove("hidden"); + document.querySelector("#submit").classList.add("hidden"); + document.querySelector("#card-element").classList.add("hidden"); + document.querySelector("#submit").disabled = true; + document.getElementById('share_container').style.display = 'block'; + }; + + // Show the customer the error from Stripe if their card fails to charge + var showError = function(errorMsgText) { + loading(false); + var errorMsg = document.querySelector("#card-error"); + errorMsg.textContent = errorMsgText; + setTimeout(function() { + errorMsg.textContent = ""; + }, 4000); + }; + + // Show a spinner on payment submission + var loading = function(isLoading) { + if (isLoading) { + // Disable the button and show a spinner + document.querySelector("#submit").disabled = true; + document.querySelector("#spinner").classList.remove("hidden"); + document.querySelector("#button-text").classList.add("hidden"); + } else { + document.querySelector("#submit").disabled = false; + document.querySelector("#spinner").classList.add("hidden"); + document.querySelector("#button-text").classList.remove("hidden"); + } + }; +} + +var sendCommunication = function(paymentIntentId, method) { + data = { + 'method': method, + 'sender': { + 'name': document.querySelector('#name').value, + 'email': document.querySelector('#email').value, + 'line_1': document.querySelector('#sender_line_1').getAttribute('value'), + 'line_2': document.querySelector('#sender_line_2').getAttribute('value'), + 'city': document.querySelector('#sender_city').getAttribute('value'), + 'state': document.querySelector('#sender_state').getAttribute('value'), + 'zipcode': document.querySelector('#sender_zipcode').getAttribute('value'), + 'country': document.querySelector('#sender_country').getAttribute('value'), + 'county': document.querySelector('#sender_county').getAttribute('value'), + 'district_federal': document.querySelector('#sender_district_federal').getAttribute('value'), + 'district_state_senate': document.querySelector('#sender_district_senate').getAttribute('value'), + 'district_state_representative': document.querySelector('#sender_district_representative').getAttribute('value'), + }, + 'recipients': [], + 'letter': { + 'id': document.querySelector('#pdf_view').getAttribute('value'), + 'category': $("#category").find(":selected").text(), + 'sentiment': $("#sentiment").find(":selected").text(), + 'policy_or_law': $("#policy_or_law").find(":selected").text() + }, + 'payment': { + 'id': paymentIntentId + } + } + + selected_cards = document.getElementsByClassName('selected_card') + for (const element of selected_cards){ + if(element.getAttribute('value') != null){ + data['recipients'].push(element.getAttribute('value')); + } + } + + send_communication_url = '/send_communication' + $.post({ + url: send_communication_url, + cache: false, + data: data + }) + + /* done - Sender: name, email, zipcode, county, district, state */ + /* done - Recipients: id */ + /* done - Letter: id, cat, sent, policy */ + /* done - PaymentId: log payment intent for later queries */ + /* send to URL for logging Communication: priority, letter, fax, email */ +} + +function copyLetterToClipboard(){ + + category = $('#category').find(":selected").attr('value'); + sentiment = $('#sentiment').find(":selected").text(); + policy_or_law = $('#policy_or_law').find(":selected").text(); + + var get_url = window.location; + var base_url = get_url.protocol + "//" + get_url.host + "?" + + var param_url = "" + + param_url += "category=" + encodeURIComponent(category) + "&"; + param_url += "sentiment=" + encodeURIComponent(sentiment) + "&"; + param_url += "policy_or_law=" + encodeURIComponent(policy_or_law); + + navigator.clipboard.writeText(base_url + param_url); + + document.getElementById("share_button").innerHTML = "Copied Link!" +} + + diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000..9f35d6e --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'noreply@vocalvoters.com' + layout 'mailer' +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 0000000..4cd38bd --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,13 @@ +class UserMailer < ApplicationMailer + + def account_activation(user) + @user = user + mail to: user.email, subject: "Account activation" + end + + def password_reset(user) + @user = user + mail to: user.email, subject: "Password reset" + end + +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000..10a4cba --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/email.rb b/app/models/email.rb new file mode 100644 index 0000000..543234a --- /dev/null +++ b/app/models/email.rb @@ -0,0 +1,6 @@ +class Email < ApplicationRecord + belongs_to :sender + belongs_to :recipient + belongs_to :letter + belongs_to :payment +end diff --git a/app/models/fax.rb b/app/models/fax.rb new file mode 100644 index 0000000..7739dd7 --- /dev/null +++ b/app/models/fax.rb @@ -0,0 +1,5 @@ +class Fax < ApplicationRecord + belongs_to :sender + belongs_to :recipient + belongs_to :letter +end diff --git a/app/models/letter.rb b/app/models/letter.rb new file mode 100644 index 0000000..d5e188b --- /dev/null +++ b/app/models/letter.rb @@ -0,0 +1,11 @@ +class Letter < ApplicationRecord + belongs_to :user + before_save :downcase_fields + + private + + def downcase_fields + self.category = category.downcase + self.tags = tags.downcase + end +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 0000000..4d12821 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,5 @@ +class Post < ApplicationRecord + belongs_to :sender + belongs_to :recipient + belongs_to :letter +end diff --git a/app/models/recipient.rb b/app/models/recipient.rb new file mode 100644 index 0000000..abf1da2 --- /dev/null +++ b/app/models/recipient.rb @@ -0,0 +1,2 @@ +class Recipient < ApplicationRecord +end diff --git a/app/models/sender.rb b/app/models/sender.rb new file mode 100644 index 0000000..81ba4af --- /dev/null +++ b/app/models/sender.rb @@ -0,0 +1,2 @@ +class Sender < ApplicationRecord +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..c531691 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,94 @@ +class User < ApplicationRecord + attr_accessor :remember_token, :activation_token, :reset_token + before_save :downcase_email + before_create :create_activation_digest + + validates :name, presence: true, length: { maximum: 50 } + + before_save { email.downcase! } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: true + has_secure_password + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true + + + # Returns the hash digest of the given string. + def User.digest(string) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : + BCrypt::Engine.cost + BCrypt::Password.create(string, cost: cost) + end + + # Returns a random token. + def User.new_token + SecureRandom.urlsafe_base64 + end + + # Remembers a user in the database for use in persistent sessions. + def remember + self.remember_token = User.new_token + update_attribute(:remember_digest, User.digest(remember_token)) + remember_digest + end + + # Returns true if the given token matches the digest. + def authenticated?(attribute, token) + digest = send("#{attribute}_digest") + return false if digest.nil? + BCrypt::Password.new(digest).is_password?(token) + end + + # Forgets a user. + def forget + update_attribute(:remember_digest, nil) + end + + # Returns a session token to prevent session hijacking. + # We reuse the remember digest for convenience. + def session_token + remember_digest || remember + end + + # Activates an account. + def activate + update_columns(activated: true, activated_at: Time.zone.now) + end + + # Sends activation email. + def send_activation_email + UserMailer.account_activation(self).deliver_now + end + + # Sets the password reset attributes. + def create_reset_digest + self.reset_token = User.new_token + update_columns(reset_digest: User.digest(reset_token), + reset_sent_at: Time.zone.now) + end + + # Sends password reset email. + def send_password_reset_email + UserMailer.password_reset(self).deliver_now + end + + # Returns true if a password reset has expired. + def password_reset_expired? + reset_sent_at < 2.hours.ago + end + + private + + # Converts email to all lower-case. + def downcase_email + self.email = email.downcase + end + + # Creates and assigns the activation token and digest. + def create_activation_digest + self.activation_token = User.new_token + self.activation_digest = User.digest(activation_token) + end + +end diff --git a/app/views/emails/_email.json.jbuilder b/app/views/emails/_email.json.jbuilder new file mode 100644 index 0000000..0a2abd6 --- /dev/null +++ b/app/views/emails/_email.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! email, :id, :email_address, :sender_id, :recipient_id, :letter_id, :payment_id, :created_at, :updated_at +json.url email_url(email, format: :json) diff --git a/app/views/emails/_form.html.erb b/app/views/emails/_form.html.erb new file mode 100644 index 0000000..8d4ab30 --- /dev/null +++ b/app/views/emails/_form.html.erb @@ -0,0 +1,42 @@ +<%= form_with(model: email) do |form| %> + <% if email.errors.any? %> +
+

<%= pluralize(email.errors.count, "error") %> prohibited this email from being saved:

+ +
    + <% email.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :email_address %> + <%= form.text_field :email_address %> +
+ +
+ <%= form.label :sender_id %> + <%= form.text_field :sender_id %> +
+ +
+ <%= form.label :recipient_id %> + <%= form.text_field :recipient_id %> +
+ +
+ <%= form.label :letter_id %> + <%= form.text_field :letter_id %> +
+ +
+ <%= form.label :payment_id %> + <%= form.text_field :payment_id %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/emails/edit.html.erb b/app/views/emails/edit.html.erb new file mode 100644 index 0000000..1431b41 --- /dev/null +++ b/app/views/emails/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Email

+ +<%= render 'form', email: @email %> + +<%= link_to 'Show', @email %> | +<%= link_to 'Back', emails_path %> diff --git a/app/views/emails/index.html.erb b/app/views/emails/index.html.erb new file mode 100644 index 0000000..de7f2fb --- /dev/null +++ b/app/views/emails/index.html.erb @@ -0,0 +1,37 @@ +

<%= notice %>

+ +

Emails

+ + + + + + + + + + + + + + + + <% @emails.each do |email| %> + + + + + + + + + + + + <% end %> + +
Email addressSenderRecipientLetterPaymentSuccess
<%= email.email_address %><%= email.sender_id %><%= email.recipient_id %><%= email.letter_id %><%= email.payment %><%= email.success %><%= link_to 'Show', email %><%= link_to 'Edit', edit_email_path(email) %><%= link_to 'Destroy', email, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Email', new_email_path %> diff --git a/app/views/emails/index.json.jbuilder b/app/views/emails/index.json.jbuilder new file mode 100644 index 0000000..70f812c --- /dev/null +++ b/app/views/emails/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @emails, partial: "emails/email", as: :email diff --git a/app/views/emails/new.html.erb b/app/views/emails/new.html.erb new file mode 100644 index 0000000..61c2c95 --- /dev/null +++ b/app/views/emails/new.html.erb @@ -0,0 +1,5 @@ +

New Email

+ +<%= render 'form', email: @email %> + +<%= link_to 'Back', emails_path %> diff --git a/app/views/emails/show.html.erb b/app/views/emails/show.html.erb new file mode 100644 index 0000000..8a3319a --- /dev/null +++ b/app/views/emails/show.html.erb @@ -0,0 +1,29 @@ +

<%= notice %>

+ +

+ Email address: + <%= @email.email_address %> +

+ +

+ Sender: + <%= @email.sender_id %> +

+ +

+ Recipient: + <%= @email.recipient_id %> +

+ +

+ Letter: + <%= @email.letter_id %> +

+ +

+ Payment: + <%= @email.payment_id %> +

+ +<%= link_to 'Edit', edit_email_path(@email) %> | +<%= link_to 'Back', emails_path %> diff --git a/app/views/emails/show.json.jbuilder b/app/views/emails/show.json.jbuilder new file mode 100644 index 0000000..ff416bd --- /dev/null +++ b/app/views/emails/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "emails/email", email: @email diff --git a/app/views/faxes/_fax.json.jbuilder b/app/views/faxes/_fax.json.jbuilder new file mode 100644 index 0000000..94b99e1 --- /dev/null +++ b/app/views/faxes/_fax.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! fax, :id, :number_fax, :sender_id, :recipient_id, :letter_id, :payment_id, :created_at, :updated_at +json.url fax_url(fax, format: :json) diff --git a/app/views/faxes/_form.html.erb b/app/views/faxes/_form.html.erb new file mode 100644 index 0000000..42d447a --- /dev/null +++ b/app/views/faxes/_form.html.erb @@ -0,0 +1,42 @@ +<%= form_with(model: fax) do |form| %> + <% if fax.errors.any? %> +
+

<%= pluralize(fax.errors.count, "error") %> prohibited this fax from being saved:

+ +
    + <% fax.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :number_fax %> + <%= form.number_field :number_fax %> +
+ +
+ <%= form.label :sender_id %> + <%= form.text_field :sender_id %> +
+ +
+ <%= form.label :recipient_id %> + <%= form.text_field :recipient_id %> +
+ +
+ <%= form.label :letter_id %> + <%= form.text_field :letter_id %> +
+ +
+ <%= form.label :payment_id %> + <%= form.text_field :payment_id %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/faxes/edit.html.erb b/app/views/faxes/edit.html.erb new file mode 100644 index 0000000..ab1d74a --- /dev/null +++ b/app/views/faxes/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Fax

+ +<%= render 'form', fax: @fax %> + +<%= link_to 'Show', @fax %> | +<%= link_to 'Back', faxes_path %> diff --git a/app/views/faxes/index.html.erb b/app/views/faxes/index.html.erb new file mode 100644 index 0000000..4e764af --- /dev/null +++ b/app/views/faxes/index.html.erb @@ -0,0 +1,37 @@ +

<%= notice %>

+ +

Faxes

+ + + + + + + + + + + + + + + + <% @faxes.each do |fax| %> + + + + + + + + + + + + <% end %> + +
Number faxSenderRecipientLetterPaymentSuccess
<%= fax.number_fax %><%= fax.sender_id %><%= fax.recipient_id %><%= fax.letter_id %><%= fax.payment %><%= fax.success %><%= link_to 'Show', fax %><%= link_to 'Edit', edit_fax_path(fax) %><%= link_to 'Destroy', fax, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Fax', new_fax_path %> diff --git a/app/views/faxes/index.json.jbuilder b/app/views/faxes/index.json.jbuilder new file mode 100644 index 0000000..42c75e2 --- /dev/null +++ b/app/views/faxes/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @faxes, partial: "faxes/fax", as: :fax diff --git a/app/views/faxes/new.html.erb b/app/views/faxes/new.html.erb new file mode 100644 index 0000000..6a4ed85 --- /dev/null +++ b/app/views/faxes/new.html.erb @@ -0,0 +1,5 @@ +

New Fax

+ +<%= render 'form', fax: @fax %> + +<%= link_to 'Back', faxes_path %> diff --git a/app/views/faxes/show.html.erb b/app/views/faxes/show.html.erb new file mode 100644 index 0000000..ba5681d --- /dev/null +++ b/app/views/faxes/show.html.erb @@ -0,0 +1,29 @@ +

<%= notice %>

+ +

+ Number fax: + <%= @fax.number_fax %> +

+ +

+ Sender: + <%= @fax.sender_id %> +

+ +

+ Recipient: + <%= @fax.recipient_id %> +

+ +

+ Letter: + <%= @fax.letter_id %> +

+ +

+ Payment: + <%= @fax.payment_id %> +

+ +<%= link_to 'Edit', edit_fax_path(@fax) %> | +<%= link_to 'Back', faxes_path %> diff --git a/app/views/faxes/show.json.jbuilder b/app/views/faxes/show.json.jbuilder new file mode 100644 index 0000000..16a6591 --- /dev/null +++ b/app/views/faxes/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "faxes/fax", fax: @fax diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb new file mode 100644 index 0000000..fad83f4 --- /dev/null +++ b/app/views/layouts/_footer.html.erb @@ -0,0 +1,12 @@ +
+ + VocalVoters.com by Metacortex + + +
diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb new file mode 100644 index 0000000..ccbc3d3 --- /dev/null +++ b/app/views/layouts/_header.html.erb @@ -0,0 +1,65 @@ + diff --git a/app/views/layouts/_shim.html.erb b/app/views/layouts/_shim.html.erb new file mode 100644 index 0000000..ecfd51d --- /dev/null +++ b/app/views/layouts/_shim.html.erb @@ -0,0 +1,4 @@ + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..ea68839 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,39 @@ + + + + + <%= favicon_link_tag 'vocalvoters_logos/vocalvoters-favicon-256.png' %> + <%= full_title(yield(:title)) %> + + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + + + + <%= stylesheet_link_tag 'application', media: 'all', + 'data-turbolinks-track': 'reload' %> + <%= javascript_pack_tag 'application', media: 'all', + 'data-turbolinks-track': 'reload' %> + + <%= render 'layouts/shim' %> + + <%= wicked_pdf_stylesheet_link_tag "pdf" %> + + + + > + <%= render 'layouts/header' %> +
+ <% flash.each do |message_type, message| %> + <%= content_tag(:div, message, class: "alert alert-#{message_type}") %> + <% end %> + <%= yield %> + <%= render 'layouts/footer' %> + <%= debug(params) if Rails.env.development? %> +
+ + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..cbd34d2 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/layouts/pdf.html.erb b/app/views/layouts/pdf.html.erb new file mode 100644 index 0000000..05bc7f2 --- /dev/null +++ b/app/views/layouts/pdf.html.erb @@ -0,0 +1,15 @@ + + + + + <%= wicked_pdf_stylesheet_link_tag "pdf" %> + + + +
+ <%= yield %> +
+ + diff --git a/app/views/letters/_form.html.erb b/app/views/letters/_form.html.erb new file mode 100644 index 0000000..2e54344 --- /dev/null +++ b/app/views/letters/_form.html.erb @@ -0,0 +1,114 @@ +<%= form_with(model: letter) do |form| %> + <% if letter.errors.any? %> +
+

<%= pluralize(letter.errors.count, "error") %> prohibited this letter from being saved:

+ +
    + <% letter.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+
+ <%= form.label :category %> + <%= form.text_field :category, class: 'form-control' %> +
+ +
+ <%= form.label :policy_or_law %> + <%= form.text_field :policy_or_law, class: 'form-control' %> +
+ +
+ <%= form.label :tags %> + <%= form.text_field :tags, class: 'form-control' %> +
+ +
+ <%= form.label :sentiment %> + <%= form.select :sentiment, + options_for_select([['Very Positive', 1.0], ['Positive', 0.5], + ['Neutral', 0],['Negative', -0.5], ['Very Negative', -1.0]], + @letter.sentiment), {}, { :class => 'form-control' } %> +
+ +
+ <%= form.label :target_level %> + <%= form.select :target_level, + options_for_select([['All', 'all'], ['Federal', 'federal'], + ['State', 'state'],['Local', 'local']], @letter.target_level), + {}, { :class => 'form-control' } %> +
+ +
+ <%= form.label :target_state %> + <%= form.select :target_state, + options_for_select([ + ['All', 'all'], + ['Alabama', 'alabama'], + ['Alaska', 'alaska'], + ['Arizona', 'arizona'], + ['Arkansas', 'arkansas'], + ['California', 'california'], + ['Colorado', 'colorado'], + ['Connecticut', 'connecticut'], + ['Delaware', 'delaware'], + ['Florida', 'florida'], + ['Georgia', 'georgia'], + ['Hawaii', 'hawaii'], + ['Idaho', 'idaho'], + ['Illinois', 'illinois'], + ['Indiana', 'indiana'], + ['Iowa', 'iowa'], + ['Kansas', 'kansas'], + ['Kentucky', 'kentucky'], + ['Louisiana', 'louisiana'], + ['Maine', 'maine'], + ['Maryland', 'maryland'], + ['Massachusetts', 'massachusetts'], + ['Michigan', 'michigan'], + ['Minnesota', 'minnesota'], + ['Mississippi', 'mississippi'], + ['Missouri', 'missouri'], + ['Montana', 'montana'], + ['Nebraska', 'nebraska'], + ['Navada', 'navada'], + ['New Hampshire', 'new hampshire'], + ['New Jersey', 'new jersey'], + ['New Mexico', 'new mexico'], + ['New York', 'new york'], + ['North Carolina', 'north carolina'], + ['North Dakota', 'north dakota'], + ['Ohio', 'ohio'], + ['Oklahoma', 'oklahoma'], + ['Oregon', 'oregon'], + ['Pennsylvania', 'pennsylvania'], + ['Rhode Island', 'rhode island'], + ['South Carolina', 'south carolina'], + ['South Dakota', 'south dakota'], + ['Tennessee', 'tennessee'], + ['Texas', 'texas'], + ['Utah', 'utah'], + ['Vermont', 'vermont'], + ['Virginia', 'virginia'], + ['Washington', 'washington'], + ['West Virginia', 'west virginia'], + ['Wisconsin', 'wisconsin'], + ['Wyoming', 'wyoming']], @letter.target_state), + {}, { :class => 'form-control' } %> +
+
+ +
+
+ <%= form.label :body %> + <%= form.text_area :body, class: 'form-control' %> +
+
+ <%= form.submit yield(:button_text), class: "btn btn-primary" %> +
+
+<% end %> diff --git a/app/views/letters/_letter.json.jbuilder b/app/views/letters/_letter.json.jbuilder new file mode 100644 index 0000000..d243d29 --- /dev/null +++ b/app/views/letters/_letter.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! letter, :id, :category, :policy_or_law, :sentiment, :body, :user_id, :created_at, :updated_at +json.url letter_url(letter, format: :json) diff --git a/app/views/letters/edit.html.erb b/app/views/letters/edit.html.erb new file mode 100644 index 0000000..8cb20bb --- /dev/null +++ b/app/views/letters/edit.html.erb @@ -0,0 +1,12 @@ +<% provide(:title, 'Edit Letter') %> +<% provide(:button_text, 'Save changes') %> +

Editing Letter

+ +
+
+ <%= render 'form', letter: @letter %> +
+
+ +<%= link_to 'Show', @letter %> | +<%= link_to 'Back', letters_path %> diff --git a/app/views/letters/find_policy.json.jbuilder b/app/views/letters/find_policy.json.jbuilder new file mode 100644 index 0000000..5a485b9 --- /dev/null +++ b/app/views/letters/find_policy.json.jbuilder @@ -0,0 +1 @@ +json.array! @policy_or_laws diff --git a/app/views/letters/index.html.erb b/app/views/letters/index.html.erb new file mode 100644 index 0000000..49db5e3 --- /dev/null +++ b/app/views/letters/index.html.erb @@ -0,0 +1,35 @@ +

<%= notice %>

+ +

Letters

+ + + + + + + + + + + + + + + <% @letters.each do |letter| %> + + + + + + + + + + + <% end %> + +
CategoryPolicy or lawSentimentBodyUser
<%= letter.category %><%= letter.policy_or_law %><%= letter.sentiment %><%= letter.body %><%= letter.user_id %><%= link_to 'Show', letter %><%= link_to 'Edit', edit_letter_path(letter) %><%= link_to 'Destroy', letter, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Letter', new_letter_path %> diff --git a/app/views/letters/index.json.jbuilder b/app/views/letters/index.json.jbuilder new file mode 100644 index 0000000..45c84ae --- /dev/null +++ b/app/views/letters/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @letters, partial: "letters/letter", as: :letter diff --git a/app/views/letters/letter.html.erb b/app/views/letters/letter.html.erb new file mode 100644 index 0000000..aa1e86c --- /dev/null +++ b/app/views/letters/letter.html.erb @@ -0,0 +1,78 @@ + + +
+
+
+
+
+
+ <%= @recipient_position.html_safe %> <%= @recipient_name.html_safe %>
+ <%= @recipient_location.html_safe %>
+ <% if @recipient.present? %> + <%= @recipient.address_line_1 %>
+ <%= @recipient.address_line_2 %> + <%= @recipient.address_city %> <%= @recipient.address_state %>, + <%= @recipient.address_zipcode %> + <% end %> +
+
+ +
+

+ <%= @recipient_name.html_safe %>, +

+ +

+ My name is <%= @sender_name.html_safe %> and I am writing to express that I + <%= @sentiment %> <%= @letter.category %>. +

+ +

+ Particlulary, I <%= @sentiment %> <%= @letter.policy_or_law %>. +

+ + <%= simple_format(@letter.body) %> + +

+ Thank you,
+ <%= @sender_name.html_safe %>
+ <%= @sender_region.html_safe %> + <%= @sender_region_verified.html_safe %> +

+
+ +
+
+ + +
+
diff --git a/app/views/letters/new.html.erb b/app/views/letters/new.html.erb new file mode 100644 index 0000000..13325bf --- /dev/null +++ b/app/views/letters/new.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, 'Edit Letter') %> +<% provide(:button_text, 'Create Letter') %> +

New Letter

+ +
+
+ <%= render 'form', letter: @letter %> +
+
+ +<%= link_to 'Back', letters_path %> diff --git a/app/views/letters/show.html.erb b/app/views/letters/show.html.erb new file mode 100644 index 0000000..3381db2 --- /dev/null +++ b/app/views/letters/show.html.erb @@ -0,0 +1,35 @@ +

<%= notice %>

+ +

+ Category: + <%= @letter.category %>
+ Policy or law: + <%= @letter.policy_or_law %>
+ Target Level: + <%= @letter.target_level %>
+ Target State: + <%= @letter.target_state %>
+ Created by: + <%= User.find_by(id: @letter.user_id).name %> +

+ +

+ Sentiment: + <%= @letter.sentiment %> +

+ +

+ Body:
+ <%= @letter.body %> +

+ +

+ Letter:
+ +

+ + +<%= link_to 'Edit', edit_letter_path(@letter) %> | +<%= link_to 'Back', letters_path %> diff --git a/app/views/letters/show.json.jbuilder b/app/views/letters/show.json.jbuilder new file mode 100644 index 0000000..3cfeee2 --- /dev/null +++ b/app/views/letters/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "letters/letter", letter: @letter diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb new file mode 100644 index 0000000..0c299a7 --- /dev/null +++ b/app/views/password_resets/edit.html.erb @@ -0,0 +1,21 @@ +<% provide(:title, 'Reset password') %> +

Reset password

+ +
+
+ <%= form_with(model: @user, url: password_reset_path(params[:id]), + local: true) do |f| %> + <%= render 'shared/error_messages' %> + + <%= hidden_field_tag :email, @user.email %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation, "Confirmation" %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit "Update password", class: "btn btn-primary" %> + <% end %> +
+
diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb new file mode 100644 index 0000000..f21b8f4 --- /dev/null +++ b/app/views/password_resets/new.html.erb @@ -0,0 +1,14 @@ +<% provide(:title, "Forgot password") %> +

Forgot password

+ +
+
+ <%= form_with(url: password_resets_path, scope: :password_reset, + local: true) do |f| %> + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.submit "Submit", class: "btn btn-primary" %> + <% end %> +
+
diff --git a/app/views/posts/_form.html.erb b/app/views/posts/_form.html.erb new file mode 100644 index 0000000..9cd5c7b --- /dev/null +++ b/app/views/posts/_form.html.erb @@ -0,0 +1,62 @@ +<%= form_with(model: post) do |form| %> + <% if post.errors.any? %> +
+

<%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:

+ +
    + <% post.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :address_line_1 %> + <%= form.text_field :address_line_1 %> +
+ +
+ <%= form.label :address_line_2 %> + <%= form.text_field :address_line_2 %> +
+ +
+ <%= form.label :address_city %> + <%= form.text_field :address_city %> +
+ +
+ <%= form.label :address_state %> + <%= form.text_field :address_state %> +
+ +
+ <%= form.label :address_zipcode %> + <%= form.text_field :address_zipcode %> +
+ +
+ <%= form.label :sender_id %> + <%= form.text_field :sender_id %> +
+ +
+ <%= form.label :recipient_id %> + <%= form.text_field :recipient_id %> +
+ +
+ <%= form.label :letter_id %> + <%= form.text_field :letter_id %> +
+ +
+ <%= form.label :payment_id %> + <%= form.text_field :payment_id %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/posts/_post.json.jbuilder b/app/views/posts/_post.json.jbuilder new file mode 100644 index 0000000..a4ef438 --- /dev/null +++ b/app/views/posts/_post.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! post, :id, :address_line_1, :address_line_2, :address_city, :address_state, :address_zipcode, :sender_id, :recipient_id, :letter_id, :payment_id, :created_at, :updated_at +json.url post_url(post, format: :json) diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb new file mode 100644 index 0000000..ded33f7 --- /dev/null +++ b/app/views/posts/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Post

+ +<%= render 'form', post: @post %> + +<%= link_to 'Show', @post %> | +<%= link_to 'Back', posts_path %> diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb new file mode 100644 index 0000000..e5c3a52 --- /dev/null +++ b/app/views/posts/index.html.erb @@ -0,0 +1,47 @@ +

<%= notice %>

+ +

Posts

+ + + + + + + + + + + + + + + + + + + + + <% @posts.each do |post| %> + + + + + + + + + + + + + + + + + <% end %> + +
Address line 1Address line 2Address cityAddress stateAddress zipcodePrioritySenderRecipientLetterPaymentSuccess
<%= post.address_line_1 %><%= post.address_line_2 %><%= post.address_city %><%= post.address_state %><%= post.address_zipcode %><%= post.priority %><%= post.sender_id %><%= post.recipient_id %><%= post.letter_id %><%= post.payment %><%= post.success %><%= link_to 'Show', post %><%= link_to 'Edit', edit_post_path(post) %><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Post', new_post_path %> diff --git a/app/views/posts/index.json.jbuilder b/app/views/posts/index.json.jbuilder new file mode 100644 index 0000000..a3c6f4a --- /dev/null +++ b/app/views/posts/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @posts, partial: "posts/post", as: :post diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb new file mode 100644 index 0000000..fb1e2a1 --- /dev/null +++ b/app/views/posts/new.html.erb @@ -0,0 +1,5 @@ +

New Post

+ +<%= render 'form', post: @post %> + +<%= link_to 'Back', posts_path %> diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb new file mode 100644 index 0000000..2036a5c --- /dev/null +++ b/app/views/posts/show.html.erb @@ -0,0 +1,49 @@ +

<%= notice %>

+ +

+ Address line 1: + <%= @post.address_line_1 %> +

+ +

+ Address line 2: + <%= @post.address_line_2 %> +

+ +

+ Address city: + <%= @post.address_city %> +

+ +

+ Address state: + <%= @post.address_state %> +

+ +

+ Address zipcode: + <%= @post.address_zipcode %> +

+ +

+ Sender: + <%= @post.sender_id %> +

+ +

+ Recipient: + <%= @post.recipient_id %> +

+ +

+ Letter: + <%= @post.letter_id %> +

+ +

+ Payment: + <%= @post.payment_id %> +

+ +<%= link_to 'Edit', edit_post_path(@post) %> | +<%= link_to 'Back', posts_path %> diff --git a/app/views/posts/show.json.jbuilder b/app/views/posts/show.json.jbuilder new file mode 100644 index 0000000..5274482 --- /dev/null +++ b/app/views/posts/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "posts/post", post: @post diff --git a/app/views/recipients/_form.html.erb b/app/views/recipients/_form.html.erb new file mode 100644 index 0000000..1554988 --- /dev/null +++ b/app/views/recipients/_form.html.erb @@ -0,0 +1,202 @@ +<%= form_with(model: recipient) do |form| %> + <% if recipient.errors.any? %> +
+

<%= pluralize(recipient.errors.count, "error") %> prohibited this recipient from being saved:

+ +
    + <% recipient.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+
+
+ <%= form.label :name %> + <%= form.text_field :name, class: 'form-control' %> +
+
+
+ +
+
+
+ <%= form.label :level %> + <%= form.select :level, + [['Local', 'local'], + ['State', 'state'], + ['Federal', 'federal']], + { include_blank: true }, { class: 'form-control' } %> +
+
+ +
+
+ <%= form.label :position %> + <%= form.select :position, + [['Representative', 'representative'], + ['Senator', 'senator'], + ['Governor', 'governor']], + { include_blank: true }, { class: 'form-control' } %> + +
+
+ +
+
+ <%= form.label :state %> + <%= form.select :state, + [['Alabama', 'AL'], + ['Alaska', 'AK'], + ['Arizona', 'AZ'], + ['Arkansas', 'AR'], + ['California', 'CA'], + ['Colorado', 'CO'], + ['Connecticut', 'CT'], + ['Delaware', 'DE'], + ['Florida', 'FL'], + ['Georgia', 'GA'], + ['Hawaii', 'HI'], + ['Idaho', 'ID'], + ['Illinois', 'IL'], + ['Indiana', 'IN'], + ['Iowa', 'IA'], + ['Kansas', 'KS'], + ['Kentucky', 'KY'], + ['Louisiana', 'LA'], + ['Maine', 'ME'], + ['Maryland', 'MD'], + ['Massachusetts', 'MA'], + ['Michigan', 'MI'], + ['Minnesota', 'MN'], + ['Mississippi', 'MS'], + ['Missouri', 'MO'], + ['Montana', 'MT'], + ['Nebraska', 'NE'], + ['Navada', 'NV'], + ['New Hampshire', 'NH'], + ['New Jersey', 'NJ'], + ['New Mexico', 'NM'], + ['New York', 'NY'], + ['North Carolina', 'NC'], + ['North Dakota', 'ND'], + ['Ohio', 'OH'], + ['Oklahoma', 'OK'], + ['Oregon', 'OR'], + ['Pennsylvania', 'PA'], + ['Rhode Island', 'RI'], + ['South Carolina', 'SC'], + ['South Dakota', 'SD'], + ['Tennessee', 'TN'], + ['Texas', 'TX'], + ['Utah', 'UT'], + ['Vermont', 'VT'], + ['Virginia', 'VA'], + ['Washington', 'WA'], + ['West Virginia', 'WV'], + ['Wisconsin', 'WI'], + ['Wyoming', 'WY']], + { include_blank: true }, { class: 'form-control' } %> +
+
+ +
+
+ <%= form.label :district %> + <%= form.text_field :district, class: 'form-control' %> +
+
+
+
+ <%= form.label :retirement_status %> + <%= form.select :retired, + [['Active', false], ['Retired', true]], + { include_blank: true }, { class: 'form-control' } %> +
+
+ +
+
+
+
+ +
+
+
+ <%= form.label :number_phone %> + <%= form.number_field :number_phone, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :number_fax %> + <%= form.number_field :number_fax, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :email_address %> + <%= form.text_field :email_address, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :contact_form %> + <%= form.text_field :contact_form, class: 'form-control' %> +
+
+ +
+
+
+
+ +
+
+
+ <%= form.label :address_line_1 %> + <%= form.text_field :address_line_1, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :address_line_2 %> + <%= form.text_field :address_line_2, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :address_city %> + <%= form.text_field :address_city, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :address_state %> + <%= form.text_field :address_state, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :address_zipcode %> + <%= form.text_field :address_zipcode, class: 'form-control' %> +
+
+ +
+ +
+
+ <%= form.submit yield(:button_text), class: "btn btn-primary" %> +
+
+<% end %> diff --git a/app/views/recipients/_recipient.html.erb b/app/views/recipients/_recipient.html.erb new file mode 100644 index 0000000..5157cd0 --- /dev/null +++ b/app/views/recipients/_recipient.html.erb @@ -0,0 +1,65 @@ +
+ <%= recipient.name %>, + <% if recipient.level == 'federal' %>US<% else %><%= recipient.state %><% end %> + <%= recipient.position.capitalize %> +
+ <% if recipient.level == 'federal' %> + <% if recipient.position == 'senator' %> + US + <%= recipient.position.capitalize %> from + <%= recipient.state %> + <% else %> + <%= recipient.state %> + <%= recipient.district.to_i.ordinalize %> + Congressional District + <% end %> + <% else %> + <%= recipient.state %> + <% if recipient.position != 'governor' %> + <% if recipient.position == 'senator' %> + Senator District + <% else %> + House District + <% end %> + <%= recipient.district.to_i.ordinalize %> + <% else %> + Governor + <% end %> + <% end %> +
Phone #: + <%= number_to_phone(recipient.number_phone, area_code: true) %>
+ <% if recipient.contact_form.present? %> + Contact Form: <%=link_to 'Link', + recipient.contact_form, target: "_blank" %> +
+ <% end %> + Mail: + <%= recipient.address_line_1 %>
+ <%= recipient.address_city %> + <%= recipient.address_state %> <%= recipient.address_zipcode %> + + + + + + + + + + + + +
diff --git a/app/views/recipients/_recipient.json.jbuilder b/app/views/recipients/_recipient.json.jbuilder new file mode 100644 index 0000000..bb7c9aa --- /dev/null +++ b/app/views/recipients/_recipient.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! recipient, :id, :name, :position, :level, :district, :state, :number_fax, :number_phone, :email_address, :address_line_1, :address_line_2, :address_city, :address_state, :address_zipcode, :created_at, :updated_at +json.url recipient_url(recipient, format: :json) diff --git a/app/views/recipients/edit.html.erb b/app/views/recipients/edit.html.erb new file mode 100644 index 0000000..2db17b1 --- /dev/null +++ b/app/views/recipients/edit.html.erb @@ -0,0 +1,12 @@ +<% provide(:title, 'Edit Recipient') %> +<% provide(:button_text, 'Save changes') %> +

Editing Recipient

+ +
+
+ <%= render 'form', recipient: @recipient %> +
+
+ +<%= link_to 'Show', @recipient %> | +<%= link_to 'Back', recipients_path %> diff --git a/app/views/recipients/index.html.erb b/app/views/recipients/index.html.erb new file mode 100644 index 0000000..28f9d74 --- /dev/null +++ b/app/views/recipients/index.html.erb @@ -0,0 +1,65 @@ +<% provide(:title, 'Recipients') %> + +

<%= notice %>

+ +
+

+
+ Recipients +
+
+ <%= link_to 'New Recipient', new_recipient_path %> +
+

+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + <% @recipients.each do |recipient| %> + + + + + + + + + + + + + + + + + + + + <% end %> + +
RetiredNamePositionLevelDistrictStateNumber faxNumber phoneEmail addressAddress line 1Address line 2Address cityAddress stateAddress zipcode
<%= recipient.retired %><%= recipient.name %><%= recipient.position %><%= recipient.level %><%= recipient.district %><%= recipient.state %><%= recipient.number_fax %><%= recipient.number_phone %><%= recipient.email_address %><%= recipient.address_line_1 %><%= recipient.address_line_2 %><%= recipient.address_city %><%= recipient.address_state %><%= recipient.address_zipcode %><%= link_to 'Show', recipient %><%= link_to 'Edit', edit_recipient_path(recipient) %><%= link_to 'Destroy', recipient, method: :delete, data: { confirm: 'Are you sure?' } %>
+
diff --git a/app/views/recipients/index.json.jbuilder b/app/views/recipients/index.json.jbuilder new file mode 100644 index 0000000..14437d8 --- /dev/null +++ b/app/views/recipients/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @recipients, partial: "recipients/recipient", as: :recipient diff --git a/app/views/recipients/lookup.html.erb b/app/views/recipients/lookup.html.erb new file mode 100644 index 0000000..f6e93aa --- /dev/null +++ b/app/views/recipients/lookup.html.erb @@ -0,0 +1,57 @@ +
+
+ + + + + + + + + + + + + +
+
+ + +
+
+

+
+
+
+
+
+ Select Legislator(s)
+ + <%= @sender_line_1 %> <%= @sender_line_2 %> + <%= @sender_city %>, <%= @sender_state %> <%= @sender_zipcode %> + <% if @accuracy_class.present? %> +
Review Address, Potentially Inaccurate + <% end %> +
+
+

+ <% federal_flag = false %> + <% state_flag = false %> + <% for recipient in @recipients %> + <% if recipient.level == 'federal' and not federal_flag %> +

Federal Representatives

+
+ <% federal_flag = true %> + <% elsif recipient.level != 'federal' and not state_flag %> +
+

State Representatives

+
+ <% state_flag = true %> + <% end %> +
+ <%= render recipient, :layout => false %> +
+ <% end %> +
diff --git a/app/views/recipients/lookup.json.jbuilder b/app/views/recipients/lookup.json.jbuilder new file mode 100644 index 0000000..2962137 --- /dev/null +++ b/app/views/recipients/lookup.json.jbuilder @@ -0,0 +1,11 @@ +json.sender do + json.address @sender_address + json.zipcode @sender_zipcode + json.county @sender_county + json.district @sender_district + json.state @sender_state + json.address_accuracy @address_accuracy +end +json.recipients do + json.array! @recipients, partial: "recipients/recipient", as: :recipient +end diff --git a/app/views/recipients/new.html.erb b/app/views/recipients/new.html.erb new file mode 100644 index 0000000..c624ff4 --- /dev/null +++ b/app/views/recipients/new.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, 'New Recipient') %> +<% provide(:button_text, 'Create Recipient') %> +

New Recipient

+ +
+
+ <%= render 'form', recipient: @recipient %> +
+
+ +<%= link_to 'Back', recipients_path %> diff --git a/app/views/recipients/show.html.erb b/app/views/recipients/show.html.erb new file mode 100644 index 0000000..87b44bf --- /dev/null +++ b/app/views/recipients/show.html.erb @@ -0,0 +1,80 @@ +

<%= notice %>

+ +

+ Name: + <%= @recipient.name %> +

+ +

+ Retired: + <%= @recipient.retired %> +

+ +

+ Position: + <%= @recipient.position %> +

+ +

+ Level: + <%= @recipient.level %> +

+ +

+ District: + <%= @recipient.district %> +

+ +

+ State: + <%= @recipient.state %> +

+ +

+ Number fax: + <%= @recipient.number_fax %> +

+ +

+ Number phone: + <%= @recipient.number_phone %> +

+ +

+ Email address: + <%= @recipient.email_address %> +

+ +

+ Contact form: + <%= @recipient.contact_form %> +

+ +

+ Address line 1: + <%= @recipient.address_line_1 %> +

+ +

+ Address line 2: + <%= @recipient.address_line_2 %> +

+ +

+ Address city: + <%= @recipient.address_city %> +

+ +

+ Address state: + <%= @recipient.address_state %> +

+ +

+ Address zipcode: + <%= @recipient.address_zipcode %> +

+ +<%= link_to 'Edit', edit_recipient_path(@recipient) %> | +<%= link_to 'Back', recipients_path %> | +<%= link_to 'New Recipient', new_recipient_path %> diff --git a/app/views/recipients/show.json.jbuilder b/app/views/recipients/show.json.jbuilder new file mode 100644 index 0000000..60f4498 --- /dev/null +++ b/app/views/recipients/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "recipients/recipient", recipient: @recipient diff --git a/app/views/senders/_form.html.erb b/app/views/senders/_form.html.erb new file mode 100644 index 0000000..d0be231 --- /dev/null +++ b/app/views/senders/_form.html.erb @@ -0,0 +1,66 @@ +<%= form_with(model: sender) do |form| %> + <% if sender.errors.any? %> +
+

<%= pluralize(sender.errors.count, "error") %> prohibited this sender from being saved:

+ +
    + <% sender.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+
+
+ <%= form.label :name %> + <%= form.text_field :name, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :email %> + <%= form.text_field :email, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :zipcode %> + <%= form.number_field :zipcode, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :county %> + <%= form.text_field :county, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :district %> + <%= form.text_field :district, class: 'form-control' %> +
+
+ +
+
+ <%= form.label :state %> + <%= form.text_field :state, class: 'form-control' %> +
+
+ +
+ +
+
+
+ <%= form.submit yield(:button_text), class: "btn btn-primary" %> +
+
+
+<% end %> diff --git a/app/views/senders/_sender.json.jbuilder b/app/views/senders/_sender.json.jbuilder new file mode 100644 index 0000000..dd206ad --- /dev/null +++ b/app/views/senders/_sender.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! sender, :id, :name, :zipcode, :county, :district, :state, :user_id, :created_at, :updated_at +json.url sender_url(sender, format: :json) diff --git a/app/views/senders/edit.html.erb b/app/views/senders/edit.html.erb new file mode 100644 index 0000000..2bcc352 --- /dev/null +++ b/app/views/senders/edit.html.erb @@ -0,0 +1,14 @@ +<% provide(:title, 'Edit Letter') %> +<% provide(:button_text, 'Save changes') %> + +

Editing Sender

+ +
+
+ <%= render 'form', sender: @sender %> +
+
+ + +<%= link_to 'Show', @sender %> | +<%= link_to 'Back', senders_path %> diff --git a/app/views/senders/index.html.erb b/app/views/senders/index.html.erb new file mode 100644 index 0000000..01abb67 --- /dev/null +++ b/app/views/senders/index.html.erb @@ -0,0 +1,37 @@ +

<%= notice %>

+ +

Senders

+ + + + + + + + + + + + + + + + <% @senders.each do |sender| %> + + + + + + + + + + + + <% end %> + +
NameZipcodeCountyDistrictStateUser
<%= sender.name %><%= sender.zipcode %><%= sender.county %><%= sender.district %><%= sender.state %><%= sender.user_id %><%= link_to 'Show', sender %><%= link_to 'Edit', edit_sender_path(sender) %><%= link_to 'Destroy', sender, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Sender', new_sender_path %> diff --git a/app/views/senders/index.json.jbuilder b/app/views/senders/index.json.jbuilder new file mode 100644 index 0000000..2137d86 --- /dev/null +++ b/app/views/senders/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @senders, partial: "senders/sender", as: :sender diff --git a/app/views/senders/new.html.erb b/app/views/senders/new.html.erb new file mode 100644 index 0000000..bcb547f --- /dev/null +++ b/app/views/senders/new.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, 'Edit Letter') %> +<% provide(:button_text, 'Create Sender') %> +

New Sender

+ +
+
+ <%= render 'form', sender: @sender %> +
+
+ +<%= link_to 'Back', senders_path %> diff --git a/app/views/senders/show.html.erb b/app/views/senders/show.html.erb new file mode 100644 index 0000000..0e6800a --- /dev/null +++ b/app/views/senders/show.html.erb @@ -0,0 +1,34 @@ +

<%= notice %>

+ +

+ Name: + <%= @sender.name %> +

+ +

+ Zipcode: + <%= @sender.zipcode %> +

+ +

+ County: + <%= @sender.county %> +

+ +

+ District: + <%= @sender.district %> +

+ +

+ State: + <%= @sender.state %> +

+ +

+ User: + <%= @sender.user_id %> +

+ +<%= link_to 'Edit', edit_sender_path(@sender) %> | +<%= link_to 'Back', senders_path %> diff --git a/app/views/senders/show.json.jbuilder b/app/views/senders/show.json.jbuilder new file mode 100644 index 0000000..9bff71c --- /dev/null +++ b/app/views/senders/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "senders/sender", sender: @sender diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..98a300c --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,25 @@ +<% provide(:title, "Log in") %> +

Log in

+ +
+
+ <%= form_with(url: login_path, scope: :session, local: true) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + + <%= f.password_field :password, class: 'form-control' %> + +
+ <%= f.label :remember_me, class: "checkbox inline" do %> + <%= f.check_box :remember_me %> + Remember me + <% end %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

New user? <%= link_to "Sign up now!", signup_path %>

+
+
diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb new file mode 100644 index 0000000..f80053e --- /dev/null +++ b/app/views/shared/_error_messages.html.erb @@ -0,0 +1,12 @@ +<% if @user.errors.any? %> +
+
+ The form contains <%= pluralize(@user.errors.count, "error") %>. +
+
    + <% @user.errors.full_messages.each do |msg| %> +
  • <%= msg %>
  • + <% end %> +
+
+<% end %> diff --git a/app/views/static_pages/about.html.erb b/app/views/static_pages/about.html.erb new file mode 100644 index 0000000..0c40015 --- /dev/null +++ b/app/views/static_pages/about.html.erb @@ -0,0 +1,28 @@ +<% provide(:title, "About") %> + +
+
+

About

+
+
+

+ Contacting your local, state, and federal representative is far harder than it should be. Do you even know your representative? If you know your representatives, when was the last time you called or wrote? +

+

+ Representatives have two objectives: +

    +
  • Keep constituents happy enough to keep voting for them
  • +
  • Get supporters to turn out to vote (advertising via fund raising)
  • +
+

+

+ What is often overlooked is how difficult it is for representatives to know how their constituents feel about given legislation. The reality is there are always many policies and bills at the local, state and federal being simultaneously discussed. It's impossible for our representatives to survey their district / region on every topic. Worse, your representatives often engage over social media, often censored today. +

+

+ We make it easy to engage your representatives. In 30 seconds you can share your opinions with your representative. You can send a letter via post or fax (for a fee) or we provide their contact information, enabling you to call them yourself (for free). Those engaging with their representatives are far more likely to be donors and advocates, so reaching out will have a larger impact. +

+

+ Why a letter or fax? A letter is far more effective than email or contact form. Letters cannot be censored, sent to spam, and there's a physical reminder of your opinions. +

+
+
diff --git a/app/views/static_pages/contact.html.erb b/app/views/static_pages/contact.html.erb new file mode 100644 index 0000000..6441665 --- /dev/null +++ b/app/views/static_pages/contact.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, "Contact") %> + +
+
+

+

Contact the Team

+
+

+

+

+ support [at sign] vocalvoters.com +
+





+

+
+
+
+

Why contact us?

+
+

+

    +
  • Add a concern / letter to the collection of options
  • +
  • Would like to integrate VocalVoters.com to their website
  • +
  • Report a missing federal, state or local representative
  • +
  • Report a spelling error in a current letter
  • +
+

+
+
diff --git a/app/views/static_pages/create_payment_intent.json.jbuilder b/app/views/static_pages/create_payment_intent.json.jbuilder new file mode 100644 index 0000000..9cc6054 --- /dev/null +++ b/app/views/static_pages/create_payment_intent.json.jbuilder @@ -0,0 +1 @@ +json.clientSecret @payment_info diff --git a/app/views/static_pages/help.html.erb b/app/views/static_pages/help.html.erb new file mode 100644 index 0000000..b9ee8c0 --- /dev/null +++ b/app/views/static_pages/help.html.erb @@ -0,0 +1,3 @@ +<% provide(:title, "Help") %> +

Help

+

This is a help page.

diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb new file mode 100644 index 0000000..fa44a84 --- /dev/null +++ b/app/views/static_pages/home.html.erb @@ -0,0 +1,235 @@ +<% provide(:title, "Home") %> +
+

+ <%= image_tag 'vocalvoters_logos/vocalvoters-beta.png', + style: 'width:50%', class: "spashlogo" %> +

+

+ Share Your Opinion With Your Representatives
+ Send a Letter or Fax in 30 Seconds or Less +

+
+
+
+ <%= form_with url: govlookup_path, method: :get do |form| %> +
+
+
+ <%= form.text_field :name, + placeholder: 'Your Full Name', + class: 'form-control' %> +
+
+
+
+ <%= form.text_field :email, + placeholder: '@Email', + class: 'form-control' %> +
+
+
+
+ <%= form.text_field :address, + placeholder: 'Address: 123 Street Address, ST 11111', + style: 'margin-bottom:1px', + class: 'form-control' %> +
+
+
+ <% end %> +
+
+
+ <%= button_tag 'Contact Legislators', + id: 'legislator_button', + style: 'margin-top:1em;', + data: { disable_with: "Please wait..." }, + class: "btn btn-primary" %> +
+
+
+
+
+ +
+
+
+
+
+ + +
+ +
+ +
+ +
+ + + + +
+ +
+ + +<%= javascript_pack_tag 'landing_page' %> diff --git a/app/views/user_mailer/account_activation.html.erb b/app/views/user_mailer/account_activation.html.erb new file mode 100644 index 0000000..0f0b3b0 --- /dev/null +++ b/app/views/user_mailer/account_activation.html.erb @@ -0,0 +1,12 @@ +

VocalVoters

+ +

+ Howdy <%= @user.name %>, +

+ +

+ Welcome to the VocalVoters! Click on the link below to activate your account: +

+ +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, + email: @user.email) %> diff --git a/app/views/user_mailer/account_activation.text.erb b/app/views/user_mailer/account_activation.text.erb new file mode 100644 index 0000000..dc76082 --- /dev/null +++ b/app/views/user_mailer/account_activation.text.erb @@ -0,0 +1,8 @@ +Howdy <%= @user.name %>, + +Welcome to the VocalVoters! Click on the link below to activate your account: + + +<%= link_to "Activate", edit_account_activation_url(@user.activation_token, + email: @user.email) %> + diff --git a/app/views/user_mailer/password_reset.html.erb b/app/views/user_mailer/password_reset.html.erb new file mode 100644 index 0000000..87f6d23 --- /dev/null +++ b/app/views/user_mailer/password_reset.html.erb @@ -0,0 +1,21 @@ +

Password reset

+ +

+ Howdy <%= @user.name %>, +

+ +

+ To reset your password click the link below: +

+ +<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, + email: @user.email) %> + +

+ This link will expire in two hours. +

+ +

+ If you did not request your password to be reset, please ignore this email and + your password will stay as it is. +

diff --git a/app/views/user_mailer/password_reset.text.erb b/app/views/user_mailer/password_reset.text.erb new file mode 100644 index 0000000..08ac1bc --- /dev/null +++ b/app/views/user_mailer/password_reset.text.erb @@ -0,0 +1,10 @@ +Howdy <%= @user.name %>, + +To reset your password click the link below: + +<%= edit_password_reset_url(@user.reset_token, email: @user.email) %> + +This link will expire in two hours. + +If you did not request your password to be reset, please ignore this email and +your password will stay as it is. \ No newline at end of file diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb new file mode 100644 index 0000000..03dbdce --- /dev/null +++ b/app/views/users/_form.html.erb @@ -0,0 +1,17 @@ +<%= form_with(model: @user, local: true) do |f| %> + <%= render 'shared/error_messages', object: @user %> + + <%= f.label :name %> + <%= f.text_field :name, class: 'form-control' %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.label :password_confirmation %> + <%= f.password_field :password_confirmation, class: 'form-control' %> + + <%= f.submit yield(:button_text), class: "btn btn-primary" %> +<% end %> diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb new file mode 100644 index 0000000..e44f493 --- /dev/null +++ b/app/views/users/_user.html.erb @@ -0,0 +1,7 @@ +
  • + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, method: :delete, + data: { confirm: "You sure?" } %> + <% end %> +
  • diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb new file mode 100644 index 0000000..e564b0f --- /dev/null +++ b/app/views/users/edit.html.erb @@ -0,0 +1,8 @@ +<% provide(:title, 'Edit user') %> +<% provide(:button_text, 'Save changes') %> +

    Update your profile

    +
    +
    + <%= render 'form' %> +
    +
    diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb new file mode 100644 index 0000000..5b6dbae --- /dev/null +++ b/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +<% provide(:title, 'All users') %> +

    All users

    + +<%= will_paginate %> + +
      + <%= render @users %> +
    + +<%= will_paginate %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 0000000..5ec12ff --- /dev/null +++ b/app/views/users/new.html.erb @@ -0,0 +1,8 @@ +<% provide(:title, 'Sign up') %> +<% provide(:button_text, 'Create my account') %> +

    Sign up

    +
    +
    + <%= render 'form' %> +
    +
    diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb new file mode 100644 index 0000000..875e620 --- /dev/null +++ b/app/views/users/show.html.erb @@ -0,0 +1,11 @@ +<% provide(:title, @user.name) %> + +
    + +
    diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..4df1949 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,70 @@ +module.exports = function(api) { + var validEnv = ['development', 'test', 'production'] + var currentEnv = api.env() + var isDevelopmentEnv = api.env('development') + var isProductionEnv = api.env('production') + var isTestEnv = api.env('test') + + if (!validEnv.includes(currentEnv)) { + throw new Error( + 'Please specify a valid `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(currentEnv) + + '.' + ) + } + + return { + presets: [ + isTestEnv && [ + '@babel/preset-env', + { + targets: { + node: 'current' + } + } + ], + (isProductionEnv || isDevelopmentEnv) && [ + '@babel/preset-env', + { + forceAllTransforms: true, + useBuiltIns: 'entry', + corejs: 3, + modules: false, + exclude: ['transform-typeof-symbol'] + } + ] + ].filter(Boolean), + plugins: [ + 'babel-plugin-macros', + '@babel/plugin-syntax-dynamic-import', + isTestEnv && 'babel-plugin-dynamic-import-node', + '@babel/plugin-transform-destructuring', + [ + '@babel/plugin-proposal-class-properties', + { + loose: true + } + ], + [ + '@babel/plugin-proposal-object-rest-spread', + { + useBuiltIns: true + } + ], + [ + '@babel/plugin-transform-runtime', + { + helpers: false + } + ], + [ + '@babel/plugin-transform-regenerator', + { + async: false + } + ] + ].filter(Boolean) + } +} diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..a71368e --- /dev/null +++ b/bin/bundle @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../../Gemfile", __FILE__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_version + @bundler_version ||= + env_var_version || cli_arg_version || + lockfile_version + end + + def bundler_requirement + return "#{Gem::Requirement.default}.a" unless bundler_version + + bundler_gem_version = Gem::Version.new(bundler_version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") + + requirement += ".a" if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..21d3e02 --- /dev/null +++ b/bin/rails @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +load File.expand_path("spring", __dir__) +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..7327f47 --- /dev/null +++ b/bin/rake @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +load File.expand_path("spring", __dir__) +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..90700ac --- /dev/null +++ b/bin/setup @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +require "fileutils" + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies + system! 'bin/yarn' + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:prepare' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/spring b/bin/spring new file mode 100755 index 0000000..9675cce --- /dev/null +++ b/bin/spring @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) + gem "bundler" + require "bundler" + + # Load Spring without loading other gems in the Gemfile, for speed. + Bundler.locked_gems.specs.find { |spec| spec.name == "spring" }&.tap do |spring| + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem "spring", spring.version + require "spring/binstub" + rescue Gem::LoadError + # Ignore when Spring is not installed. + end +end diff --git a/bin/webpack b/bin/webpack new file mode 100755 index 0000000..1031168 --- /dev/null +++ b/bin/webpack @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/webpack_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::WebpackRunner.run(ARGV) +end diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server new file mode 100755 index 0000000..dd96627 --- /dev/null +++ b/bin/webpack-dev-server @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/dev_server_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::DevServerRunner.run(ARGV) +end diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 0000000..4700a9e --- /dev/null +++ b/bin/yarn @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + yarn = ENV["PATH"].split(File::PATH_SEPARATOR). + select { |dir| File.expand_path(dir) != __dir__ }. + product(["yarn", "yarn.exe"]). + map { |dir, file| File.expand_path(file, dir) }. + find { |file| File.executable?(file) } + + if yarn + exec yarn, *ARGV + else + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..4a3c09a --- /dev/null +++ b/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..bdd8167 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,22 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module VocalVoters + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 6.1 + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..3cda23b --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000..c98a192 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: vocalvoters_production diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000..cf4a957 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +h/cbVyiKC+6BLQL3+y6TIRBtlRvEe6ERivyr8uqQU+AfuAGUDK6T0CWOg6RVFlLu9q1nxUnikvXd5/KgMnxAVkFrFlQg8ZeIW50xsfWZZpQHPVkArE9PZGf+nn1sXVflKaQ/egk39H+5S+QX5htLa/GpJ6mf4FUVJzI4FkTlmtmmovhy2WYXH4zXvfKlKl41xtEhM7WmF/ICUyWwh4L0Fk4RrQryJEMJtL1nQwiqjS++uH4BJNgTVEKRyFSKUaNcZt7tK3L/u5udF1h+RGy2fqQc0UH1AosN+XMADXctDhdiRxBs+/WoFwwCk1RHl3o/FUGhrzrzXwfqIZ98YkvisD/cLHqdWvnPnQ6khotWDvWiGvQLel62NIj6UwG3m4nefoKhefEGxISw7G3kMSvTVqxmzr4BYh5aYT+E--up11m3+H1hGvF+mW--HK1qITEUGPEATsy4N03R4w== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..2b37359 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,30 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + adapter: postgresql + encoding: unicode + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + database: vocalvoters_production + username: vocalvoters_app + password: <%= ENV['VOCALVOTERS_DATABASE_PASSWORD'] %> + diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..cac5315 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..977b5b3 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,92 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Host URL, local host + host = 'localhost:3000' + + # Do not fallback to assets pipeline if a precompiled asset is missed. + # config.assets.compile = false + + + # Use this on the cloud IDE. + # config.action_mailer.default_url_options = { host: host, protocol: 'https' } + + # Use this if developing on localhost. + config.action_mailer.default_url_options = { host: host, protocol: 'http' } + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Allow connections to local server + config.hosts.clear +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..2d0ef4c --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,137 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Include generic and useful information about system operation, but avoid logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). + config.log_level = :info + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "faxgov_us_production" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + config.action_mailer.raise_delivery_errors = true + config.action_mailer.delivery_method = :smtp + host = 'vocalvoters.com' + config.action_mailer.default_url_options = { host: host } + ActionMailer::Base.smtp_settings = { + :address => 'smtp.sendgrid.net', + :port => '587', + :authentication => :plain, + :user_name => 'apikey', + :password => ENV['SENDGRID_API_KEY_VOCALVOTERS'], + :domain => 'heroku.com', + :enable_starttls_auto => true + } + + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Log disallowed deprecations. + config.active_support.disallowed_deprecation = :log + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require "syslog/logger" + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Inserts middleware to perform automatic connection switching. + # The `database_selector` hash is used to pass options to the DatabaseSelector + # middleware. The `delay` is used to determine how long to wait after a write + # to send a subsequent read to the primary. + # + # The `database_resolver` class is used by the middleware to determine which + # database is appropriate to use based on the time delay. + # + # The `database_resolver_context` class is used by the middleware to set + # timestamps for the last write to the primary. The resolver uses the context + # class timestamps to determine how long to wait before reading from the + # replica. + # + # By default Rails will store a last write timestamp in the session. The + # DatabaseSelector middleware is designed as such you can define your own + # strategy for connection switching and pass that into the middleware through + # these configuration options. + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..6c118ed --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,61 @@ +require "active_support/core_ext/integer/time" + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + config.cache_classes = false + config.action_view.cache_template_loading = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + config.cache_store = :null_store + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + config.action_mailer.default_url_options = { host: 'example.com' } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true +end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000..89d2efa --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000..4b828e8 --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..33699c3 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code +# by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". +Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..1c1fd35 --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,30 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +#Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https, :http +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :http, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # If you are using webpack-dev-server then specify webpack-dev-server host +# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +#end + +# If you are using UJS then enable automatic nonce generation +#Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Set the nonce only to specific directives +#Rails.application.config.content_security_policy_nonce_directives = %w(script-src) + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +#Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000..5a6a32d --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..4b34a03 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,6 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..ac033bf --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000..00fa42e --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +Mime::Type.register "application/pdf", :pdf diff --git a/config/initializers/permissions_policy.rb b/config/initializers/permissions_policy.rb new file mode 100644 index 0000000..00f64d7 --- /dev/null +++ b/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/config/initializers/wicked_pdf.rb b/config/initializers/wicked_pdf.rb new file mode 100644 index 0000000..a84ba8b --- /dev/null +++ b/config/initializers/wicked_pdf.rb @@ -0,0 +1,36 @@ +# WickedPDF Global Configuration +# +# Use this to set up shared configuration options for your entire application. +# Any of the configuration options shown here can also be applied to single +# models by passing arguments to the `render :pdf` call. +# +# To learn more, check out the README: +# +# https://github.com/mileszs/wicked_pdf/blob/master/README.md + +WickedPdf.config = { + # Path to the wkhtmltopdf executable: This usually isn't needed if using + # one of the wkhtmltopdf-binary family of gems. + # exe_path: '/usr/local/bin/wkhtmltopdf', + # or + # exe_path: Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf') + + # Layout file to be used for all PDFs + # (but can be overridden in `render :pdf` calls) + # layout: 'pdf.html', + + # Using wkhtmltopdf without an X server can be achieved by enabling the + # 'use_xvfb' flag. This will wrap all wkhtmltopdf commands around the + # 'xvfb-run' command, in order to simulate an X server. + # + # use_xvfb: true, +} + +WickedPdf.config ||= {} +WickedPdf.config.merge!({ + layout: "pdf.html.erb", + orientation: "Portrait", + page_size: "A4", + zoom: 1, + dpi: 300 +}) diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000..bbfc396 --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..cf9b342 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000..91606ec --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,43 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +# +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { ENV['RACK_ENV'] || "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked web server processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +preload_app! + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..9854792 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,38 @@ +Rails.application.routes.draw do + + get 'password_resets/new' + get 'password_resets/edit' + root 'static_pages#home' + + get '/help', to: 'static_pages#help' + get '/about', to: 'static_pages#about' + get '/contact', to: 'static_pages#contact' + + get '/signup', to: 'users#new' + + get '/login', to: 'sessions#new' + post '/login', to: 'sessions#create' + delete '/logout', to: 'sessions#destroy' + + post '/payment_system/vZ692SnI2btP1aybeJDA4TPTmEslSjOD', to: 'payments#webhook' + + resources :users + resources :account_activations, only: [:edit] + resources :password_resets, only: [:new, :create, :edit, :update] + + resources :emails + resources :posts + resources :faxes + + resources :recipients + resources :senders + resources :letters + + post 'send_communication', to: 'send_communication#send_communication' + + get 'govlookup', to: 'recipients#lookup' + get 'find_policy', to: 'letters#find_policy' + + post 'create-payment-intent', to: 'static_pages#create_payment_intent' + get 'create-payment-intent', to: 'static_pages#create_payment_intent' +end diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 0000000..db5bf13 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +Spring.watch( + ".ruby-version", + ".rbenv-vars", + "tmp/restart.txt", + "tmp/caching-dev.txt" +) diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000..d32f76e --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/config/webpack/development.js b/config/webpack/development.js new file mode 100644 index 0000000..c5edff9 --- /dev/null +++ b/config/webpack/development.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/config/webpack/environment.js b/config/webpack/environment.js new file mode 100644 index 0000000..09ec75f --- /dev/null +++ b/config/webpack/environment.js @@ -0,0 +1,11 @@ +const { environment } = require('@rails/webpacker') + +const webpack = require('webpack') +environment.plugins.prepend('Provide', + new webpack.ProvidePlugin({ + $: 'jquery/src/jquery', + jQuery: 'jquery/src/jquery' + }) +) + +module.exports = environment diff --git a/config/webpack/production.js b/config/webpack/production.js new file mode 100644 index 0000000..be0f53a --- /dev/null +++ b/config/webpack/production.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/config/webpack/test.js b/config/webpack/test.js new file mode 100644 index 0000000..c5edff9 --- /dev/null +++ b/config/webpack/test.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/config/webpacker.yml b/config/webpacker.yml new file mode 100644 index 0000000..4b4aa3d --- /dev/null +++ b/config/webpacker.yml @@ -0,0 +1,92 @@ +# Note: You must restart bin/webpack-dev-server for changes to take effect + +default: &default + source_path: app/javascript + source_entry_path: packs + public_root_path: public + public_output_path: packs + cache_path: tmp/cache/webpacker + webpack_compile_output: true + + # Additional paths webpack should lookup modules + # ['app/assets', 'engine/foo/app/assets'] + additional_paths: ['app/javascript/images', 'app/assets/images', 'vendor/assets'] + + # Reload manifest.json on all requests so we reload latest compiled packs + cache_manifest: false + + # Extract and emit a css file + extract_css: false + + static_assets_extensions: + - .jpg + - .jpeg + - .png + - .gif + - .tiff + - .ico + - .svg + - .eot + - .otf + - .ttf + - .woff + - .woff2 + + extensions: + - .mjs + - .js + - .sass + - .scss + - .css + - .module.sass + - .module.scss + - .module.css + - .png + - .svg + - .gif + - .jpeg + - .jpg + +development: + <<: *default + compile: true + + # Reference: https://webpack.js.org/configuration/dev-server/ + dev_server: + https: false + host: localhost + port: 3035 + public: localhost:3035 + hmr: false + # Inline should be set to true if using HMR + inline: true + overlay: true + compress: true + disable_host_check: true + use_local_ip: false + quiet: false + pretty: false + headers: + 'Access-Control-Allow-Origin': '*' + watch_options: + ignored: '**/node_modules/**' + + +test: + <<: *default + compile: true + + # Compile test packs to a separate directory + public_output_path: packs-test + +production: + <<: *default + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Extract and emit a css file + extract_css: true + + # Cache manifest.json for performance + cache_manifest: true diff --git a/db/migrate/20210603041537_create_users.rb b/db/migrate/20210603041537_create_users.rb new file mode 100644 index 0000000..e0c8121 --- /dev/null +++ b/db/migrate/20210603041537_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[6.1] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/db/migrate/20210603042417_add_index_to_users_email.rb b/db/migrate/20210603042417_add_index_to_users_email.rb new file mode 100644 index 0000000..936844c --- /dev/null +++ b/db/migrate/20210603042417_add_index_to_users_email.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersEmail < ActiveRecord::Migration[6.1] + def change + add_index :users, :email, unique: true + end +end diff --git a/db/migrate/20210603042940_add_password_digest_to_users.rb b/db/migrate/20210603042940_add_password_digest_to_users.rb new file mode 100644 index 0000000..f7e2a41 --- /dev/null +++ b/db/migrate/20210603042940_add_password_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordDigestToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :password_digest, :string + end +end diff --git a/db/migrate/20210603185100_add_remember_digest_to_users.rb b/db/migrate/20210603185100_add_remember_digest_to_users.rb new file mode 100644 index 0000000..ba2b6de --- /dev/null +++ b/db/migrate/20210603185100_add_remember_digest_to_users.rb @@ -0,0 +1,5 @@ +class AddRememberDigestToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :remember_digest, :string + end +end diff --git a/db/migrate/20210604190943_add_admin_to_users.rb b/db/migrate/20210604190943_add_admin_to_users.rb new file mode 100644 index 0000000..cd55f45 --- /dev/null +++ b/db/migrate/20210604190943_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/db/migrate/20210604195135_add_activation_to_users.rb b/db/migrate/20210604195135_add_activation_to_users.rb new file mode 100644 index 0000000..7d7b1a6 --- /dev/null +++ b/db/migrate/20210604195135_add_activation_to_users.rb @@ -0,0 +1,7 @@ +class AddActivationToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :activation_digest, :string + add_column :users, :activated, :boolean, default: false + add_column :users, :activated_at, :datetime + end +end diff --git a/db/migrate/20210605041256_add_reset_to_users.rb b/db/migrate/20210605041256_add_reset_to_users.rb new file mode 100644 index 0000000..dd79fee --- /dev/null +++ b/db/migrate/20210605041256_add_reset_to_users.rb @@ -0,0 +1,6 @@ +class AddResetToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :reset_digest, :string + add_column :users, :reset_sent_at, :datetime + end +end diff --git a/db/migrate/20210609044142_create_pay_subscriptions.pay.rb b/db/migrate/20210609044142_create_pay_subscriptions.pay.rb new file mode 100644 index 0000000..2f03737 --- /dev/null +++ b/db/migrate/20210609044142_create_pay_subscriptions.pay.rb @@ -0,0 +1,18 @@ +# This migration comes from pay (originally 20170205020145) +class CreatePaySubscriptions < ActiveRecord::Migration[4.2] + def change + create_table :pay_subscriptions do |t| + # Some Billable objects use string as ID, add `type: :string` if needed + t.references :owner, polymorphic: true + t.string :name, null: false + t.string :processor, null: false + t.string :processor_id, null: false + t.string :processor_plan, null: false + t.integer :quantity, default: 1, null: false + t.datetime :trial_ends_at + t.datetime :ends_at + + t.timestamps + end + end +end diff --git a/db/migrate/20210609044143_create_pay_charges.pay.rb b/db/migrate/20210609044143_create_pay_charges.pay.rb new file mode 100644 index 0000000..9e764b6 --- /dev/null +++ b/db/migrate/20210609044143_create_pay_charges.pay.rb @@ -0,0 +1,19 @@ +# This migration comes from pay (originally 20170727235816) +class CreatePayCharges < ActiveRecord::Migration[4.2] + def change + create_table :pay_charges do |t| + # Some Billable objects use string as ID, add `type: :string` if needed + t.references :owner, polymorphic: true + t.string :processor, null: false + t.string :processor_id, null: false + t.integer :amount, null: false + t.integer :amount_refunded + t.string :card_type + t.string :card_last4 + t.string :card_exp_month + t.string :card_exp_year + + t.timestamps + end + end +end diff --git a/db/migrate/20210609044144_add_status_to_pay_subscriptions.pay.rb b/db/migrate/20210609044144_add_status_to_pay_subscriptions.pay.rb new file mode 100644 index 0000000..4286f0a --- /dev/null +++ b/db/migrate/20210609044144_add_status_to_pay_subscriptions.pay.rb @@ -0,0 +1,15 @@ +# This migration comes from pay (originally 20190816015720) +class AddStatusToPaySubscriptions < ActiveRecord::Migration[4.2] + def self.up + add_column :pay_subscriptions, :status, :string + + # Any existing subscriptions should be marked as 'active' + # This won't actually make them active if their ends_at column is expired + Pay::Subscription.reset_column_information + Pay::Subscription.update_all(status: :active) + end + + def self.down + remove_column :pay_subscriptions, :status + end +end diff --git a/db/migrate/20210609044145_add_data_to_pay_models.pay.rb b/db/migrate/20210609044145_add_data_to_pay_models.pay.rb new file mode 100644 index 0000000..a61a9f1 --- /dev/null +++ b/db/migrate/20210609044145_add_data_to_pay_models.pay.rb @@ -0,0 +1,16 @@ +# This migration comes from pay (originally 20200603134434) +class AddDataToPayModels < ActiveRecord::Migration[4.2] + def change + add_column :pay_subscriptions, :data, data_column_type + add_column :pay_charges, :data, data_column_type + end + + def data_column_type + case Pay::Adapter.current_adapter + when "postgresql" + :jsonb + else + :json + end + end +end diff --git a/db/migrate/20210609044146_add_data_to_pay_billable.pay.rb b/db/migrate/20210609044146_add_data_to_pay_billable.pay.rb new file mode 100644 index 0000000..85ded1e --- /dev/null +++ b/db/migrate/20210609044146_add_data_to_pay_billable.pay.rb @@ -0,0 +1,20 @@ +# This migration comes from pay (originally 20210309004259) +class AddDataToPayBillable < ActiveRecord::Migration[4.2] + def change + # Load all the billable models + Rails.application.eager_load! + + Pay.billable_models.each do |model| + add_column model.table_name, :pay_data, data_column_type + end + end + + def data_column_type + case Pay::Adapter.current_adapter + when "postgresql" + :jsonb + else + :json + end + end +end diff --git a/db/migrate/20210609044147_add_currency_to_pay_charges.pay.rb b/db/migrate/20210609044147_add_currency_to_pay_charges.pay.rb new file mode 100644 index 0000000..aca8b2a --- /dev/null +++ b/db/migrate/20210609044147_add_currency_to_pay_charges.pay.rb @@ -0,0 +1,6 @@ +# This migration comes from pay (originally 20210406215234) +class AddCurrencyToPayCharges < ActiveRecord::Migration[4.2] + def change + add_column :pay_charges, :currency, :string + end +end diff --git a/db/migrate/20210609044148_add_application_fee_to_pay_models.pay.rb b/db/migrate/20210609044148_add_application_fee_to_pay_models.pay.rb new file mode 100644 index 0000000..92cc74a --- /dev/null +++ b/db/migrate/20210609044148_add_application_fee_to_pay_models.pay.rb @@ -0,0 +1,8 @@ +# This migration comes from pay (originally 20210406215506) +class AddApplicationFeeToPayModels < ActiveRecord::Migration[4.2] + def change + add_column :pay_charges, :application_fee_amount, :integer + add_column :pay_subscriptions, :application_fee_percent, :decimal, precision: 8, scale: 2 + add_column :pay_charges, :pay_subscription_id, :integer + end +end diff --git a/db/migrate/20210610040024_create_letters.rb b/db/migrate/20210610040024_create_letters.rb new file mode 100644 index 0000000..e317fb2 --- /dev/null +++ b/db/migrate/20210610040024_create_letters.rb @@ -0,0 +1,14 @@ +class CreateLetters < ActiveRecord::Migration[6.1] + def change + create_table :letters do |t| + t.string :category + t.string :policy_or_law + t.string :tags + t.float :sentiment + t.text :body + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20210629195032_create_senders.rb b/db/migrate/20210629195032_create_senders.rb new file mode 100644 index 0000000..ae9778c --- /dev/null +++ b/db/migrate/20210629195032_create_senders.rb @@ -0,0 +1,14 @@ +class CreateSenders < ActiveRecord::Migration[6.1] + def change + create_table :senders do |t| + t.string :name + t.integer :zipcode + t.string :county + t.string :district + t.string :state + t.references :user, null: true, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20210629200322_create_recipients.rb b/db/migrate/20210629200322_create_recipients.rb new file mode 100644 index 0000000..91425a0 --- /dev/null +++ b/db/migrate/20210629200322_create_recipients.rb @@ -0,0 +1,21 @@ +class CreateRecipients < ActiveRecord::Migration[6.1] + def change + create_table :recipients do |t| + t.string :name + t.string :position + t.string :level + t.string :district + t.string :state + t.integer :number_fax + t.integer :number_phone + t.string :email_address + t.string :address_line_1 + t.string :address_line_2 + t.string :address_city + t.string :address_state + t.string :address_zipcode + + t.timestamps + end + end +end diff --git a/db/migrate/20210629202832_create_faxes.rb b/db/migrate/20210629202832_create_faxes.rb new file mode 100644 index 0000000..7b568f6 --- /dev/null +++ b/db/migrate/20210629202832_create_faxes.rb @@ -0,0 +1,14 @@ +class CreateFaxes < ActiveRecord::Migration[6.1] + def change + create_table :faxes do |t| + t.integer :number_fax + t.string :payment, null: false + t.boolean :success, default: true + t.references :sender, null: false, foreign_key: true + t.references :recipient, null: false, foreign_key: true + t.references :letter, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20210629203005_create_posts.rb b/db/migrate/20210629203005_create_posts.rb new file mode 100644 index 0000000..9cfbaf1 --- /dev/null +++ b/db/migrate/20210629203005_create_posts.rb @@ -0,0 +1,19 @@ +class CreatePosts < ActiveRecord::Migration[6.1] + def change + create_table :posts do |t| + t.string :address_line_1 + t.string :address_line_2 + t.string :address_city + t.string :address_state + t.string :address_zipcode + t.string :payment, null: false + t.boolean :priority, default: false + t.boolean :success, default: true + t.references :sender, null: false, foreign_key: true + t.references :recipient, null: false, foreign_key: true + t.references :letter, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20210629203017_create_emails.rb b/db/migrate/20210629203017_create_emails.rb new file mode 100644 index 0000000..2795da8 --- /dev/null +++ b/db/migrate/20210629203017_create_emails.rb @@ -0,0 +1,14 @@ +class CreateEmails < ActiveRecord::Migration[6.1] + def change + create_table :emails do |t| + t.string :email_address + t.boolean :success, default: true + t.references :sender, null: false, foreign_key: true + t.references :recipient, null: false, foreign_key: true + t.references :letter, null: false, foreign_key: true + t.string :payment, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20210630181252_add_contact_form_to_recipients.rb b/db/migrate/20210630181252_add_contact_form_to_recipients.rb new file mode 100644 index 0000000..924bb1c --- /dev/null +++ b/db/migrate/20210630181252_add_contact_form_to_recipients.rb @@ -0,0 +1,5 @@ +class AddContactFormToRecipients < ActiveRecord::Migration[6.1] + def change + add_column :recipients, :contact_form, :string + end +end diff --git a/db/migrate/20210630184151_add_email_to_senders.rb b/db/migrate/20210630184151_add_email_to_senders.rb new file mode 100644 index 0000000..e50c5ab --- /dev/null +++ b/db/migrate/20210630184151_add_email_to_senders.rb @@ -0,0 +1,5 @@ +class AddEmailToSenders < ActiveRecord::Migration[6.1] + def change + add_column :senders, :email, :string + end +end diff --git a/db/migrate/20210910044626_change_number_to_bigint_on_recipients.rb b/db/migrate/20210910044626_change_number_to_bigint_on_recipients.rb new file mode 100644 index 0000000..202484e --- /dev/null +++ b/db/migrate/20210910044626_change_number_to_bigint_on_recipients.rb @@ -0,0 +1,6 @@ +class ChangeNumberToBigintOnRecipients < ActiveRecord::Migration[6.1] + def change + change_column :recipients, :number_fax, :bigint + change_column :recipients, :number_phone, :bigint + end +end diff --git a/db/migrate/20210915042754_add_retirement_to_recipients.rb b/db/migrate/20210915042754_add_retirement_to_recipients.rb new file mode 100644 index 0000000..78be205 --- /dev/null +++ b/db/migrate/20210915042754_add_retirement_to_recipients.rb @@ -0,0 +1,5 @@ +class AddRetirementToRecipients < ActiveRecord::Migration[6.1] + def change + add_column :recipients, :retired, :boolean, :default => false + end +end diff --git a/db/migrate/20211021051914_add_target_level_and_target_state_to_letters.rb b/db/migrate/20211021051914_add_target_level_and_target_state_to_letters.rb new file mode 100644 index 0000000..829dc8a --- /dev/null +++ b/db/migrate/20211021051914_add_target_level_and_target_state_to_letters.rb @@ -0,0 +1,6 @@ +class AddTargetLevelAndTargetStateToLetters < ActiveRecord::Migration[6.1] + def change + add_column :letters, :target_level, :string, default: "all" + add_column :letters, :target_state, :string, default: "all" + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..4a7ca90 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,172 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2021_10_21_051914) do + + create_table "emails", force: :cascade do |t| + t.string "email_address" + t.boolean "success", default: true + t.integer "sender_id", null: false + t.integer "recipient_id", null: false + t.integer "letter_id", null: false + t.string "payment", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["letter_id"], name: "index_emails_on_letter_id" + t.index ["recipient_id"], name: "index_emails_on_recipient_id" + t.index ["sender_id"], name: "index_emails_on_sender_id" + end + + create_table "faxes", force: :cascade do |t| + t.integer "number_fax" + t.string "payment", null: false + t.boolean "success", default: true + t.integer "sender_id", null: false + t.integer "recipient_id", null: false + t.integer "letter_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["letter_id"], name: "index_faxes_on_letter_id" + t.index ["recipient_id"], name: "index_faxes_on_recipient_id" + t.index ["sender_id"], name: "index_faxes_on_sender_id" + end + + create_table "letters", force: :cascade do |t| + t.string "category" + t.string "policy_or_law" + t.string "tags" + t.float "sentiment" + t.text "body" + t.integer "user_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "target_level", default: "all" + t.string "target_state", default: "all" + t.index ["user_id"], name: "index_letters_on_user_id" + end + + create_table "pay_charges", force: :cascade do |t| + t.string "owner_type" + t.integer "owner_id" + t.string "processor", null: false + t.string "processor_id", null: false + t.integer "amount", null: false + t.integer "amount_refunded" + t.string "card_type" + t.string "card_last4" + t.string "card_exp_month" + t.string "card_exp_year" + t.datetime "created_at" + t.datetime "updated_at" + t.json "data" + t.string "currency" + t.integer "application_fee_amount" + t.integer "pay_subscription_id" + end + + create_table "pay_subscriptions", force: :cascade do |t| + t.string "owner_type" + t.integer "owner_id" + t.string "name", null: false + t.string "processor", null: false + t.string "processor_id", null: false + t.string "processor_plan", null: false + t.integer "quantity", default: 1, null: false + t.datetime "trial_ends_at" + t.datetime "ends_at" + t.datetime "created_at" + t.datetime "updated_at" + t.string "status" + t.json "data" + t.decimal "application_fee_percent", precision: 8, scale: 2 + end + + create_table "posts", force: :cascade do |t| + t.string "address_line_1" + t.string "address_line_2" + t.string "address_city" + t.string "address_state" + t.string "address_zipcode" + t.string "payment", null: false + t.boolean "priority", default: false + t.boolean "success", default: true + t.integer "sender_id", null: false + t.integer "recipient_id", null: false + t.integer "letter_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["letter_id"], name: "index_posts_on_letter_id" + t.index ["recipient_id"], name: "index_posts_on_recipient_id" + t.index ["sender_id"], name: "index_posts_on_sender_id" + end + + create_table "recipients", force: :cascade do |t| + t.string "name" + t.string "position" + t.string "level" + t.string "district" + t.string "state" + t.integer "number_fax" + t.integer "number_phone" + t.string "email_address" + t.string "address_line_1" + t.string "address_line_2" + t.string "address_city" + t.string "address_state" + t.string "address_zipcode" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "contact_form" + t.boolean "retired", default: false + end + + create_table "senders", force: :cascade do |t| + t.string "name" + t.integer "zipcode" + t.string "county" + t.string "district" + t.string "state" + t.integer "user_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "email" + t.index ["user_id"], name: "index_senders_on_user_id" + end + + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "password_digest" + t.string "remember_digest" + t.boolean "admin", default: false + t.string "activation_digest" + t.boolean "activated", default: false + t.datetime "activated_at" + t.string "reset_digest" + t.datetime "reset_sent_at" + t.index ["email"], name: "index_users_on_email", unique: true + end + + add_foreign_key "emails", "letters" + add_foreign_key "emails", "recipients" + add_foreign_key "emails", "senders" + add_foreign_key "faxes", "letters" + add_foreign_key "faxes", "recipients" + add_foreign_key "faxes", "senders" + add_foreign_key "letters", "users" + add_foreign_key "posts", "letters" + add_foreign_key "posts", "recipients" + add_foreign_key "posts", "senders" + add_foreign_key "senders", "users" +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..ac2ffe3 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,28 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) +# Create a main sample user. +User.create!(name: "Austin Walters", + email: "austin@example.com", + password: "1password", + password_confirmation: "1password", + admin: true, + activated: true, + activated_at: Time.zone.now) + +# Generate a bunch of additional users. +99.times do |n| + name = Faker::Name.name + email = "example-#{n+1}@railstutorial.org" + password = "password" + User.create!(name: name, + email: email, + password: password, + password_confirmation: password, + activated: true, + activated_at: Time.zone.now) +end diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000..e69de29 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..de4d826 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "vocalvoters", + "private": true, + "dependencies": { + "@rails/actioncable": "^6.0.0", + "@rails/activestorage": "^6.0.0", + "@rails/ujs": "^6.0.0", + "@rails/webpacker": "5.4.0", + "bootstrap": "3.4.1", + "jquery": "3.5.1", + "turbolinks": "^5.2.0", + "webpack": "^4.46.0", + "webpack-cli": "^3.3.12" + }, + "version": "0.1.0", + "devDependencies": { + "webpack-dev-server": "^3.11.2" + } +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..2be3af2 --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
    +
    +

    The page you were looking for doesn't exist.

    +

    You may have mistyped the address or the page may have moved.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..c08eac0 --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
    +
    +

    The change you wanted was rejected.

    +

    Maybe you tried to change something you didn't have access to.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..78a030a --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
    +
    +

    We're sorry, but something went wrong.

    +
    +

    If you are the application owner check the logs for more information.

    +
    + + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..e69de29 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..c19f78a --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/storage/.keep b/storage/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000..d19212a --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/test/channels/application_cable/connection_test.rb b/test/channels/application_cable/connection_test.rb new file mode 100644 index 0000000..800405f --- /dev/null +++ b/test/channels/application_cable/connection_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/controllers/account_activations_controller_test.rb b/test/controllers/account_activations_controller_test.rb new file mode 100644 index 0000000..bcd2199 --- /dev/null +++ b/test/controllers/account_activations_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AccountActivationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/emails_controller_test.rb b/test/controllers/emails_controller_test.rb new file mode 100644 index 0000000..56e1e53 --- /dev/null +++ b/test/controllers/emails_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class EmailsControllerTest < ActionDispatch::IntegrationTest + setup do + @email = emails(:one) + end + + test "should get index" do + get emails_url + assert_response :success + end + + test "should get new" do + get new_email_url + assert_response :success + end + + test "should create email" do + assert_difference('Email.count') do + post emails_url, params: { email: { email_address: @email.email_address, letter_id: @email.letter_id, payment_id: @email.payment_id, recipient_id: @email.recipient_id, sender_id: @email.sender_id } } + end + + assert_redirected_to email_url(Email.last) + end + + test "should show email" do + get email_url(@email) + assert_response :success + end + + test "should get edit" do + get edit_email_url(@email) + assert_response :success + end + + test "should update email" do + patch email_url(@email), params: { email: { email_address: @email.email_address, letter_id: @email.letter_id, payment_id: @email.payment_id, recipient_id: @email.recipient_id, sender_id: @email.sender_id } } + assert_redirected_to email_url(@email) + end + + test "should destroy email" do + assert_difference('Email.count', -1) do + delete email_url(@email) + end + + assert_redirected_to emails_url + end +end diff --git a/test/controllers/faxes_controller_test.rb b/test/controllers/faxes_controller_test.rb new file mode 100644 index 0000000..a2e35d2 --- /dev/null +++ b/test/controllers/faxes_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class FaxesControllerTest < ActionDispatch::IntegrationTest + setup do + @fax = faxes(:one) + end + + test "should get index" do + get faxes_url + assert_response :success + end + + test "should get new" do + get new_fax_url + assert_response :success + end + + test "should create fax" do + assert_difference('Fax.count') do + post faxes_url, params: { fax: { letter_id: @fax.letter_id, number_fax: @fax.number_fax, payment_id: @fax.payment_id, recipient_id: @fax.recipient_id, sender_id: @fax.sender_id } } + end + + assert_redirected_to fax_url(Fax.last) + end + + test "should show fax" do + get fax_url(@fax) + assert_response :success + end + + test "should get edit" do + get edit_fax_url(@fax) + assert_response :success + end + + test "should update fax" do + patch fax_url(@fax), params: { fax: { letter_id: @fax.letter_id, number_fax: @fax.number_fax, payment_id: @fax.payment_id, recipient_id: @fax.recipient_id, sender_id: @fax.sender_id } } + assert_redirected_to fax_url(@fax) + end + + test "should destroy fax" do + assert_difference('Fax.count', -1) do + delete fax_url(@fax) + end + + assert_redirected_to faxes_url + end +end diff --git a/test/controllers/letters_controller_test.rb b/test/controllers/letters_controller_test.rb new file mode 100644 index 0000000..320b998 --- /dev/null +++ b/test/controllers/letters_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class LettersControllerTest < ActionDispatch::IntegrationTest + setup do + @letter = letters(:letter_one) + end + + test "should get index" do + get letters_url + assert_response :success + end + + test "should get new" do + get new_letter_url + assert_response :success + end + + test "should create letter" do + assert_difference('Letter.count') do + post letters_url, params: { letter: { body: @letter.body, category: @letter.category, policy_or_law: @letter.policy_or_law, sentiment: @letter.sentiment, user_id: @letter.user_id } } + end + + assert_redirected_to letter_url(Letter.last) + end + + test "should show letter" do + get letter_url(@letter) + assert_response :success + end + + test "should get edit" do + get edit_letter_url(@letter) + assert_response :success + end + + test "should update letter" do + patch letter_url(@letter), params: { letter: { body: @letter.body, category: @letter.category, policy_or_law: @letter.policy_or_law, sentiment: @letter.sentiment, user_id: @letter.user_id } } + assert_redirected_to letter_url(@letter) + end + + test "should destroy letter" do + assert_difference('Letter.count', -1) do + delete letter_url(@letter) + end + + assert_redirected_to letters_url + end +end diff --git a/test/controllers/payments_controller_test.rb b/test/controllers/payments_controller_test.rb new file mode 100644 index 0000000..c5e86fa --- /dev/null +++ b/test/controllers/payments_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class PaymentsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb new file mode 100644 index 0000000..b7ce839 --- /dev/null +++ b/test/controllers/posts_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class PostsControllerTest < ActionDispatch::IntegrationTest + setup do + @post = posts(:one) + end + + test "should get index" do + get posts_url + assert_response :success + end + + test "should get new" do + get new_post_url + assert_response :success + end + + test "should create post" do + assert_difference('Post.count') do + post posts_url, params: { post: { address_city: @post.address_city, address_line_1: @post.address_line_1, address_line_2: @post.address_line_2, address_state: @post.address_state, address_zipcode: @post.address_zipcode, letter_id: @post.letter_id, payment_id: @post.payment_id, recipient_id: @post.recipient_id, sender_id: @post.sender_id } } + end + + assert_redirected_to post_url(Post.last) + end + + test "should show post" do + get post_url(@post) + assert_response :success + end + + test "should get edit" do + get edit_post_url(@post) + assert_response :success + end + + test "should update post" do + patch post_url(@post), params: { post: { address_city: @post.address_city, address_line_1: @post.address_line_1, address_line_2: @post.address_line_2, address_state: @post.address_state, address_zipcode: @post.address_zipcode, letter_id: @post.letter_id, payment_id: @post.payment_id, recipient_id: @post.recipient_id, sender_id: @post.sender_id } } + assert_redirected_to post_url(@post) + end + + test "should destroy post" do + assert_difference('Post.count', -1) do + delete post_url(@post) + end + + assert_redirected_to posts_url + end +end diff --git a/test/controllers/recipients_controller_test.rb b/test/controllers/recipients_controller_test.rb new file mode 100644 index 0000000..9c9b1f7 --- /dev/null +++ b/test/controllers/recipients_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class RecipientsControllerTest < ActionDispatch::IntegrationTest + setup do + @recipient = recipients(:one) + end + + test "should get index" do + get recipients_url + assert_response :success + end + + test "should get new" do + get new_recipient_url + assert_response :success + end + + test "should create recipient" do + assert_difference('Recipient.count') do + post recipients_url, params: { recipient: { address_city: @recipient.address_city, address_line_1: @recipient.address_line_1, address_line_2: @recipient.address_line_2, address_state: @recipient.address_state, address_zipcode: @recipient.address_zipcode, district: @recipient.district, email_address: @recipient.email_address, level: @recipient.level, name: @recipient.name, number_fax: @recipient.number_fax, number_phone: @recipient.number_phone, position: @recipient.position, state: @recipient.state } } + end + + assert_redirected_to recipient_url(Recipient.last) + end + + test "should show recipient" do + get recipient_url(@recipient) + assert_response :success + end + + test "should get edit" do + get edit_recipient_url(@recipient) + assert_response :success + end + + test "should update recipient" do + patch recipient_url(@recipient), params: { recipient: { address_city: @recipient.address_city, address_line_1: @recipient.address_line_1, address_line_2: @recipient.address_line_2, address_state: @recipient.address_state, address_zipcode: @recipient.address_zipcode, district: @recipient.district, email_address: @recipient.email_address, level: @recipient.level, name: @recipient.name, number_fax: @recipient.number_fax, number_phone: @recipient.number_phone, position: @recipient.position, state: @recipient.state } } + assert_redirected_to recipient_url(@recipient) + end + + test "should destroy recipient" do + assert_difference('Recipient.count', -1) do + delete recipient_url(@recipient) + end + + assert_redirected_to recipients_url + end +end diff --git a/test/controllers/send_communication_controller_test.rb b/test/controllers/send_communication_controller_test.rb new file mode 100644 index 0000000..7f9fbd6 --- /dev/null +++ b/test/controllers/send_communication_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class SendCommunicationControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/senders_controller_test.rb b/test/controllers/senders_controller_test.rb new file mode 100644 index 0000000..84ac455 --- /dev/null +++ b/test/controllers/senders_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class SendersControllerTest < ActionDispatch::IntegrationTest + setup do + @sender = senders(:one) + end + + test "should get index" do + get senders_url + assert_response :success + end + + test "should get new" do + get new_sender_url + assert_response :success + end + + test "should create sender" do + assert_difference('Sender.count') do + post senders_url, params: { sender: { county: @sender.county, district: @sender.district, name: @sender.name, state: @sender.state, user_id: @sender.user_id, zipcode: @sender.zipcode } } + end + + assert_redirected_to sender_url(Sender.last) + end + + test "should show sender" do + get sender_url(@sender) + assert_response :success + end + + test "should get edit" do + get edit_sender_url(@sender) + assert_response :success + end + + test "should update sender" do + patch sender_url(@sender), params: { sender: { county: @sender.county, district: @sender.district, name: @sender.name, state: @sender.state, user_id: @sender.user_id, zipcode: @sender.zipcode } } + assert_redirected_to sender_url(@sender) + end + + test "should destroy sender" do + assert_difference('Sender.count', -1) do + delete sender_url(@sender) + end + + assert_redirected_to senders_url + end +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000..811a08d --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + test "should get new" do + get login_path + assert_response :success + end +end diff --git a/test/controllers/static_pages_controller_test.rb b/test/controllers/static_pages_controller_test.rb new file mode 100644 index 0000000..b63391b --- /dev/null +++ b/test/controllers/static_pages_controller_test.rb @@ -0,0 +1,33 @@ +require "test_helper" + +class StaticPagesControllerTest < ActionDispatch::IntegrationTest + + def setup + @base_title = "VocalVoters" + end + + test "should get root" do + get root_url + assert_response :success + assert_select "title", "Home | #{@base_title}" + end + + test "should get help" do + get help_path + assert_response :success + assert_select "title", "Help | #{@base_title}" + end + + test "should get about" do + get about_path + assert_response :success + assert_select "title", "About | #{@base_title}" + end + + test "should get contact" do + get contact_path + assert_response :success + assert_select "title", "Contact | #{@base_title}" + end + +end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb new file mode 100644 index 0000000..ae7f999 --- /dev/null +++ b/test/controllers/users_controller_test.rb @@ -0,0 +1,73 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + @other_user = users(:archer) + end + + test "should get new" do + get signup_path + assert_response :success + end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end + + test "should redirect edit when not logged in" do + get edit_user_path(@user) + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should redirect update when not logged in" do + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert_not flash.empty? + assert_redirected_to login_url + end + + test "should not allow the admin attribute to be edited via the web" do + log_in_as(@other_user) + assert_not @other_user.admin? + patch user_path(@other_user), params: { + user: { password: "password", + password_confirmation: "password", + admin: true } } + assert_not @other_user.admin? + end + + test "should redirect edit when logged in as wrong user" do + log_in_as(@other_user) + get edit_user_path(@user) + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect update when logged in as wrong user" do + log_in_as(@other_user) + patch user_path(@user), params: { user: { name: @user.name, + email: @user.email } } + assert flash.empty? + assert_redirected_to root_url + end + + test "should redirect destroy when not logged in" do + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_redirected_to login_url + end + + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference 'User.count' do + delete user_path(@user) + end + assert_redirected_to root_url + end + +end diff --git a/test/fixtures/emails.yml b/test/fixtures/emails.yml new file mode 100644 index 0000000..4744bb3 --- /dev/null +++ b/test/fixtures/emails.yml @@ -0,0 +1,15 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + email_address: MyString + sender: one + recipient: one + letter: one + payment: one + +two: + email_address: MyString + sender: two + recipient: two + letter: two + payment: two diff --git a/test/fixtures/faxes.yml b/test/fixtures/faxes.yml new file mode 100644 index 0000000..f7da01f --- /dev/null +++ b/test/fixtures/faxes.yml @@ -0,0 +1,15 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + number_fax: 1 + sender: one + recipient: one + letter: one + payment: one + +two: + number_fax: 1 + sender: two + recipient: two + letter: two + payment: two diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/letters.yml b/test/fixtures/letters.yml new file mode 100644 index 0000000..d4d9c07 --- /dev/null +++ b/test/fixtures/letters.yml @@ -0,0 +1,26 @@ + +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +letter_one: + category: Guns + policy_or_law: 13A-11-7 + tags: guns + sentiment: 1.0 + body: This is my gun. Come and take it. + user: one + +letter_two: + category: Guns + policy_or_law: 14A-12-5 + tags: guns + sentiment: 0.5 + body: I don't like guns, but support peoples right. + user: two + +letter_three: + category: Voting + policy_or_law: HR1 + tags: voting + sentiment: 1.0 + body: I'm pro voting year round! + user: two diff --git a/test/fixtures/posts.yml b/test/fixtures/posts.yml new file mode 100644 index 0000000..f5131cf --- /dev/null +++ b/test/fixtures/posts.yml @@ -0,0 +1,23 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + address_line_1: MyString + address_line_2: MyString + address_city: MyString + address_state: MyString + address_zipcode: MyString + sender: one + recipient: one + letter: one + payment: one + +two: + address_line_1: MyString + address_line_2: MyString + address_city: MyString + address_state: MyString + address_zipcode: MyString + sender: two + recipient: two + letter: two + payment: two diff --git a/test/fixtures/recipients.yml b/test/fixtures/recipients.yml new file mode 100644 index 0000000..b8201fe --- /dev/null +++ b/test/fixtures/recipients.yml @@ -0,0 +1,31 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + position: MyString + level: MyString + district: MyString + state: MyString + number_fax: 1 + number_phone: 1 + email_address: MyString + address_line_1: MyString + address_line_2: MyString + address_city: MyString + address_state: MyString + address_zipcode: MyString + +two: + name: MyString + position: MyString + level: MyString + district: MyString + state: MyString + number_fax: 1 + number_phone: 1 + email_address: MyString + address_line_1: MyString + address_line_2: MyString + address_city: MyString + address_state: MyString + address_zipcode: MyString diff --git a/test/fixtures/senders.yml b/test/fixtures/senders.yml new file mode 100644 index 0000000..48be94b --- /dev/null +++ b/test/fixtures/senders.yml @@ -0,0 +1,17 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + zipcode: 1 + county: MyString + district: MyString + state: MyString + user: one + +two: + name: MyString + zipcode: 1 + county: MyString + district: MyString + state: MyString + user: two diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..0dbf1dc --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,44 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +michael: + name: Michael Example + email: michael@example.com + password_digest: <%= User.digest('password') %> + admin: true + activated: true + activated_at: <%= Time.zone.now %> + +archer: + name: Sterling Archer + email: duchess@example.gov + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> + +inactive: + name: Inactive User + email: inactive@example.com + password_digest: <%= User.digest('password') %> + admin: false + activated: false + +one: + name: name1 + email: name1@email.com + activated: true + activated_at: <%= Time.zone.now %> + +two: + name: name2 + email: name2@email.com + activated: true + activated_at: <%= Time.zone.now %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + activated: true + activated_at: <%= Time.zone.now %> +<% end %> \ No newline at end of file diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb new file mode 100644 index 0000000..32b6fe7 --- /dev/null +++ b/test/helpers/application_helper_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class ApplicationHelperTest < ActionView::TestCase + test "full title helper" do + assert_equal full_title, "VocalVoters" + assert_equal full_title("Help"), "Help | VocalVoters" + end +end diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/password_resets_test.rb b/test/integration/password_resets_test.rb new file mode 100644 index 0000000..a342962 --- /dev/null +++ b/test/integration/password_resets_test.rb @@ -0,0 +1,80 @@ +require "test_helper" + +class PasswordResetsTest < ActionDispatch::IntegrationTest + + def setup + ActionMailer::Base.deliveries.clear + @user = users(:michael) + end + + test "password resets" do + get new_password_reset_path + assert_template 'password_resets/new' + assert_select 'input[name=?]', 'password_reset[email]' + # Invalid email + post password_resets_path, params: { password_reset: { email: "" } } + assert_not flash.empty? + assert_template 'password_resets/new' + # Valid email + post password_resets_path, + params: { password_reset: { email: @user.email } } + assert_not_equal @user.reset_digest, @user.reload.reset_digest + assert_equal 1, ActionMailer::Base.deliveries.size + assert_not flash.empty? + assert_redirected_to root_url + # Password reset form + user = assigns(:user) + # Wrong email + get edit_password_reset_path(user.reset_token, email: "") + assert_redirected_to root_url + # Inactive user + user.toggle!(:activated) + get edit_password_reset_path(user.reset_token, email: user.email) + assert_redirected_to root_url + user.toggle!(:activated) + # Right email, wrong token + get edit_password_reset_path('wrong token', email: user.email) + assert_redirected_to root_url + # Right email, right token + get edit_password_reset_path(user.reset_token, email: user.email) + assert_template 'password_resets/edit' + assert_select "input[name=email][type=hidden][value=?]", user.email + # Invalid password & confirmation + patch password_reset_path(user.reset_token), + params: { email: user.email, + user: { password: "foobaz", + password_confirmation: "barquux" } } + assert_select 'div#error_explanation' + # Empty password + patch password_reset_path(user.reset_token), + params: { email: user.email, + user: { password: "", + password_confirmation: "" } } + assert_select 'div#error_explanation' + # Valid password & confirmation + patch password_reset_path(user.reset_token), + params: { email: user.email, + user: { password: "foobaz", + password_confirmation: "foobaz" } } + assert is_logged_in? + assert_not flash.empty? + assert_redirected_to user + end + + test "expired token" do + get new_password_reset_path + post password_resets_path, + params: { password_reset: { email: @user.email } } + + @user = assigns(:user) + @user.update_attribute(:reset_sent_at, 3.hours.ago) + patch password_reset_path(@user.reset_token), + params: { email: @user.email, + user: { password: "foobar", + password_confirmation: "foobar" } } + assert_response :redirect + follow_redirect! + assert_match /expired/i, response.body + end + +end diff --git a/test/integration/site_layout_test.rb b/test/integration/site_layout_test.rb new file mode 100644 index 0000000..996579d --- /dev/null +++ b/test/integration/site_layout_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class SiteLayoutTest < ActionDispatch::IntegrationTest + + test "layout links" do + get root_path + assert_template 'static_pages/home' + assert_select "a[href=?]", root_path, count: 2 + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + get contact_path + assert_select "title", full_title("Contact") + end + +end diff --git a/test/integration/users_edit_test.rb b/test/integration/users_edit_test.rb new file mode 100644 index 0000000..1c57c35 --- /dev/null +++ b/test/integration/users_edit_test.rb @@ -0,0 +1,55 @@ +require 'test_helper' + +class UsersEditTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "unsuccessful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + patch user_path(@user), params: { user: { name: "", + email: "foo@invalid", + password: "foo", + password_confirmation: "bar" } } + + assert_template 'users/edit' + end + + test "successful edit" do + log_in_as(@user) + get edit_user_path(@user) + assert_template 'users/edit' + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end + + test "successful edit with friendly forwarding" do + get edit_user_path(@user) + log_in_as(@user) + assert_redirected_to edit_user_url(@user) + name = "Foo Bar" + email = "foo@bar.com" + patch user_path(@user), params: { user: { name: name, + email: email, + password: "", + password_confirmation: "" } } + assert_not flash.empty? + assert_redirected_to @user + @user.reload + assert_equal name, @user.name + assert_equal email, @user.email + end + +end diff --git a/test/integration/users_index_test.rb b/test/integration/users_index_test.rb new file mode 100644 index 0000000..cbafd90 --- /dev/null +++ b/test/integration/users_index_test.rb @@ -0,0 +1,35 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:michael) + @non_admin = users(:archer) + end + + test "index as admin including pagination and delete links" do + log_in_as(@admin) + first_page_of_users = User.paginate(page: 1) + first_page_of_users.first.toggle!(:activated) + get users_path + assert_template 'users/index' + assert_select 'div.pagination' + assigns(:users).each do |user| + assert user.activated? + assert_select 'a[href=?]', user_path(user), text: user.name + unless user == @admin + assert_select 'a[href=?]', user_path(user), text: 'delete' + end + end + assert_difference 'User.count', -1 do + delete user_path(@non_admin) + end + end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_select 'a', text: 'delete', count: 0 + end + +end diff --git a/test/integration/users_login_test.rb b/test/integration/users_login_test.rb new file mode 100644 index 0000000..28a3648 --- /dev/null +++ b/test/integration/users_login_test.rb @@ -0,0 +1,56 @@ +require "test_helper" + +class UsersLoginTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:michael) + end + + test "login with valid email/invalid password" do + get login_path + assert_template 'sessions/new' + post login_path, params: { session: { email: @user.email, + password: "invalid" } } + assert_not is_logged_in? + assert_template 'sessions/new' + assert_not flash.empty? + get root_path + assert flash.empty? + end + + test "login with valid information followed by logout" do + get login_path + post login_path, params: { session: { email: @user.email, + password: 'password' } } + assert is_logged_in? + assert_redirected_to @user + follow_redirect! + assert_template 'users/show' + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", logout_path + assert_select "a[href=?]", user_path(@user) + delete logout_path + assert_not is_logged_in? + assert_redirected_to root_url + # Simulate a user clicking logout in a second window. + delete logout_path + follow_redirect! + assert_select "a[href=?]", login_path + assert_select "a[href=?]", logout_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + end + + test "login with remembering" do + log_in_as(@user, remember_me: '1') + assert_not cookies[:remember_token].blank? + end + + test "login without remembering" do + # Log in to set the cookie. + log_in_as(@user, remember_me: '1') + # Log in again and verify that the cookie is deleted. + log_in_as(@user, remember_me: '0') + assert cookies[:remember_token].blank? + end + +end diff --git a/test/integration/users_show_test.rb b/test/integration/users_show_test.rb new file mode 100644 index 0000000..6777616 --- /dev/null +++ b/test/integration/users_show_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +class UsersShowTest < ActionDispatch::IntegrationTest + + def setup + @inactive_user = users(:inactive) + @activated_user = users(:archer) + end + + test "should redirect when user not activated" do + get user_path(@inactive_user) + assert_response :redirect + assert_redirected_to root_url + end + + test "should display user when activated" do + get user_path(@activated_user) + assert_response :success + assert_template 'users/show' + end +end diff --git a/test/integration/users_signup_test.rb b/test/integration/users_signup_test.rb new file mode 100644 index 0000000..2e40259 --- /dev/null +++ b/test/integration/users_signup_test.rb @@ -0,0 +1,51 @@ +require "test_helper" + +class UsersSignupTest < ActionDispatch::IntegrationTest + + + def setup + ActionMailer::Base.deliveries.clear + end + + test "invalid signup information" do + get signup_path + assert_no_difference 'User.count' do + post users_path, params: { user: { name: "", + email: "user@invalid", + password: "foo", + password_confirmation: "bar" } } + end + assert_template 'users/new' + assert_select 'div#error_explanation' + assert_select 'div.alert-danger' + end + + test "valid signup information with account activation" do + get signup_path + assert_difference 'User.count', 1 do + post users_path, params: { user: { name: "Example User", + email: "user@example.com", + password: "password", + password_confirmation: "password" } } + end + assert_equal 1, ActionMailer::Base.deliveries.size + user = assigns(:user) + assert_not user.activated? + # Try to log in before activation. + log_in_as(user) + assert_not is_logged_in? + # Invalid activation token + get edit_account_activation_path("invalid token", email: user.email) + assert_not is_logged_in? + # Valid token, wrong email + get edit_account_activation_path(user.activation_token, email: 'wrong') + assert_not is_logged_in? + # Valid activation token + get edit_account_activation_path(user.activation_token, email: user.email) + assert user.reload.activated? + follow_redirect! + assert_template 'users/show' + assert is_logged_in? + end + +end diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/mailers/previews/user_mailer_preview.rb b/test/mailers/previews/user_mailer_preview.rb new file mode 100644 index 0000000..e787797 --- /dev/null +++ b/test/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,20 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/account_activation + def account_activation + user = User.first + user.activation_token = User.new_token + UserMailer.account_activation(user) + end + + # Preview this email at + # http://localhost:3000/rails/mailers/user_mailer/password_reset + def password_reset + user = User.first + user.reset_token = User.new_token + UserMailer.password_reset(user) + end + +end diff --git a/test/mailers/user_mailer_test.rb b/test/mailers/user_mailer_test.rb new file mode 100644 index 0000000..0db08c4 --- /dev/null +++ b/test/mailers/user_mailer_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +class UserMailerTest < ActionMailer::TestCase + + test "account_activation" do + user = users(:michael) + user.activation_token = User.new_token + mail = UserMailer.account_activation(user) + assert_equal "Account activation", mail.subject + assert_equal [user.email], mail.to + assert_equal ["noreply@vocalvoters.com"], mail.from + assert_match user.name, mail.body.encoded + assert_match user.activation_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end + + test "password_reset" do + user = users(:michael) + user.reset_token = User.new_token + mail = UserMailer.password_reset(user) + assert_equal "Password reset", mail.subject + assert_equal [user.email], mail.to + assert_equal ["noreply@vocalvoters.com"], mail.from + assert_match user.reset_token, mail.body.encoded + assert_match CGI.escape(user.email), mail.body.encoded + end + +end diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/models/email_test.rb b/test/models/email_test.rb new file mode 100644 index 0000000..a07b515 --- /dev/null +++ b/test/models/email_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class EmailTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/fax_test.rb b/test/models/fax_test.rb new file mode 100644 index 0000000..765dd4f --- /dev/null +++ b/test/models/fax_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class FaxTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/letter_test.rb b/test/models/letter_test.rb new file mode 100644 index 0000000..d2fcfec --- /dev/null +++ b/test/models/letter_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class LetterTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/post_test.rb b/test/models/post_test.rb new file mode 100644 index 0000000..ff155c4 --- /dev/null +++ b/test/models/post_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class PostTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/recipient_test.rb b/test/models/recipient_test.rb new file mode 100644 index 0000000..2bcf5d6 --- /dev/null +++ b/test/models/recipient_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class RecipientTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/sender_test.rb b/test/models/sender_test.rb new file mode 100644 index 0000000..a0c5c85 --- /dev/null +++ b/test/models/sender_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class SenderTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 0000000..bd3b01a --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,79 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + + def setup + @user = User.new(name: "Example User", email: "user@example.com", + password: "foobar", password_confirmation: "foobar") + end + + test "should be valid" do + assert @user.valid? + end + + test "name should be present" do + @user.name = " " + assert_not @user.valid? + end + + test "email should be present" do + @user.email = " " + assert_not @user.valid? + end + + test "name should not be too long" do + @user.name = "a" * 51 + assert_not @user.valid? + end + + test "email should not be too long" do + @user.email = "a" * 244 + "@example.com" + assert_not @user.valid? + end + + test "email validation should accept valid addresses" do + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org + first.last@foo.jp alice+bob@baz.cn] + valid_addresses.each do |valid_address| + @user.email = valid_address + assert @user.valid?, "#{valid_address.inspect} should be valid" + end + end + + test "email validation should reject invalid addresses" do + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. + foo@bar_baz.com foo@bar+baz.com] + invalid_addresses.each do |invalid_address| + @user.email = invalid_address + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" + end + end + + test "email addresses should be unique" do + duplicate_user = @user.dup + @user.save + assert_not duplicate_user.valid? + end + + test "email addresses should be saved as lower-case" do + mixed_case_email = "Foo@ExAMPle.CoM" + @user.email = mixed_case_email + @user.save + assert_equal mixed_case_email.downcase, @user.reload.email + end + + test "password should be present (nonblank)" do + @user.password = @user.password_confirmation = " " * 6 + assert_not @user.valid? + end + + test "password should have a minimum length" do + @user.password = @user.password_confirmation = "a" * 5 + assert_not @user.valid? + end + + test "authenticated? should return false for a user with nil digest" do + assert_not @user.authenticated?(:remember, '') + end + +end diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/system/emails_test.rb b/test/system/emails_test.rb new file mode 100644 index 0000000..bb1576d --- /dev/null +++ b/test/system/emails_test.rb @@ -0,0 +1,51 @@ +require "application_system_test_case" + +class EmailsTest < ApplicationSystemTestCase + setup do + @email = emails(:one) + end + + test "visiting the index" do + visit emails_url + assert_selector "h1", text: "Emails" + end + + test "creating a Email" do + visit emails_url + click_on "New Email" + + fill_in "Email address", with: @email.email_address + fill_in "Letter", with: @email.letter_id + fill_in "Payment", with: @email.payment_id + fill_in "Recipient", with: @email.recipient_id + fill_in "Sender", with: @email.sender_id + click_on "Create Email" + + assert_text "Email was successfully created" + click_on "Back" + end + + test "updating a Email" do + visit emails_url + click_on "Edit", match: :first + + fill_in "Email address", with: @email.email_address + fill_in "Letter", with: @email.letter_id + fill_in "Payment", with: @email.payment_id + fill_in "Recipient", with: @email.recipient_id + fill_in "Sender", with: @email.sender_id + click_on "Update Email" + + assert_text "Email was successfully updated" + click_on "Back" + end + + test "destroying a Email" do + visit emails_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "Email was successfully destroyed" + end +end diff --git a/test/system/faxes_test.rb b/test/system/faxes_test.rb new file mode 100644 index 0000000..e4aa35f --- /dev/null +++ b/test/system/faxes_test.rb @@ -0,0 +1,51 @@ +require "application_system_test_case" + +class FaxesTest < ApplicationSystemTestCase + setup do + @fax = faxes(:one) + end + + test "visiting the index" do + visit faxes_url + assert_selector "h1", text: "Faxes" + end + + test "creating a Fax" do + visit faxes_url + click_on "New Fax" + + fill_in "Letter", with: @fax.letter_id + fill_in "Number fax", with: @fax.number_fax + fill_in "Payment", with: @fax.payment_id + fill_in "Recipient", with: @fax.recipient_id + fill_in "Sender", with: @fax.sender_id + click_on "Create Fax" + + assert_text "Fax was successfully created" + click_on "Back" + end + + test "updating a Fax" do + visit faxes_url + click_on "Edit", match: :first + + fill_in "Letter", with: @fax.letter_id + fill_in "Number fax", with: @fax.number_fax + fill_in "Payment", with: @fax.payment_id + fill_in "Recipient", with: @fax.recipient_id + fill_in "Sender", with: @fax.sender_id + click_on "Update Fax" + + assert_text "Fax was successfully updated" + click_on "Back" + end + + test "destroying a Fax" do + visit faxes_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "Fax was successfully destroyed" + end +end diff --git a/test/system/letters_test.rb b/test/system/letters_test.rb new file mode 100644 index 0000000..d8b7c35 --- /dev/null +++ b/test/system/letters_test.rb @@ -0,0 +1,51 @@ +require "application_system_test_case" + +class LettersTest < ApplicationSystemTestCase + setup do + @letter = letters(:letter_one) + end + + test "visiting the index" do + visit letters_url + assert_selector "h1", text: "Letters" + end + + test "creating a Letter" do + visit letters_url + click_on "New Letter" + + fill_in "Body", with: @letter.body + fill_in "Category", with: @letter.category + fill_in "Policy or law", with: @letter.policy_or_law + fill_in "Sentiment", with: @letter.sentiment + fill_in "User", with: @letter.user_id + click_on "Create Letter" + + assert_text "Letter was successfully created" + click_on "Back" + end + + test "updating a Letter" do + visit letters_url + click_on "Edit", match: :first + + fill_in "Body", with: @letter.body + fill_in "Category", with: @letter.category + fill_in "Policy or law", with: @letter.policy_or_law + fill_in "Sentiment", with: @letter.sentiment + fill_in "User", with: @letter.user_id + click_on "Update Letter" + + assert_text "Letter was successfully updated" + click_on "Back" + end + + test "destroying a Letter" do + visit letters_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "Letter was successfully destroyed" + end +end diff --git a/test/system/posts_test.rb b/test/system/posts_test.rb new file mode 100644 index 0000000..e3d0519 --- /dev/null +++ b/test/system/posts_test.rb @@ -0,0 +1,59 @@ +require "application_system_test_case" + +class PostsTest < ApplicationSystemTestCase + setup do + @post = posts(:one) + end + + test "visiting the index" do + visit posts_url + assert_selector "h1", text: "Posts" + end + + test "creating a Post" do + visit posts_url + click_on "New Post" + + fill_in "Address city", with: @post.address_city + fill_in "Address line 1", with: @post.address_line_1 + fill_in "Address line 2", with: @post.address_line_2 + fill_in "Address state", with: @post.address_state + fill_in "Address zipcode", with: @post.address_zipcode + fill_in "Letter", with: @post.letter_id + fill_in "Payment", with: @post.payment_id + fill_in "Recipient", with: @post.recipient_id + fill_in "Sender", with: @post.sender_id + click_on "Create Post" + + assert_text "Post was successfully created" + click_on "Back" + end + + test "updating a Post" do + visit posts_url + click_on "Edit", match: :first + + fill_in "Address city", with: @post.address_city + fill_in "Address line 1", with: @post.address_line_1 + fill_in "Address line 2", with: @post.address_line_2 + fill_in "Address state", with: @post.address_state + fill_in "Address zipcode", with: @post.address_zipcode + fill_in "Letter", with: @post.letter_id + fill_in "Payment", with: @post.payment_id + fill_in "Recipient", with: @post.recipient_id + fill_in "Sender", with: @post.sender_id + click_on "Update Post" + + assert_text "Post was successfully updated" + click_on "Back" + end + + test "destroying a Post" do + visit posts_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "Post was successfully destroyed" + end +end diff --git a/test/system/recipients_test.rb b/test/system/recipients_test.rb new file mode 100644 index 0000000..5a1d5ba --- /dev/null +++ b/test/system/recipients_test.rb @@ -0,0 +1,67 @@ +require "application_system_test_case" + +class RecipientsTest < ApplicationSystemTestCase + setup do + @recipient = recipients(:one) + end + + test "visiting the index" do + visit recipients_url + assert_selector "h1", text: "Recipients" + end + + test "creating a Recipient" do + visit recipients_url + click_on "New Recipient" + + fill_in "Address city", with: @recipient.address_city + fill_in "Address line 1", with: @recipient.address_line_1 + fill_in "Address line 2", with: @recipient.address_line_2 + fill_in "Address state", with: @recipient.address_state + fill_in "Address zipcode", with: @recipient.address_zipcode + fill_in "District", with: @recipient.district + fill_in "Email address", with: @recipient.email_address + fill_in "Level", with: @recipient.level + fill_in "Name", with: @recipient.name + fill_in "Number fax", with: @recipient.number_fax + fill_in "Number phone", with: @recipient.number_phone + fill_in "Position", with: @recipient.position + fill_in "State", with: @recipient.state + click_on "Create Recipient" + + assert_text "Recipient was successfully created" + click_on "Back" + end + + test "updating a Recipient" do + visit recipients_url + click_on "Edit", match: :first + + fill_in "Address city", with: @recipient.address_city + fill_in "Address line 1", with: @recipient.address_line_1 + fill_in "Address line 2", with: @recipient.address_line_2 + fill_in "Address state", with: @recipient.address_state + fill_in "Address zipcode", with: @recipient.address_zipcode + fill_in "District", with: @recipient.district + fill_in "Email address", with: @recipient.email_address + fill_in "Level", with: @recipient.level + fill_in "Name", with: @recipient.name + fill_in "Number fax", with: @recipient.number_fax + fill_in "Number phone", with: @recipient.number_phone + fill_in "Position", with: @recipient.position + fill_in "State", with: @recipient.state + click_on "Update Recipient" + + assert_text "Recipient was successfully updated" + click_on "Back" + end + + test "destroying a Recipient" do + visit recipients_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "Recipient was successfully destroyed" + end +end diff --git a/test/system/senders_test.rb b/test/system/senders_test.rb new file mode 100644 index 0000000..b8fb6ba --- /dev/null +++ b/test/system/senders_test.rb @@ -0,0 +1,53 @@ +require "application_system_test_case" + +class SendersTest < ApplicationSystemTestCase + setup do + @sender = senders(:one) + end + + test "visiting the index" do + visit senders_url + assert_selector "h1", text: "Senders" + end + + test "creating a Sender" do + visit senders_url + click_on "New Sender" + + fill_in "County", with: @sender.county + fill_in "District", with: @sender.district + fill_in "Name", with: @sender.name + fill_in "State", with: @sender.state + fill_in "User", with: @sender.user_id + fill_in "Zipcode", with: @sender.zipcode + click_on "Create Sender" + + assert_text "Sender was successfully created" + click_on "Back" + end + + test "updating a Sender" do + visit senders_url + click_on "Edit", match: :first + + fill_in "County", with: @sender.county + fill_in "District", with: @sender.district + fill_in "Name", with: @sender.name + fill_in "State", with: @sender.state + fill_in "User", with: @sender.user_id + fill_in "Zipcode", with: @sender.zipcode + click_on "Update Sender" + + assert_text "Sender was successfully updated" + click_on "Back" + end + + test "destroying a Sender" do + visit senders_url + page.accept_confirm do + click_on "Destroy", match: :first + end + + assert_text "Sender was successfully destroyed" + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..c5c4897 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,35 @@ +ENV['RAILS_ENV'] ||= 'test' +require_relative "../config/environment" +require "rails/test_help" +require "minitest/reporters" +Minitest::Reporters.use! + +class ActiveSupport::TestCase + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) + + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + include ApplicationHelper + + # Returns true if a test user is logged in. + def is_logged_in? + !session[:user_id].nil? + end + + # Log in as a particular user. + def log_in_as(user) + session[:user_id] = user.id + end + +end + +class ActionDispatch::IntegrationTest + + # Log in as a particular user. + def log_in_as(user, password: 'password', remember_me: '1') + post login_path, params: { session: { email: user.email, + password: password, + remember_me: remember_me } } + end +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000..e69de29 diff --git a/tmp/pids/.keep b/tmp/pids/.keep new file mode 100644 index 0000000..e69de29 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000..e69de29