├── .bummr-build.sh ├── .circleci └── config.yml ├── .codeclimate.yml ├── .dockerignore ├── .foreman.example ├── .gitattributes ├── .gitignore ├── .haml-lint.yml ├── .hound.yml ├── .node-version ├── .reek ├── .reek.yml ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .ruby-gemset ├── .ruby-version ├── .scss-lint.yml ├── .snyk ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── INSTALL.md ├── LICENSE.md ├── Procfile ├── README.md ├── Rakefile ├── VERSION ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── javascripts │ │ ├── admin │ │ │ ├── accepted_payments.js │ │ │ ├── application.js │ │ │ ├── categories_form.js │ │ │ ├── form.js.coffee │ │ │ ├── funding_sources.js │ │ │ ├── languages.js │ │ │ ├── org_autocomplete.js │ │ │ ├── required_documents.js │ │ │ ├── service_areas.js │ │ │ └── tags.js │ │ └── application.js │ └── stylesheets │ │ ├── _variables.scss │ │ ├── admin │ │ └── application.css.scss │ │ ├── application.css.scss │ │ └── framework_and_overrides.css.scss ├── controllers │ ├── admin │ │ ├── contacts_controller.rb │ │ ├── csv_controller.rb │ │ ├── dashboard_controller.rb │ │ ├── locations_controller.rb │ │ ├── organization_contacts_controller.rb │ │ ├── organizations_controller.rb │ │ ├── programs_controller.rb │ │ ├── registrations_controller.rb │ │ ├── service_contacts_controller.rb │ │ └── services_controller.rb │ ├── api │ │ └── v1 │ │ │ ├── address_controller.rb │ │ │ ├── categories_controller.rb │ │ │ ├── contact_phones_controller.rb │ │ │ ├── contacts_controller.rb │ │ │ ├── cors_controller.rb │ │ │ ├── errors_controller.rb │ │ │ ├── location_phones_controller.rb │ │ │ ├── locations_controller.rb │ │ │ ├── mail_address_controller.rb │ │ │ ├── organizations_controller.rb │ │ │ ├── root_controller.rb │ │ │ ├── search_controller.rb │ │ │ ├── services_controller.rb │ │ │ └── status_controller.rb │ ├── api_applications_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ ├── cacheable.rb │ │ ├── custom_errors.rb │ │ ├── pagination_headers.rb │ │ ├── private_registration.rb │ │ ├── taggable.rb │ │ └── token_validator.rb │ ├── home_controller.rb │ └── user │ │ └── registrations_controller.rb ├── helpers │ └── admin │ │ ├── csv_download_helper.rb │ │ └── form_helper.rb ├── mailers │ ├── .gitkeep │ ├── admin_mailer.rb │ ├── application_mailer.rb │ └── custom_mailer.rb ├── models │ ├── .gitkeep │ ├── address.rb │ ├── admin.rb │ ├── api_application.rb │ ├── application_record.rb │ ├── category.rb │ ├── concerns │ │ ├── parent_presence_validatable.rb │ │ └── search.rb │ ├── contact.rb │ ├── holiday_schedule.rb │ ├── location.rb │ ├── mail_address.rb │ ├── nested_categories.rb │ ├── organization.rb │ ├── phone.rb │ ├── program.rb │ ├── regular_schedule.rb │ ├── service.rb │ └── user.rb ├── policies │ ├── application_policy.rb │ ├── contact_policy.rb │ ├── location_policy.rb │ ├── organization_policy.rb │ ├── program_policy.rb │ └── service_policy.rb ├── serializers │ ├── address_serializer.rb │ ├── category_serializer.rb │ ├── contact_serializer.rb │ ├── holiday_schedule_serializer.rb │ ├── location_serializer.rb │ ├── locations_organization_serializer.rb │ ├── locations_serializer.rb │ ├── mail_address_serializer.rb │ ├── nearby_serializer.rb │ ├── organization_serializer.rb │ ├── phone_serializer.rb │ ├── regular_schedule_serializer.rb │ ├── service_serializer.rb │ └── summarized_organization_serializer.rb ├── validators │ ├── array_validator.rb │ ├── date_validator.rb │ ├── email_validator.rb │ ├── pg_array_validator.rb │ ├── phone_validator.rb │ ├── regex_validator.rb │ ├── service_area_validator.rb │ ├── state_province_validator.rb │ ├── url_validator.rb │ ├── weekday_validator.rb │ └── zip_validator.rb └── views │ ├── admin │ ├── contacts │ │ ├── _form.html.haml │ │ ├── edit.html.haml │ │ ├── forms │ │ │ ├── _danger.html.haml │ │ │ ├── _department.html.haml │ │ │ ├── _editing.html.haml │ │ │ ├── _email.html.haml │ │ │ ├── _fields.html.haml │ │ │ ├── _new_contact.html.haml │ │ │ ├── _new_org_contact.html.haml │ │ │ ├── _new_service_contact.html.haml │ │ │ └── _title.html.haml │ │ └── new.html.haml │ ├── csv │ │ ├── addresses.csv.shaper │ │ ├── contacts.csv.shaper │ │ ├── holiday_schedules.csv.shaper │ │ ├── locations.csv.shaper │ │ ├── mail_addresses.csv.shaper │ │ ├── organizations.csv.shaper │ │ ├── phones.csv.shaper │ │ ├── programs.csv.shaper │ │ ├── regular_schedules.csv.shaper │ │ └── services.csv.shaper │ ├── dashboard │ │ └── index.html.haml │ ├── locations │ │ ├── _confirm_delete_location.html.haml │ │ ├── _form.html.haml │ │ ├── edit.html.haml │ │ ├── forms │ │ │ ├── _accessibility.html.haml │ │ │ ├── _address.html.haml │ │ │ ├── _address_fields.html.haml │ │ │ ├── _admin_email_fields.html.haml │ │ │ ├── _admin_email_fields_for_new_location.html.haml │ │ │ ├── _admin_emails.html.haml │ │ │ ├── _description.html.haml │ │ │ ├── _fields.html.haml │ │ │ ├── _holiday_schedule_fields.html.haml │ │ │ ├── _languages.html.haml │ │ │ ├── _mail_address.html.haml │ │ │ ├── _mail_address_fields.html.haml │ │ │ ├── _new_location_form.html.haml │ │ │ ├── _phone_fields.html.haml │ │ │ ├── _phones.html.haml │ │ │ ├── _regular_schedule_fields.html.haml │ │ │ ├── _short_desc.html.haml │ │ │ ├── _transportation.html.haml │ │ │ └── _virtual.html.haml │ │ ├── index.html.haml │ │ └── new.html.haml │ ├── mailer │ │ ├── confirmation_instructions.html.haml │ │ └── reset_password_instructions.html.haml │ ├── organization_contacts │ │ ├── edit.html.haml │ │ └── new.html.haml │ ├── organizations │ │ ├── _confirm_delete_organization.html.haml │ │ ├── _form.html.haml │ │ ├── edit.html.haml │ │ ├── forms │ │ │ ├── _accreditations.html.haml │ │ │ ├── _date_incorporated.html.haml │ │ │ ├── _description.html.haml │ │ │ ├── _fields.html.haml │ │ │ ├── _legal_status.html.haml │ │ │ ├── _licenses.html.haml │ │ │ ├── _new_organization_form.html.haml │ │ │ ├── _tax_id.html.haml │ │ │ └── _tax_status.html.haml │ │ ├── index.html.haml │ │ └── new.html.haml │ ├── programs │ │ ├── _confirm_delete_program.html.haml │ │ ├── _form.html.haml │ │ ├── edit.html.haml │ │ ├── forms │ │ │ ├── _danger.html.haml │ │ │ ├── _editing.html.haml │ │ │ ├── _fields.html.haml │ │ │ └── _new_program_form.html.haml │ │ ├── index.html.haml │ │ └── new.html.haml │ ├── service_contacts │ │ ├── edit.html.haml │ │ └── new.html.haml │ ├── services │ │ ├── _confirm_delete_service.html.haml │ │ ├── _form.html.haml │ │ ├── edit.html.haml │ │ ├── forms │ │ │ ├── _accepted_payments.html.haml │ │ │ ├── _application_process.haml │ │ │ ├── _audience.html.haml │ │ │ ├── _categories.html.haml │ │ │ ├── _description.html.haml │ │ │ ├── _eligibility.html.haml │ │ │ ├── _fees.html.haml │ │ │ ├── _fields.html.haml │ │ │ ├── _interpretation_services.html.haml │ │ │ ├── _keywords.html.haml │ │ │ ├── _languages.html.haml │ │ │ ├── _new_service_form.html.haml │ │ │ ├── _program.html.haml │ │ │ ├── _required_documents.html.haml │ │ │ ├── _service_areas.html.haml │ │ │ ├── _service_locations.html.haml │ │ │ ├── _status.html.haml │ │ │ └── _wait.html.haml │ │ ├── index.html.haml │ │ └── new.html.haml │ └── shared │ │ ├── _navigation.html.haml │ │ ├── _navigation_links.html.haml │ │ └── forms │ │ ├── _alternate_name.html.haml │ │ ├── _choose_org.html.haml │ │ ├── _email.html.haml │ │ ├── _funding_sources.html.haml │ │ ├── _holiday_hours.html.haml │ │ ├── _hours.html.haml │ │ ├── _name.html.haml │ │ └── _website.html.haml │ ├── admin_mailer │ └── existing_email_signup.html.haml │ ├── api_applications │ ├── _form.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ └── new.html.haml │ ├── devise │ ├── confirmations │ │ └── new.html.haml │ ├── mailer │ │ ├── confirmation_instructions.html.haml │ │ └── reset_password_instructions.html.haml │ ├── passwords │ │ ├── edit.html.haml │ │ └── new.html.haml │ ├── registrations │ │ ├── edit.html.haml │ │ └── new.html.haml │ ├── sessions │ │ └── new.html.haml │ └── shared │ │ ├── _error_messages.html.erb │ │ └── _links.html.haml │ ├── home │ └── index.html.haml │ ├── layouts │ ├── admin.html.haml │ └── application.html.haml │ └── shared │ ├── _messages.html.haml │ ├── _navigation.html.haml │ └── _navigation_links.html.haml ├── bin ├── autospec ├── bundle ├── rails ├── rake ├── rspec ├── setup ├── spring ├── update └── yarn ├── categories-in-ohana-api-admin.png ├── config.ru ├── config ├── application.example.yml ├── application.rb ├── boot.rb ├── database.travis.yml ├── database.vagrant.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── active_model_serializers.rb │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── auto_strip_attributes.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── csv_shaper.rb │ ├── devise.rb │ ├── figaro.rb │ ├── filter_parameter_logging.rb │ ├── friendly_id.rb │ ├── generators.rb │ ├── geocoder.rb │ ├── inflections.rb │ ├── kaminari.rb │ ├── kaminari_config.rb │ ├── mime_types.rb │ ├── new_framework_defaults_5_2.rb │ ├── secret_token.rb │ ├── session_store.rb │ ├── version.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── settings.example.yml ├── settings.yml ├── spring.rb └── storage.yml ├── data ├── oe.json ├── ohana_api_development.dump └── sample-csv │ ├── addresses.csv │ ├── contacts.csv │ ├── holiday_schedules.csv │ ├── locations.csv │ ├── mail_addresses.csv │ ├── organizations.csv │ ├── phones.csv │ ├── programs.csv │ ├── regular_schedules.csv │ ├── services.csv │ └── taxonomy.csv ├── db ├── migrate │ ├── 20140328034023_create_addresses.rb │ ├── 20140328034531_create_organizations.rb │ ├── 20140328034754_create_locations.rb │ ├── 20140328035528_create_users.rb │ ├── 20140328041648_create_api_applications.rb │ ├── 20140328041859_create_contacts.rb │ ├── 20140328042108_create_faxes.rb │ ├── 20140328042218_create_mail_addresses.rb │ ├── 20140328042359_create_phones.rb │ ├── 20140328043104_create_services.rb │ ├── 20140328044447_create_categories.rb │ ├── 20140328052427_create_friendly_id_slugs.rb │ ├── 20140402222453_create_categories_services.rb │ ├── 20140404220233_add_ancestry_to_category.rb │ ├── 20140424182454_remove_kind_from_locations.rb │ ├── 20140505011725_add_search_vector_to_locations.rb │ ├── 20140508030435_add_search_indexes_to_locations.rb │ ├── 20140508030926_add_search_index_to_organizations.rb │ ├── 20140508031024_add_search_index_to_categories.rb │ ├── 20140508194831_update_search_vector_migration.rb │ ├── 20140522153640_add_number_type_to_phones.rb │ ├── 20140629181523_devise_create_admins.rb │ ├── 20140630171418_add_super_admin_to_admins.rb │ ├── 20140829154350_add_search_indexes_to_services.rb │ ├── 20140909031145_convert_languages_to_array_type.rb │ ├── 20140929221750_add_fields_to_organization.rb │ ├── 20141007144757_add_fields_to_location.rb │ ├── 20141009185459_add_fields_to_address.rb │ ├── 20141009204519_add_fields_to_mail_address.rb │ ├── 20141010031124_add_country_prefix_to_phones.rb │ ├── 20141010155451_drop_faxes_table.rb │ ├── 20141010171020_update_contact_fields.rb │ ├── 20141010171817_add_contact_ref_to_phones.rb │ ├── 20141017154640_add_fields_to_services.rb │ ├── 20141021195019_update_service_status.rb │ ├── 20141023040419_add_active_to_locations.rb │ ├── 20141024022657_create_programs.rb │ ├── 20141024025404_add_program_ref_to_services.rb │ ├── 20141027154101_create_regular_schedules.rb │ ├── 20141029170109_remove_hours_from_locations.rb │ ├── 20141030012617_create_holiday_schedules.rb │ ├── 20141030204742_add_array_fields_to_organization.rb │ ├── 20141106215928_add_organization_ref_to_contacts.rb │ ├── 20141107161835_add_service_ref_to_contacts.rb │ ├── 20141108042551_add_organization_ref_to_phones.rb │ ├── 20141108183056_add_service_ref_to_phones.rb │ ├── 20141108194838_replace_location_urls_with_website.rb │ ├── 20141108214741_add_website_index_to_locations.rb │ ├── 20141109021540_replace_location_emails_with_email.rb │ ├── 20141109022202_add_email_index_to_locations.rb │ ├── 20141118132537_rename_wait_to_wait_time.rb │ ├── 20141208165502_rename_oe_id_to_taxonomy_id.rb │ ├── 20150107163352_add_interpretation_services_to_services.rb │ ├── 20150314204202_rename_fields.rb │ ├── 20150315202808_update_null_constraints.rb │ └── 20180212023953_convert_admin_emails_to_array.rb ├── seeds.rb └── structure.sql ├── docker-compose.yml ├── lib ├── address_extractor.rb ├── api_constraints.rb ├── asset_hosts.rb ├── assets │ └── .gitkeep ├── category_id_collector.rb ├── category_importer.rb ├── category_presenter.rb ├── config_validator.rb ├── contact_importer.rb ├── contact_presenter.rb ├── default_host.rb ├── email_filter.rb ├── entity_importer.rb ├── entity_presenter.rb ├── exceptions.rb ├── file_checker.rb ├── holiday_schedule_importer.rb ├── holiday_schedule_presenter.rb ├── importer_errors.rb ├── location_filter.rb ├── location_importer.rb ├── location_presenter.rb ├── mail_address_importer.rb ├── mail_address_presenter.rb ├── organization_importer.rb ├── organization_presenter.rb ├── parent_assigner.rb ├── phone_importer.rb ├── phone_presenter.rb ├── program_importer.rb ├── program_presenter.rb ├── regular_schedule_importer.rb ├── regular_schedule_presenter.rb ├── service_importer.rb ├── service_presenter.rb ├── subdomain_constraints.rb └── tasks │ ├── .gitkeep │ ├── import.rake │ ├── oe.rake │ ├── remove_test_users_and_admins.rake │ └── taxonomy.rake ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── humans.txt └── robots.txt ├── script ├── bootstrap ├── export ├── export_prod_db ├── geocode ├── import ├── reset ├── reset_test_db ├── restore ├── restore_prod_db ├── setup_db ├── setup_heroku ├── test └── users ├── spec ├── active_record_spec_helper.rb ├── api │ ├── contacts │ │ ├── create_phone_spec.rb │ │ ├── delete_phone_spec.rb │ │ └── patch_phone_spec.rb │ ├── cors_spec.rb │ ├── create_address_spec.rb │ ├── create_contact_spec.rb │ ├── create_location_spec.rb │ ├── create_mail_address_spec.rb │ ├── create_organization_spec.rb │ ├── create_service_spec.rb │ ├── delete_address_spec.rb │ ├── delete_contact_spec.rb │ ├── delete_location_spec.rb │ ├── delete_mail_address_spec.rb │ ├── delete_organization_spec.rb │ ├── delete_service_spec.rb │ ├── get_categories_spec.rb │ ├── get_category_children_spec.rb │ ├── get_location_contacts_spec.rb │ ├── get_location_services_spec.rb │ ├── get_location_spec.rb │ ├── get_locations_spec.rb │ ├── get_organization_locations_spec.rb │ ├── get_organization_spec.rb │ ├── get_organizations_spec.rb │ ├── locations │ │ ├── create_phone_spec.rb │ │ ├── delete_phone_spec.rb │ │ ├── get_location_phones_spec.rb │ │ └── patch_phone_spec.rb │ ├── nearby_spec.rb │ ├── pagination_headers_spec.rb │ ├── patch_address_spec.rb │ ├── patch_contact_spec.rb │ ├── patch_location_spec.rb │ ├── patch_mail_address_spec.rb │ ├── patch_organization_spec.rb │ ├── patch_service_spec.rb │ ├── put_service_categories_spec.rb │ ├── root_endpoint_spec.rb │ ├── search │ │ ├── category_spec.rb │ │ ├── language_spec.rb │ │ ├── location_status_spec.rb │ │ └── service_area_spec.rb │ ├── search_spec.rb │ └── services │ │ └── update_service_categories_spec.rb ├── controllers │ ├── admin │ │ ├── contacts_controller_spec.rb │ │ ├── csv_controller_spec.rb │ │ ├── locations_controller_spec.rb │ │ ├── organization_contacts_controller_spec.rb │ │ ├── organizations_controller_spec.rb │ │ ├── programs_controller_spec.rb │ │ ├── service_contacts_controller_spec.rb │ │ └── services_controller_spec.rb │ ├── api_applications_controller_spec.rb │ └── home_controller_spec.rb ├── factories │ ├── addresses.rb │ ├── admins.rb │ ├── api_applications.rb │ ├── categories.rb │ ├── contacts.rb │ ├── holiday_schedules.rb │ ├── locations.rb │ ├── mail_addresses.rb │ ├── organizations.rb │ ├── phones.rb │ ├── programs.rb │ ├── regular_schedules.rb │ ├── services.rb │ └── users.rb ├── features │ ├── admin │ │ ├── contacts │ │ │ ├── create_contact_spec.rb │ │ │ ├── delete_contact_spec.rb │ │ │ └── update_contact_spec.rb │ │ ├── csv │ │ │ ├── download_addresses_csv_spec.rb │ │ │ ├── download_contacts_csv_spec.rb │ │ │ ├── download_holiday_schedules_csv_spec.rb │ │ │ ├── download_locations_csv_spec.rb │ │ │ ├── download_mail_addresses_csv_spec.rb │ │ │ ├── download_organizations_csv_spec.rb │ │ │ ├── download_phones_csv_spec.rb │ │ │ ├── download_programs_csv_spec.rb │ │ │ ├── download_regular_schedules_csv_spec.rb │ │ │ └── download_services_csv_spec.rb │ │ ├── dashboard_spec.rb │ │ ├── edit_account_spec.rb │ │ ├── locations │ │ │ ├── ability_to_add_admin_email_spec.rb │ │ │ ├── create_location_spec.rb │ │ │ ├── delete_location_spec.rb │ │ │ ├── update_accessibility_spec.rb │ │ │ ├── update_address_spec.rb │ │ │ ├── update_admin_emails_spec.rb │ │ │ ├── update_alternate_name_spec.rb │ │ │ ├── update_description_spec.rb │ │ │ ├── update_email_spec.rb │ │ │ ├── update_holiday_hours_spec.rb │ │ │ ├── update_hours_spec.rb │ │ │ ├── update_languages_spec.rb │ │ │ ├── update_mail_address_spec.rb │ │ │ ├── update_name_spec.rb │ │ │ ├── update_phone_numbers_spec.rb │ │ │ ├── update_short_description_spec.rb │ │ │ ├── update_transportation_spec.rb │ │ │ ├── update_virtual_spec.rb │ │ │ ├── update_website_spec.rb │ │ │ ├── visit_location_spec.rb │ │ │ └── visit_locations_spec.rb │ │ ├── organizations │ │ │ ├── create_contact_spec.rb │ │ │ ├── create_organization_spec.rb │ │ │ ├── delete_contact_spec.rb │ │ │ ├── delete_organization_spec.rb │ │ │ ├── update_accreditations_spec.rb │ │ │ ├── update_alternate_name_spec.rb │ │ │ ├── update_contact_name_spec.rb │ │ │ ├── update_date_incorporated_spec.rb │ │ │ ├── update_description_spec.rb │ │ │ ├── update_email_spec.rb │ │ │ ├── update_funding_sources_spec.rb │ │ │ ├── update_legal_status_spec.rb │ │ │ ├── update_licenses_spec.rb │ │ │ ├── update_name_spec.rb │ │ │ ├── update_phone_numbers_spec.rb │ │ │ ├── update_tax_id_spec.rb │ │ │ ├── update_tax_status_spec.rb │ │ │ ├── update_website_spec.rb │ │ │ ├── visit_organization_spec.rb │ │ │ └── visit_organizations_spec.rb │ │ ├── programs │ │ │ ├── create_program_spec.rb │ │ │ ├── delete_program_spec.rb │ │ │ ├── update_alternate_name_spec.rb │ │ │ ├── update_name_spec.rb │ │ │ └── visit_programs_spec.rb │ │ ├── services │ │ │ ├── copy_service_to_location_spec.rb │ │ │ ├── create_contact_spec.rb │ │ │ ├── create_service_spec.rb │ │ │ ├── delete_contact_spec.rb │ │ │ ├── delete_service_spec.rb │ │ │ ├── update_accepted_payments_spec.rb │ │ │ ├── update_alternate_name_spec.rb │ │ │ ├── update_audience_spec.rb │ │ │ ├── update_categories_spec.rb │ │ │ ├── update_contact_name_spec.rb │ │ │ ├── update_description_spec.rb │ │ │ ├── update_eligibility_spec.rb │ │ │ ├── update_email_spec.rb │ │ │ ├── update_fees_spec.rb │ │ │ ├── update_funding_sources_spec.rb │ │ │ ├── update_holiday_hours_spec.rb │ │ │ ├── update_hours_spec.rb │ │ │ ├── update_how_to_apply_spec.rb │ │ │ ├── update_interpretation_services_spec.rb │ │ │ ├── update_keywords_spec.rb │ │ │ ├── update_languages_spec.rb │ │ │ ├── update_name_spec.rb │ │ │ ├── update_phone_numbers_spec.rb │ │ │ ├── update_program_spec.rb │ │ │ ├── update_required_documents_spec.rb │ │ │ ├── update_service_areas_spec.rb │ │ │ ├── update_status_spec.rb │ │ │ ├── update_wait_time_spec.rb │ │ │ ├── update_website_spec.rb │ │ │ └── visit_services_spec.rb │ │ ├── sign_in_spec.rb │ │ ├── sign_out_spec.rb │ │ └── sign_up_spec.rb │ ├── create_new_api_application_spec.rb │ ├── delete_api_application_spec.rb │ ├── edit_account_spec.rb │ ├── homepage_text_spec.rb │ ├── sign_out_spec.rb │ ├── signin_spec.rb │ ├── signup_spec.rb │ └── update_api_application_spec.rb ├── lib │ ├── address_extractor_spec.rb │ ├── api_constraints_spec.rb │ ├── asset_hosts_spec.rb │ ├── category_importer_spec.rb │ ├── config_validator_spec.rb │ ├── contact_importer_spec.rb │ ├── default_host_spec.rb │ ├── file_checker_spec.rb │ ├── holiday_schedule_importer_spec.rb │ ├── importer_errors_spec.rb │ ├── location_importer_spec.rb │ ├── location_presenter_spec.rb │ ├── mail_address_importer_spec.rb │ ├── organization_importer_spec.rb │ ├── organization_presenter_spec.rb │ ├── phone_importer_spec.rb │ ├── program_importer_spec.rb │ ├── regular_schedule_importer_spec.rb │ ├── service_importer_spec.rb │ ├── service_presenter_spec.rb │ └── tasks │ │ └── remove_test_users_and_admins_rake_spec.rb ├── models │ ├── address_spec.rb │ ├── admin_spec.rb │ ├── api_application_spec.rb │ ├── category_spec.rb │ ├── contact_spec.rb │ ├── holiday_schedule_spec.rb │ ├── location_spec.rb │ ├── mail_address_spec.rb │ ├── organization_spec.rb │ ├── phone_spec.rb │ ├── program_spec.rb │ ├── regular_schedule_spec.rb │ ├── service_spec.rb │ └── user_spec.rb ├── rails_helper.rb ├── requests │ ├── api_applications_spec.rb │ └── status_spec.rb ├── routing │ └── api_applications_routing_spec.rb ├── spec_helper.rb └── support │ ├── admin_controller_helper.rb │ ├── api │ └── model_helpers.rb │ ├── api_constraints_spec_helper.rb │ ├── bullet.rb │ ├── capybara.rb │ ├── database_cleaner.rb │ ├── default_headers.rb │ ├── features │ ├── contact_helpers.rb │ ├── form_helpers.rb │ ├── phone_helpers.rb │ ├── schedule_helpers.rb │ └── session_helpers.rb │ ├── fixtures │ ├── contact_with_no_parent.csv │ ├── existing_category.csv │ ├── holiday_schedule_with_no_parent.csv │ ├── hs_with_2_digit_year.csv │ ├── hs_with_invalid_date.csv │ ├── hs_with_spelled_out_date.csv │ ├── invalid_address.csv │ ├── invalid_category.csv │ ├── invalid_contact.csv │ ├── invalid_holiday_schedule.csv │ ├── invalid_location.csv │ ├── invalid_location_org.csv │ ├── invalid_mail_address.csv │ ├── invalid_mail_address_location.csv │ ├── invalid_org.csv │ ├── invalid_parent_category.csv │ ├── invalid_phone.csv │ ├── invalid_program.csv │ ├── invalid_program_headers.csv │ ├── invalid_regular_schedule.csv │ ├── invalid_service.csv │ ├── invalid_service_location.csv │ ├── locations.csv │ ├── missing_address.csv │ ├── org_with_2_digit_year.csv │ ├── org_with_invalid_date.csv │ ├── org_with_spelled_out_date.csv │ ├── phone_with_no_parent.csv │ ├── program_with_no_parent.csv │ ├── programs.csv │ ├── regular_schedule_with_no_parent.csv │ ├── services.csv │ ├── taxonomy.csv │ ├── valid_address.csv │ ├── valid_category.csv │ ├── valid_contact_phone.csv │ ├── valid_location.csv │ ├── valid_location_contact.csv │ ├── valid_location_holiday_schedule.csv │ ├── valid_location_phone.csv │ ├── valid_location_regular_schedule.csv │ ├── valid_mail_address.csv │ ├── valid_org.csv │ ├── valid_org_contact.csv │ ├── valid_org_phone.csv │ ├── valid_program.csv │ ├── valid_service.csv │ ├── valid_service_contact.csv │ ├── valid_service_holiday_schedule.csv │ ├── valid_service_phone.csv │ └── valid_service_regular_schedule.csv │ ├── geocoder_stubs.rb │ ├── mailer_macros.rb │ ├── request_helpers.rb │ ├── shared_contexts │ └── rake.rb │ ├── shared_examples_for_schedules.rb │ ├── shoulda_matchers.rb │ ├── warden.rb │ └── webmock.rb └── vendor ├── assets ├── javascripts │ └── .gitkeep └── stylesheets │ └── .gitkeep └── plugins └── .gitkeep /.bummr-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | bundle exec rspec --fail-fast 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | tmp 3 | log 4 | *.md 5 | .git* 6 | -------------------------------------------------------------------------------- /.foreman.example: -------------------------------------------------------------------------------- 1 | formation: release=0,web=1 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.csv eol=crlf 3 | *.dump binary 4 | *.png binary 5 | *.jpg binary 6 | *.gif binary 7 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml 3 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16.13.1 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | ohana-api 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.5 2 | -------------------------------------------------------------------------------- /.scss-lint.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | Comment: 3 | exclude: 4 | - 'app/assets/stylesheets/application.css.scss' 5 | - 'app/assets/stylesheets/admin/application.css.scss' 6 | PlaceholderInExtend: 7 | exclude: 8 | - 'app/assets/stylesheets/framework_and_overrides.css.scss' 9 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | version: v1.38.1 2 | ignore: 3 | 'SNYK-RUBY-NOKOGIRI-20299': 4 | - '* > nokogiri': 5 | reason: 'We do not opt in to DTDLOAD and do not opt out of NONET' 6 | expires: '2019-02-14T00:00:00.000Z' 7 | 'SNYK-RUBY-ACTIONCABLE-20338': 8 | - '* > actioncable': 9 | reason: 'We do not use actioncable' 10 | expires: '2019-02-14T00:00:00.000Z' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Ruby image because the Rails images have been deprecated 2 | FROM ruby:2.7.5 3 | 4 | RUN apt-get update \ 5 | && apt-get install -y --no-install-recommends postgresql-client \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | RUN mkdir /usr/local/node \ 9 | && curl -L https://nodejs.org/dist/v4.4.7/node-v4.4.7-linux-x64.tar.xz | tar Jx -C /usr/local/node --strip-components=1 10 | RUN ln -s ../node/bin/node /usr/local/bin/ 11 | 12 | WORKDIR /ohana-api 13 | 14 | COPY Gemfile /ohana-api 15 | COPY Gemfile.lock /ohana-api 16 | 17 | RUN gem install bundler 18 | RUN bundle install --jobs 20 --retry 5 --without production 19 | 20 | COPY . /ohana-api 21 | 22 | EXPOSE 8080 23 | CMD ["rails", "server", "-b", "0.0.0.0", "-p", "8080"] 24 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C ./config/puma.rb 2 | release: bundle exec rake db:migrate 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically 3 | # be available to Rake. 4 | 5 | require File.expand_path('config/application', __dir__) 6 | 7 | Rails.application.load_tasks 8 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_directory ../javascripts .js 2 | //= link_directory ../javascripts/admin .js 3 | //= link_directory ../stylesheets .css 4 | //= link_directory ../stylesheets/admin .css 5 | //= link application.css 6 | //= link admin/application.css 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/accepted_payments.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('#service_accepted_payments').select2(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require bootstrap-sprockets 16 | //= require select2 17 | //= require_tree . 18 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/funding_sources.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('#service_funding_sources,#organization_funding_sources').select2(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/languages.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('#location_languages,#service_languages').select2(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/org_autocomplete.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('#org-name').select2({ 3 | minimumInputLength: 3, 4 | ajax: { 5 | url: $(this).data('url'), 6 | dataType: "json", 7 | data: function(term, page) { 8 | return { 9 | q: term, 10 | } 11 | }, 12 | results: function(data, page) { 13 | return { 14 | results: $.map( data, function(org, i) { 15 | return { id: org[0], text: org[1] } 16 | } ) 17 | } 18 | } 19 | } 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/required_documents.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('#service_required_documents').select2(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/service_areas.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('#service_service_areas').select2(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/admin/tags.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $("#service_keywords,#organization_accreditations,#organization_licenses").select2({ 3 | tags: [''], 4 | tokenSeparators: [','] 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require bootstrap-sprockets 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | $light-blue: #c8d4e8; 2 | $lighter-blue: #cbe0f3; 3 | 4 | $black: #000; 5 | $white: #fff; 6 | 7 | $red: #b5121b; 8 | $light-red: #df3e3e; 9 | $lighter-red: #de9292; 10 | 11 | $gray: #fcfcfc; 12 | 13 | $light-green: #c8e8ca; 14 | -------------------------------------------------------------------------------- /app/controllers/admin/dashboard_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin 2 | class DashboardController < ApplicationController 3 | layout 'admin' 4 | 5 | def index 6 | redirect_to new_admin_session_url unless admin_signed_in? 7 | @orgs = policy_scope(Organization) if current_admin 8 | @version = OHANA_API_VERSION 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/admin/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin 2 | class RegistrationsController < Devise::RegistrationsController 3 | before_action :configure_permitted_parameters 4 | 5 | include PrivateRegistration 6 | 7 | protected 8 | 9 | def configure_permitted_parameters 10 | devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) 11 | devise_parameter_sanitizer.permit(:account_update, keys: [:name]) 12 | end 13 | 14 | def after_inactive_sign_up_url_for(_resource) 15 | new_admin_session_url 16 | end 17 | 18 | def after_update_path_for(_resource) 19 | edit_admin_registration_url 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/api/v1/categories_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class CategoriesController < ApplicationController 4 | def index 5 | categories = Category.all 6 | render json: categories, status: :ok 7 | end 8 | 9 | def children 10 | children = Category.find_by(taxonomy_id: params[:taxonomy_id]).children 11 | render json: children, status: :ok 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/api/v1/cors_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class CorsController < ApplicationController 4 | def render_204 5 | head :no_content 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/api/v1/errors_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class ErrorsController < ApplicationController 4 | def raise_not_found! 5 | message = "No route matches #{params[:unmatched_route]}" 6 | raise ActionController::RoutingError, message 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/concerns/cacheable.rb: -------------------------------------------------------------------------------- 1 | module Cacheable 2 | def cache_time 3 | ENV['EXPIRES_IN'].to_i.minutes 4 | end 5 | 6 | def set_cache_control 7 | expires_in cache_time, public: true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/concerns/taggable.rb: -------------------------------------------------------------------------------- 1 | module Taggable 2 | def shift_and_split_params(params, *fields) 3 | fields.each do |key| 4 | params[key] = params[key].shift.split(',') 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/concerns/token_validator.rb: -------------------------------------------------------------------------------- 1 | module TokenValidator 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :validate_token!, only: %i[update destroy create] 6 | end 7 | 8 | def validate_token! 9 | render_401 unless valid_api_token? 10 | end 11 | 12 | def valid_api_token? 13 | token = request.env['HTTP_X_API_TOKEN'].to_s 14 | token.present? && token == ENV['ADMIN_APP_TOKEN'] 15 | end 16 | 17 | def render_401 18 | hash = 19 | { 20 | 'message' => 'This action requires a valid X-API-Token header.', 21 | 'status' => 401 22 | } 23 | render json: hash, status: :unauthorized 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | # If the user is signed in, we'd like to greet 3 | # them on the Home page 4 | def index 5 | @user = current_user 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/user/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class User 2 | class RegistrationsController < Devise::RegistrationsController 3 | before_action :configure_permitted_parameters 4 | 5 | include PrivateRegistration 6 | 7 | protected 8 | 9 | def configure_permitted_parameters 10 | devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) 11 | devise_parameter_sanitizer.permit(:account_update, keys: [:name]) 12 | end 13 | 14 | def after_update_path_for(_resource) 15 | edit_user_registration_url 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/helpers/admin/csv_download_helper.rb: -------------------------------------------------------------------------------- 1 | class Admin 2 | module CsvDownloadHelper 3 | def csv_tables 4 | %w[addresses contacts holiday_schedules locations mail_addresses 5 | organizations phones programs regular_schedules services] 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/custom_mailer.rb: -------------------------------------------------------------------------------- 1 | class CustomMailer < Devise::Mailer 2 | def confirmation_instructions(record, token, options = {}) 3 | options[:template_path] = template_path(record) 4 | super 5 | end 6 | 7 | def reset_password_instructions(record, token, options = {}) 8 | options[:template_path] = template_path(record) 9 | super 10 | end 11 | 12 | private 13 | 14 | def template_path(resource) 15 | return 'admin/mailer' if resource.is_a?(Admin) 16 | return 'devise/mailer' if resource.is_a?(User) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/admin.rb: -------------------------------------------------------------------------------- 1 | class Admin < ApplicationRecord 2 | # Devise already checks for presence of email and password. 3 | validates :name, presence: true 4 | validates :email, email: true, uniqueness: { case_sensitive: false } 5 | 6 | devise :database_authenticatable, :registerable, 7 | :recoverable, :rememberable, :trackable, :validatable, 8 | :confirmable 9 | end 10 | -------------------------------------------------------------------------------- /app/models/api_application.rb: -------------------------------------------------------------------------------- 1 | class ApiApplication < ApplicationRecord 2 | belongs_to :user 3 | 4 | validates :name, :api_token, uniqueness: true 5 | validates :name, :main_url, presence: true 6 | validates :main_url, url: true 7 | validates :callback_url, url: true, allow_blank: true 8 | 9 | auto_strip_attributes :name, squish: true 10 | 11 | before_create :generate_api_token 12 | 13 | private 14 | 15 | def generate_api_token 16 | self.api_token = loop do 17 | random_token = SecureRandom.hex 18 | break random_token unless self.class.exists?(api_token: random_token) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | class Category < ApplicationRecord 2 | has_and_belongs_to_many :services 3 | 4 | validates :name, :taxonomy_id, 5 | presence: { message: I18n.t('errors.messages.blank_for_category') } 6 | 7 | validates :taxonomy_id, 8 | uniqueness: { 9 | message: I18n.t('errors.messages.duplicate_taxonomy_id'), 10 | case_sensitive: false 11 | } 12 | 13 | has_ancestry 14 | end 15 | -------------------------------------------------------------------------------- /app/models/concerns/parent_presence_validatable.rb: -------------------------------------------------------------------------------- 1 | module ParentPresenceValidatable 2 | def self.included(base) 3 | base.class_eval do 4 | validate :parent_presence 5 | end 6 | end 7 | 8 | private 9 | 10 | def parent_presence 11 | return if parents.any?(&:present?) 12 | 13 | errors.add(:base, "#{self.class.name.titleize} is missing a parent: #{list_of_parents}") 14 | end 15 | 16 | def parents 17 | belongs_to_associations.map { |a| send(a.name) } 18 | end 19 | 20 | def belongs_to_associations 21 | @belongs_to_associations ||= self.class.reflect_on_all_associations(:belongs_to) 22 | end 23 | 24 | def list_of_parents 25 | belongs_to_associations.map { |a| a.name.to_s.titleize }.join(' or ') 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/models/contact.rb: -------------------------------------------------------------------------------- 1 | class Contact < ApplicationRecord 2 | include ParentPresenceValidatable 3 | 4 | default_scope { order('id ASC') } 5 | 6 | belongs_to :location, optional: true, touch: true 7 | belongs_to :organization, optional: true 8 | belongs_to :service, optional: true, touch: true 9 | 10 | has_many :phones, dependent: :destroy, inverse_of: :contact 11 | accepts_nested_attributes_for :phones, 12 | allow_destroy: true, reject_if: :all_blank 13 | 14 | validates :name, 15 | presence: { message: I18n.t('errors.messages.blank_for_contact') } 16 | 17 | validates :email, email: true, allow_blank: true 18 | 19 | auto_strip_attributes :department, :email, :name, :title, squish: true 20 | end 21 | -------------------------------------------------------------------------------- /app/models/holiday_schedule.rb: -------------------------------------------------------------------------------- 1 | class HolidaySchedule < ApplicationRecord 2 | include ParentPresenceValidatable 3 | 4 | belongs_to :location, optional: true, touch: true, inverse_of: :holiday_schedules 5 | belongs_to :service, optional: true, touch: true, inverse_of: :holiday_schedules 6 | 7 | validates :start_date, :end_date, 8 | date: true, 9 | presence: { message: I18n.t('errors.messages.blank_for_hs') } 10 | 11 | validates :opens_at, :closes_at, 12 | presence: { message: I18n.t('errors.messages.blank_for_hs_open') }, 13 | unless: :closed? 14 | end 15 | -------------------------------------------------------------------------------- /app/models/mail_address.rb: -------------------------------------------------------------------------------- 1 | class MailAddress < ApplicationRecord 2 | belongs_to :location, touch: true, inverse_of: :mail_address, optional: false 3 | 4 | validates :address_1, 5 | :city, 6 | :postal_code, 7 | :country, 8 | presence: { message: I18n.t('errors.messages.blank_for_mail_address') } 9 | 10 | validates :state_province, state_province: true 11 | 12 | validates :country, length: { allow_blank: true, maximum: 2, minimum: 2 } 13 | 14 | validates :postal_code, zip: { allow_blank: true } 15 | 16 | auto_strip_attributes :attention, :address_1, :address_2, :city, :state_province, 17 | :postal_code, :country, squish: true 18 | end 19 | -------------------------------------------------------------------------------- /app/models/program.rb: -------------------------------------------------------------------------------- 1 | class Program < ApplicationRecord 2 | belongs_to :organization, optional: false 3 | has_many :services, dependent: :destroy 4 | 5 | validates :name, 6 | presence: { message: I18n.t('errors.messages.blank_for_program') } 7 | 8 | auto_strip_attributes :alternate_name, :name 9 | 10 | def self.with_orgs(ids) 11 | joins(:organization).where(organization_id: ids).distinct 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/regular_schedule.rb: -------------------------------------------------------------------------------- 1 | class RegularSchedule < ApplicationRecord 2 | include ParentPresenceValidatable 3 | 4 | default_scope { order('weekday ASC') } 5 | 6 | belongs_to :location, optional: true, touch: true, inverse_of: :regular_schedules 7 | belongs_to :service, optional: true, touch: true, inverse_of: :regular_schedules 8 | 9 | validates :weekday, :opens_at, :closes_at, 10 | presence: { message: I18n.t('errors.messages.blank_for_rs') } 11 | 12 | validates :weekday, weekday: { allow_blank: true } 13 | end 14 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_many :api_applications, dependent: :destroy 3 | accepts_nested_attributes_for :api_applications 4 | 5 | # Devise checks for presence of email and password by default 6 | validates :name, presence: true 7 | validates :email, email: true, uniqueness: { case_sensitive: false } 8 | 9 | devise :database_authenticatable, :registerable, 10 | :recoverable, :rememberable, :trackable, :validatable, 11 | :confirmable 12 | end 13 | -------------------------------------------------------------------------------- /app/policies/contact_policy.rb: -------------------------------------------------------------------------------- 1 | class ContactPolicy < ApplicationPolicy 2 | delegate :create?, :destroy?, :edit?, :update?, to: :organization_policy 3 | 4 | private 5 | 6 | def organization_policy 7 | @organization_policy ||= Pundit.policy!(user, record.organization) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/policies/location_policy.rb: -------------------------------------------------------------------------------- 1 | class LocationPolicy < ApplicationPolicy 2 | def new? 3 | return true if user.super_admin? 4 | return super if record.id.present? 5 | 6 | can_access_at_least_one_organization? 7 | end 8 | 9 | class Scope < Scope 10 | def resolve 11 | return scope.pluck(:id, :name, :slug).sort_by(&:second) if user.super_admin? 12 | 13 | scope.with_email(user.email).pluck(:id, :name, :slug) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/policies/organization_policy.rb: -------------------------------------------------------------------------------- 1 | class OrganizationPolicy < ApplicationPolicy 2 | class Scope < Scope 3 | def resolve 4 | return scope.pluck(:id, :name, :slug).sort_by(&:second) if user.super_admin? 5 | 6 | scope.with_locations(location_ids).pluck(:id, :name, :slug) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/policies/program_policy.rb: -------------------------------------------------------------------------------- 1 | class ProgramPolicy < ApplicationPolicy 2 | delegate :create?, to: :organization_policy 3 | 4 | def new? 5 | can_access_at_least_one_organization? 6 | end 7 | 8 | private 9 | 10 | def organization_policy 11 | @organization_policy ||= Pundit.policy!(user, record.organization) 12 | end 13 | 14 | class Scope < Scope 15 | def resolve 16 | return scope.pluck(:id, :name).sort_by(&:second) if user.super_admin? 17 | 18 | scope.with_orgs(org_ids).pluck(:id, :name) 19 | end 20 | 21 | def org_ids 22 | Pundit.policy_scope!(user, Organization).map(&:first).flatten 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/policies/service_policy.rb: -------------------------------------------------------------------------------- 1 | class ServicePolicy < ApplicationPolicy 2 | class Scope < Scope 3 | def resolve 4 | return scope.pluck(:location_id, :id, :name).sort_by(&:third) if user.super_admin? 5 | 6 | scope.with_locations(location_ids).pluck(:location_id, :id, :name) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/serializers/address_serializer.rb: -------------------------------------------------------------------------------- 1 | class AddressSerializer < ActiveModel::Serializer 2 | attributes :id, :address_1, :address_2, :city, :state_province, :postal_code 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/category_serializer.rb: -------------------------------------------------------------------------------- 1 | class CategorySerializer < ActiveModel::Serializer 2 | attributes :id, :depth, :taxonomy_id, :name, :parent_id 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/contact_serializer.rb: -------------------------------------------------------------------------------- 1 | class ContactSerializer < ActiveModel::Serializer 2 | attributes :department, :email, :id, :name, :title 3 | 4 | has_many :phones 5 | end 6 | -------------------------------------------------------------------------------- /app/serializers/holiday_schedule_serializer.rb: -------------------------------------------------------------------------------- 1 | class HolidayScheduleSerializer < ActiveModel::Serializer 2 | attributes :closed, :start_date, :end_date, :opens_at, :closes_at 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/location_serializer.rb: -------------------------------------------------------------------------------- 1 | class LocationSerializer < LocationsSerializer 2 | attributes :accessibility, :email, :languages, :transportation 3 | 4 | has_many :contacts 5 | has_one :mail_address 6 | has_many :regular_schedules 7 | has_many :holiday_schedules 8 | has_many :services 9 | 10 | has_one :organization, serializer: SummarizedOrganizationSerializer 11 | 12 | def accessibility 13 | object.accessibility.map(&:text) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/serializers/locations_organization_serializer.rb: -------------------------------------------------------------------------------- 1 | class LocationsOrganizationSerializer < ActiveModel::Serializer 2 | attributes :id, :accreditations, :alternate_name, :date_incorporated, 3 | :description, :email, :funding_sources, :licenses, :name, 4 | :website, :slug, :url, :locations_url 5 | 6 | def url 7 | api_organization_url(object) 8 | end 9 | 10 | def locations_url 11 | api_org_locations_url(object) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/serializers/locations_serializer.rb: -------------------------------------------------------------------------------- 1 | class LocationsSerializer < ActiveModel::Serializer 2 | attributes :id, :active, :admin_emails, :alternate_name, :coordinates, 3 | :description, :latitude, :longitude, :name, :short_desc, :slug, 4 | :website, :updated_at, :url 5 | 6 | has_one :address 7 | has_one :organization, serializer: LocationsOrganizationSerializer 8 | has_many :phones 9 | 10 | def coordinates 11 | return [] unless object.longitude.present? && object.latitude.present? 12 | 13 | [object.longitude, object.latitude] 14 | end 15 | 16 | def url 17 | api_location_url(object) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/serializers/mail_address_serializer.rb: -------------------------------------------------------------------------------- 1 | class MailAddressSerializer < ActiveModel::Serializer 2 | attributes :id, :attention, :address_1, :address_2, :city, :state_province, :postal_code 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/nearby_serializer.rb: -------------------------------------------------------------------------------- 1 | class NearbySerializer < ActiveModel::Serializer 2 | attributes :id, :alternate_name, :latitude, :longitude, :name, :slug 3 | 4 | has_one :address 5 | end 6 | -------------------------------------------------------------------------------- /app/serializers/organization_serializer.rb: -------------------------------------------------------------------------------- 1 | class OrganizationSerializer < LocationsOrganizationSerializer 2 | has_many :contacts 3 | has_many :phones 4 | end 5 | -------------------------------------------------------------------------------- /app/serializers/phone_serializer.rb: -------------------------------------------------------------------------------- 1 | class PhoneSerializer < ActiveModel::Serializer 2 | attributes :id, :department, :extension, :number, :number_type, :vanity_number 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/regular_schedule_serializer.rb: -------------------------------------------------------------------------------- 1 | class RegularScheduleSerializer < ActiveModel::Serializer 2 | attributes :weekday, :opens_at, :closes_at 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/service_serializer.rb: -------------------------------------------------------------------------------- 1 | class ServiceSerializer < ActiveModel::Serializer 2 | attributes :id, :accepted_payments, :alternate_name, :audience, :description, 3 | :eligibility, :email, :fees, :funding_sources, :application_process, 4 | :interpretation_services, :keywords, :languages, :name, 5 | :required_documents, :service_areas, :status, :website, 6 | :wait_time, :updated_at 7 | 8 | # embed :ids, include: true 9 | has_many :categories 10 | has_many :contacts 11 | has_many :phones 12 | has_many :regular_schedules 13 | has_many :holiday_schedules 14 | end 15 | -------------------------------------------------------------------------------- /app/serializers/summarized_organization_serializer.rb: -------------------------------------------------------------------------------- 1 | class SummarizedOrganizationSerializer < ActiveModel::Serializer 2 | attributes :id, :alternate_name, :name, :slug, :url 3 | 4 | def url 5 | api_organization_url(object) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/validators/email_validator.rb: -------------------------------------------------------------------------------- 1 | class EmailValidator < RegexValidator 2 | def validate_each(record, attribute, value) 3 | default_message = "#{value} #{I18n.t('errors.messages.invalid_email')}" 4 | 5 | regex = /\A([^@\s]+)@((?:(?!-)[-a-z0-9]+(? 'true' } 6 | × 7 | %span.sr-only 8 | Close 9 | %h4.modal-title#my-modal-label 10 | Are you ABSOLUTELY sure? 11 | .modal-body 12 | %p 13 | This action CANNOT be undone. This will delete 14 | %strong 15 | = org_name 16 | and all of its associated locations permanently. 17 | .modal-footer 18 | %button.btn.btn-default{ 'data-dismiss' => 'modal' } Close 19 | = link_to t('admin.buttons.confirm_delete_organization'), { action: :destroy, id: org_id }, method: :delete, class: 'btn btn-danger' 20 | -------------------------------------------------------------------------------- /app/views/admin/organizations/edit.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h2= @organization.name 3 | = form_for [:admin, @organization], html: { class: 'edit-entry' } do |f| 4 | = render 'admin/organizations/form', f: f, organization: @organization 5 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_accreditations.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | %strong 4 | = f.label :accreditations 5 | %p.desc 6 | = t('.description') 7 | 8 | = field_set_tag do 9 | = text_field_tag 'organization[accreditations][]', organization.accreditations.join(','), id: 'organization_accreditations', class: 'form-control' 10 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_date_incorporated.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :date_incorporated 4 | %span.desc 5 | = t('.description') 6 | 7 | = field_set_tag do 8 | .row 9 | .col-sm-6 10 | = f.date_select :date_incorporated, include_blank: true, start_year: 1970, class: 'form-control' 11 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_description.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :description 4 | %span.desc 5 | = t('.description') 6 | 7 | = field_set_tag do 8 | = f.text_area :description, required: false, class: 'form-control', rows: 10 9 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_legal_status.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :legal_status 4 | %span.desc 5 | = t('.description') 6 | 7 | = field_set_tag do 8 | .row 9 | .col-sm-6 10 | = f.text_field :legal_status, required: false, maxlength: 255, class: 'form-control' 11 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_licenses.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | %strong 4 | = f.label :licenses 5 | %p.desc 6 | = t('.description') 7 | 8 | = field_set_tag do 9 | = text_field_tag 'organization[licenses][]', organization.licenses.join(','), id: 'organization_licenses', class: 'form-control' 10 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_new_organization_form.html.haml: -------------------------------------------------------------------------------- 1 | = render 'admin/organizations/forms/fields', f: f, organization: organization 2 | 3 | = f.submit t('admin.buttons.create_organization'), class: 'btn btn-primary btn-lg', data: { disable_with: 'Please wait...' } 4 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_tax_id.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :tax_id 4 | %span.desc 5 | = t('.description') 6 | 7 | = field_set_tag do 8 | .row 9 | .col-sm-6 10 | = f.text_field :tax_id, required: false, maxlength: 255, class: 'form-control' 11 | -------------------------------------------------------------------------------- /app/views/admin/organizations/forms/_tax_status.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :tax_status 4 | %span.desc 5 | = t('.description') 6 | 7 | = field_set_tag do 8 | .row 9 | .col-sm-6 10 | = f.text_field :tax_status, required: false, maxlength: 255, class: 'form-control' 11 | -------------------------------------------------------------------------------- /app/views/admin/organizations/index.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Below you should see a list of organizations that you are allowed to administer. 3 | If there are any entries missing, 4 | please #{mail_to SETTINGS[:admin_support_email], 'let us know'}. 5 | %p 6 | To start updating, click on one of the links, which will take you to the details page 7 | for the organization. 8 | 9 | - if current_admin.super_admin? 10 | %p 11 | As a super admin, you have access to all locations in the database. Please make updates responsibly. 12 | 13 | %p 14 | - @orgs.each do |org| 15 | %ul 16 | = link_to org.second, edit_admin_organization_path(org.third) 17 | = paginate @orgs 18 | -------------------------------------------------------------------------------- /app/views/admin/organizations/new.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h1 Create a new organization 3 | 4 | = form_for [:admin, @organization], url: admin_organizations_path, html: { method: :post, class: 'edit-entry' } do |f| 5 | = render 'admin/organizations/forms/new_organization_form', f: f, organization: @organization 6 | -------------------------------------------------------------------------------- /app/views/admin/programs/_confirm_delete_program.html.haml: -------------------------------------------------------------------------------- 1 | .modal-dialog 2 | .modal-content 3 | .modal-header 4 | = button_tag(type: 'button', class: 'close', data: { dismiss: 'modal' }) do 5 | %span{ 'aria-hidden' => 'true' } 6 | × 7 | %span.sr-only 8 | Close 9 | %h4.modal-title#my-modal-label 10 | Are you ABSOLUTELY sure? 11 | .modal-body 12 | %p 13 | This action CANNOT be undone. This will delete 14 | %strong 15 | = program_name 16 | and all of its associated services permanently. 17 | .modal-footer 18 | %button.btn.btn-default{ 'data-dismiss' => 'modal' } Close 19 | = link_to t('admin.buttons.confirm_delete_program'), { action: :destroy, id: program_id }, method: :delete, class: 'btn btn-danger' 20 | -------------------------------------------------------------------------------- /app/views/admin/programs/_form.html.haml: -------------------------------------------------------------------------------- 1 | = render 'admin/programs/forms/fields', f: f, program: program 2 | = render 'admin/programs/forms/danger', program: program 3 | = render 'admin/programs/forms/editing', f: f, program: program 4 | -------------------------------------------------------------------------------- /app/views/admin/programs/edit.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h2 #{@program.organization.try(:name)} / #{@program.name} 3 | = form_for [:admin, @program], html: { class: 'edit-entry' } do |f| 4 | = render 'admin/programs/form', f: f, program: @program 5 | -------------------------------------------------------------------------------- /app/views/admin/programs/forms/_danger.html.haml: -------------------------------------------------------------------------------- 1 | .danger-zone 2 | %header 3 | %strong 4 | Danger Zone 5 | %h4 6 | Delete this program 7 | %p 8 | Once you delete a program, there is no going back. All of the program's services will be deleted as well. Please be certain. 9 | %p 10 | = link_to t('admin.buttons.delete_program'), {}, href: '', data: { toggle: 'modal', target: '#modal-window' }, class: 'boxed-action' 11 | .modal.fade#modal-window{ 'aria-hidden' => 'true', 'aria-labelledby' => 'my-modal-label', 'role' => 'dialog', 'tabindex' => '-1' } 12 | = render 'admin/programs/confirm_delete_program', program_id: program.id, program_name: program.name 13 | -------------------------------------------------------------------------------- /app/views/admin/programs/forms/_editing.html.haml: -------------------------------------------------------------------------------- 1 | .save-box.navbar-default 2 | %p 3 | Editing 4 | %strong 5 | = program.name 6 | = f.submit t('admin.buttons.save_changes'), class: 'btn btn-success', data: { disable_with: 'Please wait...' } 7 | -------------------------------------------------------------------------------- /app/views/admin/programs/forms/_fields.html.haml: -------------------------------------------------------------------------------- 1 | - if program.errors.any? 2 | .alert.alert-danger 3 | %h2 #{pluralize(program.errors.count, 'error')} prohibited this program from being saved: 4 | %ul 5 | - program.errors.full_messages.each do |msg| 6 | %li= msg 7 | 8 | = render 'admin/shared/forms/choose_org', f: f 9 | = render 'admin/shared/forms/name', f: f 10 | = render 'admin/shared/forms/alternate_name', f: f 11 | -------------------------------------------------------------------------------- /app/views/admin/programs/forms/_new_program_form.html.haml: -------------------------------------------------------------------------------- 1 | = render 'admin/programs/forms/fields', f: f, program: program 2 | 3 | = f.submit t('admin.buttons.create_program'), class: 'btn btn-primary btn-lg', data: { disable_with: 'Please wait...' } 4 | -------------------------------------------------------------------------------- /app/views/admin/programs/new.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h1 Create a new program 3 | 4 | = form_for [:admin, @program], url: admin_programs_path, html: { method: :post, class: 'edit-entry' } do |f| 5 | = render 'admin/programs/forms/new_program_form', f: f, program: @program 6 | -------------------------------------------------------------------------------- /app/views/admin/service_contacts/edit.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h2 #{@contact.try(:name)} / #{@service.name} 3 | = form_for [:admin, @service.location, @service, @contact], html: { class: 'edit-entry' } do |f| 4 | = render 'admin/contacts/form', f: f, contact: @contact 5 | -------------------------------------------------------------------------------- /app/views/admin/service_contacts/new.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h1 Create a new contact 3 | 4 | = form_for [:admin, @service.location, @service, @contact], html: { method: :post, class: 'edit-entry' } do |f| 5 | = render 'admin/contacts/forms/new_service_contact', f: f, service: @service, contact: @contact 6 | -------------------------------------------------------------------------------- /app/views/admin/services/_confirm_delete_service.html.haml: -------------------------------------------------------------------------------- 1 | .modal-dialog 2 | .modal-content 3 | .modal-header 4 | = button_tag(type: 'button', class: 'close', data: { dismiss: 'modal' }) do 5 | %span{ 'aria-hidden' => 'true' } 6 | × 7 | %span.sr-only 8 | Close 9 | %h4.modal-title#my-modal-label 10 | Are you ABSOLUTELY sure? 11 | .modal-body 12 | %p 13 | This action CANNOT be undone. This will delete 14 | %strong 15 | #{service_name || service_id}. 16 | .modal-footer 17 | %button.btn.btn-default{ 'data-dismiss' => 'modal' } Close 18 | = link_to t('admin.buttons.confirm_delete_service'), { action: :destroy, id: service_id, name: service_name }, method: :delete, class: 'btn btn-danger' 19 | -------------------------------------------------------------------------------- /app/views/admin/services/edit.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h2 #{@service.try(:name)} / #{@location.name} 3 | = form_for [:admin, @location, @service], html: { class: 'edit-entry' } do |f| 4 | = render 'admin/services/form', f: f, service: @service, location: @location, nested_categories: @nested_categories 5 | 6 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_accepted_payments.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.payments 2 | %header 3 | %strong 4 | Accepted Payments 5 | 6 | = field_set_tag do 7 | = f.select :accepted_payments, SETTINGS.try(:[], :accepted_payments), {}, multiple: true, class: 'form-control', data: { placeholder: t('.placeholder') } 8 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_application_process.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :application_process 4 | %p.desc 5 | = t('.description') 6 | 7 | %p 8 | = f.text_area :application_process, class: 'form-control' 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_audience.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :audience 4 | %p.desc 5 | = t('.description') 6 | 7 | %p 8 | = f.text_area :audience, class: 'form-control' 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_categories.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | %strong 4 | = f.label :categories 5 | %p.desc 6 | = t('.description') 7 | 8 | = hidden_field_tag 'service[category_ids][]', nil 9 | = field_set_tag nil, id: 'categories' do 10 | = nested_categories 11 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_description.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :description 4 | %span.desc 5 | = t('.description') 6 | 7 | = field_set_tag do 8 | = f.text_area :description, required: false, class: 'form-control', rows: 5 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_eligibility.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :eligibility 4 | %p.desc 5 | = t('.description') 6 | 7 | %p 8 | = f.text_area :eligibility, class: 'form-control' 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_fees.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :fees 4 | %p.desc 5 | = t('.description') 6 | 7 | %p 8 | = f.text_area :fees, class: 'form-control' 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_interpretation_services.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :interpretation_services 4 | %p.desc 5 | = t('.description') 6 | 7 | %p 8 | = f.text_area :interpretation_services, class: 'form-control' 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_keywords.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | %strong 4 | = f.label :keywords 5 | %p.desc 6 | = t('.description') 7 | 8 | = field_set_tag do 9 | = text_field_tag 'service[keywords][]', service.keywords.join(','), id: 'service_keywords', class: 'form-control' 10 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_languages.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.languages 2 | %header 3 | %strong 4 | Languages this service is provided in. 5 | 6 | = field_set_tag do 7 | = f.select :languages, SETTINGS.try(:[], :languages), {}, multiple: true, class: 'form-control', data: { placeholder: t('.placeholder') } 8 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_new_service_form.html.haml: -------------------------------------------------------------------------------- 1 | = render 'admin/services/forms/fields', f: f, service: service, nested_categories: nested_categories, location: location 2 | 3 | = render 'admin/services/forms/service_locations', location: location, service: service 4 | 5 | .save-box.navbar-default 6 | %p 7 | Creating service for 8 | %strong 9 | = location.name 10 | = f.submit t('admin.buttons.create_service'), class: 'btn btn-success', data: { disable_with: 'Please wait...' } 11 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_program.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.program 2 | %header 3 | = f.label :program_id 4 | %span.desc 5 | = t('.description') 6 | 7 | %p 8 | = program_autocomplete_field_for(f, location) 9 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_required_documents.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.documents 2 | %header 3 | %strong 4 | The documents required to receive this service. 5 | 6 | = field_set_tag do 7 | = f.select :required_documents, SETTINGS.try(:[], :required_documents), {}, multiple: true, class: 'form-control', data: { placeholder: t('.placeholder') } 8 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_service_areas.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | %strong 4 | = f.label :service_areas 5 | %p.desc 6 | = t('.description') 7 | 8 | = field_set_tag do 9 | = f.select :service_areas, SETTINGS.try(:[], :valid_service_areas), {}, multiple: true, class: 'form-control', data: { placeholder: t('.placeholder') } 10 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_service_locations.html.haml: -------------------------------------------------------------------------------- 1 | - if location.organization.locations.size > 1 2 | .content-box 3 | %h2 Copy this service to other Locations 4 | %p 5 | If this service is offered at other locations, you can copy it to those 6 | locations. Then, make sure to update the service at the other locations 7 | with any information that is different, such as the hours of operation. 8 | 9 | - locs ||= location.organization.locations.pluck(:id, :name) 10 | 11 | - locs.each do |loc| 12 | - next if loc.first == location.id 13 | - next if Location.find(loc.first).services.pluck(:name).include?(service.name) 14 | = check_box_tag 'service[locations][]', loc.first, false, id: loc.first 15 | = label_tag loc.first, loc.second 16 | %br 17 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_status.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | %strong 4 | = f.label :status 5 | %p.desc 6 | = t('.description') 7 | 8 | = field_set_tag do 9 | - if f.object.new_record? 10 | = f.select :status, Service.status.options, { selected: 'active' }, class: 'form-control' 11 | - else 12 | = f.select :status, Service.status.options, {}, class: 'form-control' 13 | -------------------------------------------------------------------------------- /app/views/admin/services/forms/_wait.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :wait_time 4 | %p.desc 5 | = t('.description') 6 | 7 | %p 8 | = f.text_area :wait_time, class: 'form-control' 9 | -------------------------------------------------------------------------------- /app/views/admin/services/index.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Below you should see a list of services (and their corresponding location) that you are allowed to administer. 3 | If there are any entries missing, 4 | please #{mail_to SETTINGS[:admin_support_email], 'let us know'}. 5 | %p 6 | To start updating, click on one of the links, which will take you to the details page 7 | for the service. 8 | 9 | - if current_admin.super_admin? 10 | %p 11 | As a super admin, you have access to all services in the database. Please make updates responsibly. 12 | 13 | %p 14 | - @services.each do |service| 15 | %ul 16 | - location_name ||= Location.find(service[0]).name 17 | = link_to "#{service[2]} / #{location_name}", edit_admin_location_service_path(service[0], service[1]) 18 | = paginate @services 19 | -------------------------------------------------------------------------------- /app/views/admin/services/new.html.haml: -------------------------------------------------------------------------------- 1 | .content-box 2 | %h1 Create a new service 3 | 4 | = form_for [:admin, @location, @service], html: { method: :post, class: 'edit-entry' } do |f| 5 | = render 'admin/services/forms/new_service_form', f: f, location: @location, service: @service, nested_categories: @nested_categories 6 | -------------------------------------------------------------------------------- /app/views/admin/shared/_navigation.html.haml: -------------------------------------------------------------------------------- 1 | %header.navbar.navbar-default.navbar-fixed-top 2 | .container 3 | .navbar-header 4 | = button_tag type: 'button', class: 'navbar-toggle', data: { toggle: 'collapse', target: '.navbar-collapse' } do 5 | %span.sr-only 6 | Toggle navigation 7 | %span.icon-bar 8 | %span.icon-bar 9 | %span.icon-bar 10 | = link_to t('titles.admin', brand: t('titles.brand')), 11 | admin_dashboard_url, class: 'navbar-brand' 12 | %nav.collapse.navbar-collapse 13 | %ul.nav.navbar-nav 14 | = render 'admin/shared/navigation_links' 15 | -------------------------------------------------------------------------------- /app/views/admin/shared/_navigation_links.html.haml: -------------------------------------------------------------------------------- 1 | - if admin_signed_in? 2 | %li 3 | = link_to t('navigation.edit_account'), edit_admin_registration_path 4 | %li 5 | = link_to t('admin.buttons.organizations'), admin_organizations_path 6 | %li 7 | = link_to t('admin.buttons.locations'), admin_locations_path 8 | %li 9 | = link_to t('admin.buttons.services'), admin_services_path 10 | %li 11 | = link_to t('navigation.sign_out'), destroy_admin_session_path, method: 'delete' 12 | %ul.nav.navbar-nav.navbar-right 13 | %li 14 | = link_to "Hi, #{current_admin.name}", '#', class: 'logged-in' 15 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_alternate_name.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :alternate_name 4 | %span.desc 5 | = t('.description', type: f.object.model_name.human.downcase) 6 | 7 | = field_set_tag do 8 | .row 9 | .col-sm-6 10 | = f.text_field :alternate_name, required: false, maxlength: 255, class: 'form-control' 11 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_choose_org.html.haml: -------------------------------------------------------------------------------- 1 | - if f.object.new_record? 2 | .inst-box 3 | %header 4 | = f.label :organization_id 5 | %p 6 | = org_autocomplete_field_for(f, current_admin) 7 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_email.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :email 4 | %span.desc 5 | = t('.description', type: f.object.model_name.human.downcase) 6 | 7 | = field_set_tag do 8 | .row 9 | .col-sm-6 10 | = f.email_field :email, required: false, maxlength: 255, class: 'form-control' 11 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_funding_sources.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.funding_sources 2 | %header 3 | %strong 4 | = f.label :funding_sources 5 | %p.desc 6 | = t('.description', type: f.object.model_name.human.downcase) 7 | 8 | = field_set_tag do 9 | = f.select :funding_sources, SETTINGS.try(:[], :funding_sources), {}, multiple: true, class: 'form-control', data: { placeholder: t('.placeholder') } 10 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_holiday_hours.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.holiday-hours 2 | %header 3 | %strong 4 | Holiday Hours of operation 5 | = f.fields_for :holiday_schedules do |builder| 6 | = render 'admin/locations/forms/holiday_schedule_fields', f: builder 7 | = link_to_add_fields I18n.t('admin.buttons.add_holiday_schedule'), f, :holiday_schedules 8 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_hours.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box.hours 2 | %header 3 | %strong 4 | Hours of operation 5 | = f.fields_for :regular_schedules do |builder| 6 | = render 'admin/locations/forms/regular_schedule_fields', f: builder 7 | = link_to_add_fields I18n.t('admin.buttons.add_hours_of_operation'), f, :regular_schedules 8 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_name.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :name 4 | = field_set_tag do 5 | .row 6 | .col-sm-6 7 | = f.text_field :name, required: false, maxlength: 255, class: 'form-control' 8 | -------------------------------------------------------------------------------- /app/views/admin/shared/forms/_website.html.haml: -------------------------------------------------------------------------------- 1 | .inst-box 2 | %header 3 | = f.label :website 4 | = field_set_tag do 5 | .row 6 | .col-sm-6 7 | = f.url_field :website, required: false, maxlength: 255, class: 'form-control' 8 | -------------------------------------------------------------------------------- /app/views/admin_mailer/existing_email_signup.html.haml: -------------------------------------------------------------------------------- 1 | %p A request was made to use this email address to sign up for an account on #{@portal}. This email address is already in use. If you requested this change, please use the following link to sign in to #{@portal}: 2 | 3 | %p= link_to @sign_in_url, @sign_in_url, target: '_blank', rel: 'noopener' 4 | 5 | %p If you would like to create a new account on #{@portal}, please #{link_to 'sign up', @sign_up_url, target: '_blank', rel: 'noopener'} with a different email address. 6 | 7 | %p If you cannot remember your password, please follow the instructions for #{link_to 'resetting your password', @password_url, target: '_blank', rel: 'noopener'}. 8 | 9 | %p If you did not request a new account, please ignore this email. 10 | -------------------------------------------------------------------------------- /app/views/api_applications/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1 2 | = @api_application.name 3 | %div 4 | %p API Token: #{@api_application.api_token} 5 | = render 'form', api_application: @api_application 6 | -------------------------------------------------------------------------------- /app/views/api_applications/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Developer Applications 2 | %p These are the applications you have registered to use the Ohana API. 3 | 4 | %ul 5 | - @api_applications.each do |api_application| 6 | %li 7 | = link_to "#{api_application.name} (#{api_application.main_url})", api_application_path(api_application) 8 | %br 9 | = link_to t('buttons.register_new_application'), new_api_application_path, class: 'btn btn-primary' 10 | -------------------------------------------------------------------------------- /app/views/api_applications/new.html.haml: -------------------------------------------------------------------------------- 1 | %h3 Register a new application 2 | = render 'form', api_application: @api_application 3 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.haml: -------------------------------------------------------------------------------- 1 | .authform 2 | = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, role: 'form' }) do |f| 3 | %h3 Resend confirmation instructions 4 | = render 'devise/shared/error_messages', resource: resource 5 | .form-group 6 | = f.label :email 7 | = f.email_field :email, autofocus: true, class: 'form-control' 8 | = f.submit t('buttons.resend_confirmation_instructions'), class: 'button center' 9 | %br 10 | = render 'devise/shared/links' 11 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p Hi #{@resource.name}, 2 | %p Thanks for signing up for a developer account on #{t('titles.brand')}! 3 | %p You can confirm your account email through the link below: 4 | %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token, subdomain: ENV.fetch('DEV_SUBDOMAIN', nil)) 5 | %p 6 | After you've confirmed your account, check out the 7 | = link_to 'documentation', 'http://codeforamerica.github.io/ohana-api-docs/' 8 | to get started. 9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p Hello #{@resource.name}! 2 | %p Someone has requested a link to change your password. You can do this through the link below. 3 | %p= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token, subdomain: ENV.fetch('DEV_SUBDOMAIN', nil)) 4 | %p If you didn't request this, please ignore this email. 5 | %p Your password won't change until you access the link above and create a new one. 6 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.haml: -------------------------------------------------------------------------------- 1 | .authform 2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, role: 'form' }) do |f| 3 | %h3 Change your password 4 | = render 'devise/shared/error_messages', resource: resource 5 | = f.hidden_field :reset_password_token 6 | .form-group 7 | = f.label :password 8 | = f.password_field :password, autofocus: true, autocomplete: 'off', class: 'form-control' 9 | .form-group 10 | = f.label :password_confirmation 11 | = f.password_field :password_confirmation, autocomplete: 'off', class: 'form-control' 12 | = f.submit t('buttons.change_my_password'), class: 'button right' 13 | %br 14 | = render 'devise/shared/links' 15 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.haml: -------------------------------------------------------------------------------- 1 | .authform 2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, role: 'form' }) do |f| 3 | %h3 Forgot your password? 4 | %p 5 | Enter the email you signed up with, and we'll send you password reset instructions. 6 | = render 'devise/shared/error_messages', resource: resource 7 | .form-group 8 | = f.label :email 9 | = f.email_field :email, autofocus: true, class: 'form-control' 10 | = f.submit t('buttons.send_reset_password_instructions'), class: 'button center' 11 | %br 12 | = render 'devise/shared/links' 13 | -------------------------------------------------------------------------------- /app/views/devise/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

4 | <%= I18n.t("errors.messages.not_saved", 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | %> 8 |

9 | 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/layouts/admin.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{ content: 'width=device-width, initial-scale=1.0', name: 'viewport' } 5 | %title= content_for?(:title) ? yield(:title) : t('titles.admin', brand: t('titles.brand')) 6 | %meta{ content: content_for?(:description) ? yield(:description) : "Admin Interface for #{t('titles.brand')}", name: 'description' } 7 | = stylesheet_link_tag 'admin/application', media: 'all' 8 | = csrf_meta_tags 9 | = yield(:head) 10 | %body{ class: "#{controller_name} #{action_name}" } 11 | = render 'admin/shared/navigation' 12 | #main{ role: 'main' } 13 | .content 14 | .container 15 | .row 16 | = render 'shared/messages' 17 | = yield 18 | = javascript_include_tag 'admin/application' 19 | -------------------------------------------------------------------------------- /app/views/shared/_messages.html.haml: -------------------------------------------------------------------------------- 1 | - flash.each do |name, msg| 2 | - if msg.is_a?(String) 3 | .alert-dismissible{ class: "alert alert-#{name.to_s == 'notice' ? 'success' : 'danger'}", role: 'alert' } 4 | = button_tag(type: 'button', class: 'close', data: { dismiss: 'alert' }) do 5 | %span{ 'aria-hidden' => 'true' } 6 | × 7 | %span.sr-only 8 | Close 9 | = content_tag :div, msg, id: "flash_#{name}" 10 | -------------------------------------------------------------------------------- /app/views/shared/_navigation.html.haml: -------------------------------------------------------------------------------- 1 | %header.navbar.navbar-default.navbar-fixed-top 2 | .container 3 | .navbar-header 4 | = button_tag type: 'button', class: 'navbar-toggle', data: { toggle: 'collapse', target: '.navbar-collapse' } do 5 | %span.sr-only 6 | Toggle navigation 7 | %span.icon-bar 8 | %span.icon-bar 9 | %span.icon-bar 10 | = link_to t('titles.developer', brand: t('titles.brand')), root_path, class: 'navbar-brand' 11 | %nav.collapse.navbar-collapse 12 | %ul.nav.navbar-nav 13 | = render 'shared/navigation_links' 14 | -------------------------------------------------------------------------------- /app/views/shared/_navigation_links.html.haml: -------------------------------------------------------------------------------- 1 | - if user_signed_in? 2 | %li 3 | = link_to t('navigation.edit_account'), edit_user_registration_path 4 | %li 5 | = link_to t('navigation.your_apps'), api_applications_path 6 | %li 7 | = link_to t('navigation.sign_out'), destroy_user_session_path, method: 'delete' 8 | %ul.nav.navbar-nav.navbar-right 9 | %li 10 | = link_to "Hi, #{current_user.name}", '#', class: 'logged-in' 11 | - else 12 | %li 13 | = link_to t('navigation.sign_in'), new_user_session_path 14 | %li 15 | = link_to t('navigation.sign_up'), new_user_registration_path 16 | -------------------------------------------------------------------------------- /bin/autospec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'autospec' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rspec-core', 'autospec') 17 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | # 7 | # This file was generated by Bundler. 8 | # 9 | # The application 'rspec' is installed as part of a gem, and 10 | # this file is here to facilitate running it. 11 | # 12 | 13 | require 'pathname' 14 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 15 | Pathname.new(__FILE__).realpath) 16 | 17 | require 'rubygems' 18 | require 'bundler/setup' 19 | 20 | load Gem.bin_path('rspec-core', 'rspec') 21 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast 4 | # It gets overwritten when you run the `spring binstub` command 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m) 11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) 12 | ENV["GEM_HOME"] = "" 13 | Gem.paths = ENV 14 | 15 | gem "spring", match[1] 16 | require "spring/binstub" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /categories-in-ohana-api-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/categories-in-ohana-api-admin.png -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('config/environment', __dir__) 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/database.travis.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: postgresql 3 | database: ohana_api_test 4 | username: postgres -------------------------------------------------------------------------------- /config/database.vagrant.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | database: ohana_api_development 4 | template: template0 5 | encoding: utf8 6 | host: localhost 7 | port: 5432 8 | pool: 5 9 | timeout: 5000 10 | username: vagrant 11 | password: ohanatest 12 | 13 | # Warning: The database defined as "test" will be erased and 14 | # re-generated from your development database when you run "rake". 15 | # Do not set this db to the same as development or production. 16 | test: 17 | adapter: postgresql 18 | database: ohana_api_test 19 | template: template0 20 | encoding: utf8 21 | host: localhost 22 | port: 5432 23 | pool: 5 24 | timeout: 5000 25 | username: vagrant 26 | password: ohanatest 27 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: utf8 4 | host: localhost 5 | port: 5432 6 | pool: <%= ENV['DB_POOL'] || ENV['MAX_THREADS'] || 5 %> 7 | url: <%= ENV['DATABASE_URL'] %> 8 | timeout: 5000 9 | 10 | development: 11 | <<: *default 12 | database: ohana_api_development 13 | 14 | # Warning: The database defined as "test" will be erased and 15 | # re-generated from your development database when you run "rake". 16 | # Do not set this db to the same as development or production. 17 | test: 18 | <<: *default 19 | database: ohana_api_test 20 | 21 | production: 22 | pool: <%= ENV['DB_POOL'] || ENV['MAX_THREADS'] || 5 %> 23 | url: <%= ENV['DATABASE_URL'] %> 24 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/initializers/active_model_serializers.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport.on_load(:active_model_serializers) do 2 | # Disable for all serializers (except ArraySerializer) 3 | ActiveModel::Serializer.root = false 4 | 5 | # Disable for ArraySerializer 6 | ActiveModel::ArraySerializer.root = false 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Add Yarn node_modules folder to the asset load path. 10 | # Rails.application.config.assets.paths << Rails.root.join('node_modules') 11 | 12 | # Precompile additional assets. 13 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 14 | # Rails.application.config.assets.precompile += %w( search.js ) 15 | -------------------------------------------------------------------------------- /config/initializers/auto_strip_attributes.rb: -------------------------------------------------------------------------------- 1 | AutoStripAttributes::Config.setup do 2 | filters_enabled[:squish] = true 3 | end 4 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't 4 | # wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem 8 | # that might stem from framework code. 9 | # Rails.backtrace_cleaner.remove_silencers! 10 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :hybrid 4 | -------------------------------------------------------------------------------- /config/initializers/csv_shaper.rb: -------------------------------------------------------------------------------- 1 | CsvShaper.configure do |config| 2 | config.header_inflector = :underscore 3 | end 4 | -------------------------------------------------------------------------------- /config/initializers/figaro.rb: -------------------------------------------------------------------------------- 1 | require Rails.root.join('lib/config_validator.rb') 2 | 3 | # Define the environment variables that should be set in config/application.yml. 4 | # See config/application.example.yml if you don't have config/application.yml. 5 | Figaro.require_keys('API_PATH') if ENV['API_SUBDOMAIN'].blank? 6 | Figaro.require_keys('ADMIN_PATH') if ENV['ADMIN_SUBDOMAIN'].blank? 7 | Figaro.require_keys( 8 | 'ASSET_HOST', 9 | 'DEFAULT_PER_PAGE', 10 | 'DOMAIN_NAME', 11 | 'MAX_PER_PAGE' 12 | ) 13 | ConfigValidator.new.validate 14 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += %i[ 5 | password password_confirmation api_token email 6 | ] 7 | -------------------------------------------------------------------------------- /config/initializers/generators.rb: -------------------------------------------------------------------------------- 1 | # Rails.application.config.generators do |g| 2 | # end 3 | -------------------------------------------------------------------------------- /config/initializers/geocoder.rb: -------------------------------------------------------------------------------- 1 | Geocoder.configure( 2 | lookup: :google, 3 | api_key: ENV.fetch('GOOGLE_GEOCODING_API_KEY', nil), 4 | http_proxy: ENV.fetch('QUOTAGUARD_URL', nil), 5 | use_https: true, 6 | cache: Rails.cache, 7 | always_raise: [ 8 | Geocoder::OverQueryLimitError, 9 | Geocoder::RequestDenied, 10 | Geocoder::InvalidRequest, 11 | Geocoder::InvalidApiKey 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/kaminari.rb: -------------------------------------------------------------------------------- 1 | module Kaminari 2 | # Kaminar's built-in methods for getting the next page 3 | # and determining if a page is the last page are wrong, 4 | # so I'm overriding them. 5 | module PageScopeMethods 6 | def next_page 7 | current_page < total_pages ? (current_page + 1) : nil 8 | end 9 | 10 | def last_page? 11 | current_page == total_pages 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /config/initializers/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | # See Kaminari README for more details about config options: 2 | # https://github.com/amatsuda/kaminari#general-configuration-options 3 | Kaminari.configure do |config| 4 | config.default_per_page = ENV['DEFAULT_PER_PAGE'].to_i 5 | config.max_per_page = ENV['MAX_PER_PAGE'].to_i 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_ohana-api_session' 4 | -------------------------------------------------------------------------------- /config/initializers/version.rb: -------------------------------------------------------------------------------- 1 | OHANA_API_VERSION = File.read('VERSION').chomp 2 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | config/application.yml 7 | config/settings.yml 8 | ].each { |path| Spring.watch(path) } 9 | 10 | Spring.watch_method = :listen 11 | -------------------------------------------------------------------------------- /data/ohana_api_development.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/data/ohana_api_development.dump -------------------------------------------------------------------------------- /data/sample-csv/holiday_schedules.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,22,,"January 01, 0001","January 01, 0001",true,, 3 | 2,22,,"12/24/2015","12/24/15",false,10:00,16:00 4 | 3,22,,"June 01, 0001","September 01, 0001",true,, 5 | 4,,22,"January 01, 0001","January 01, 0001",true,, 6 | -------------------------------------------------------------------------------- /data/sample-csv/programs.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,name,alternate_name 2 | 1,1,Defeat Hunger, 3 | -------------------------------------------------------------------------------- /data/sample-csv/regular_schedules.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,weekday,opens_at,closes_at 2 | 1,22,,Monday,08:00,17:00 3 | 2,22,,Tuesday,08:00,17:00 4 | 3,22,,Wednesday,08:00,17:00 5 | 4,22,,Thursday,08:00,17:00 6 | 5,22,,Friday,08:00,17:00 7 | 6,22,,Saturday,10:00,18:00 8 | 7,22,,Sunday,11:00,17:00 9 | 8,,22,Monday,08:00,12:00 10 | 9,,22,Monday,14:00,4:00 PM 11 | 10,,22,Wednesday,10:00,3pm 12 | 13,1,,Monday,9am,03:00 13 | -------------------------------------------------------------------------------- /db/migrate/20140328034023_create_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateAddresses < ActiveRecord::Migration 2 | def change 3 | create_table :addresses do |t| 4 | t.belongs_to :location 5 | t.text :street 6 | t.text :city 7 | t.text :state 8 | t.text :zip 9 | 10 | t.timestamps 11 | end 12 | add_index :addresses, :location_id 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140328034531_create_organizations.rb: -------------------------------------------------------------------------------- 1 | class CreateOrganizations < ActiveRecord::Migration 2 | def change 3 | create_table :organizations do |t| 4 | t.text :name 5 | t.text :urls 6 | t.text :slug 7 | 8 | t.timestamps 9 | end 10 | add_index :organizations, :slug, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140328034754_create_locations.rb: -------------------------------------------------------------------------------- 1 | class CreateLocations < ActiveRecord::Migration 2 | def change 3 | create_table :locations do |t| 4 | t.belongs_to :organization 5 | t.text :accessibility 6 | t.text :admin_emails 7 | t.text :description 8 | t.text :emails 9 | t.text :hours 10 | t.text :kind 11 | t.float :latitude 12 | t.float :longitude 13 | t.text :languages 14 | t.text :name 15 | t.text :short_desc 16 | t.text :transportation 17 | t.text :urls 18 | t.text :slug 19 | 20 | t.timestamps 21 | end 22 | add_index :locations, :slug, unique: true 23 | add_index :locations, :organization_id 24 | add_index :locations, [:latitude, :longitude] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20140328041648_create_api_applications.rb: -------------------------------------------------------------------------------- 1 | class CreateApiApplications < ActiveRecord::Migration 2 | def change 3 | create_table :api_applications do |t| 4 | t.belongs_to :user 5 | t.text :name 6 | t.text :main_url 7 | t.text :callback_url 8 | t.text :api_token 9 | 10 | t.timestamps 11 | end 12 | add_index :api_applications, :user_id 13 | add_index :api_applications, :api_token, unique: true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20140328041859_create_contacts.rb: -------------------------------------------------------------------------------- 1 | class CreateContacts < ActiveRecord::Migration 2 | def change 3 | create_table :contacts do |t| 4 | t.belongs_to :location 5 | t.text :name 6 | t.text :title 7 | t.text :email 8 | t.text :fax 9 | t.text :phone 10 | t.text :extension 11 | 12 | t.timestamps 13 | end 14 | add_index :contacts, :location_id 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20140328042108_create_faxes.rb: -------------------------------------------------------------------------------- 1 | class CreateFaxes < ActiveRecord::Migration 2 | def change 3 | create_table :faxes do |t| 4 | t.belongs_to :location 5 | t.text :number 6 | t.text :department 7 | 8 | t.timestamps 9 | end 10 | add_index :faxes, :location_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140328042218_create_mail_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateMailAddresses < ActiveRecord::Migration 2 | def change 3 | create_table :mail_addresses do |t| 4 | t.belongs_to :location 5 | t.text :attention 6 | t.text :street 7 | t.text :city 8 | t.text :state 9 | t.text :zip 10 | 11 | t.timestamps 12 | end 13 | add_index :mail_addresses, :location_id 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20140328042359_create_phones.rb: -------------------------------------------------------------------------------- 1 | class CreatePhones < ActiveRecord::Migration 2 | def change 3 | create_table :phones do |t| 4 | t.belongs_to :location 5 | t.text :number 6 | t.text :department 7 | t.text :extension 8 | t.text :vanity_number 9 | 10 | t.timestamps 11 | end 12 | add_index :phones, :location_id 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140328043104_create_services.rb: -------------------------------------------------------------------------------- 1 | class CreateServices < ActiveRecord::Migration 2 | def change 3 | create_table :services do |t| 4 | t.belongs_to :location 5 | t.text :audience 6 | t.text :description 7 | t.text :eligibility 8 | t.text :fees 9 | t.text :how_to_apply 10 | t.text :name 11 | t.text :short_desc 12 | t.text :urls 13 | t.text :wait 14 | t.text :funding_sources 15 | t.text :service_areas 16 | t.text :keywords 17 | 18 | t.timestamps 19 | end 20 | add_index :services, :location_id 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20140328044447_create_categories.rb: -------------------------------------------------------------------------------- 1 | class CreateCategories < ActiveRecord::Migration 2 | def change 3 | create_table :categories do |t| 4 | t.text :name 5 | t.text :oe_id 6 | t.text :slug 7 | 8 | t.timestamps 9 | end 10 | add_index :categories, :slug, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140328052427_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration 2 | 3 | def self.up 4 | create_table :friendly_id_slugs do |t| 5 | t.string :slug, :null => false 6 | t.integer :sluggable_id, :null => false 7 | t.string :sluggable_type, :limit => 40 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type], :unique => true 12 | add_index :friendly_id_slugs, :sluggable_type 13 | end 14 | 15 | def self.down 16 | drop_table :friendly_id_slugs 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20140402222453_create_categories_services.rb: -------------------------------------------------------------------------------- 1 | class CreateCategoriesServices < ActiveRecord::Migration 2 | def change 3 | create_table :categories_services, id: false do |t| 4 | t.belongs_to :category, null: false 5 | t.belongs_to :service, null: false 6 | end 7 | add_index(:categories_services, [:service_id, :category_id], unique: true) 8 | add_index(:categories_services, [:category_id, :service_id], unique: true) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140404220233_add_ancestry_to_category.rb: -------------------------------------------------------------------------------- 1 | class AddAncestryToCategory < ActiveRecord::Migration 2 | def change 3 | add_column :categories, :ancestry, :string 4 | add_index :categories, :ancestry 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20140424182454_remove_kind_from_locations.rb: -------------------------------------------------------------------------------- 1 | class RemoveKindFromLocations < ActiveRecord::Migration 2 | def change 3 | remove_column :locations, :kind, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140508030926_add_search_index_to_organizations.rb: -------------------------------------------------------------------------------- 1 | class AddSearchIndexToOrganizations < ActiveRecord::Migration 2 | def up 3 | execute "create index organizations_name on organizations using gin(to_tsvector('english', name))" 4 | end 5 | 6 | def down 7 | execute "drop index organizations_name" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140508031024_add_search_index_to_categories.rb: -------------------------------------------------------------------------------- 1 | class AddSearchIndexToCategories < ActiveRecord::Migration 2 | def up 3 | execute "create index categories_name on categories using gin(to_tsvector('english', name))" 4 | end 5 | 6 | def down 7 | execute "drop index categories_name" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140522153640_add_number_type_to_phones.rb: -------------------------------------------------------------------------------- 1 | class AddNumberTypeToPhones < ActiveRecord::Migration 2 | def change 3 | add_column :phones, :number_type, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140630171418_add_super_admin_to_admins.rb: -------------------------------------------------------------------------------- 1 | class AddSuperAdminToAdmins < ActiveRecord::Migration 2 | def change 3 | add_column :admins, :super_admin, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140829154350_add_search_indexes_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddSearchIndexesToServices < ActiveRecord::Migration 2 | def up 3 | execute "create index services_service_areas on services using gin(to_tsvector('english', service_areas))" 4 | end 5 | 6 | def down 7 | execute "drop index services_service_areas" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140929221750_add_fields_to_organization.rb: -------------------------------------------------------------------------------- 1 | class AddFieldsToOrganization < ActiveRecord::Migration 2 | def change 3 | add_column :organizations, :alternate_name, :string 4 | add_column :organizations, :date_incorporated, :date 5 | add_column :organizations, :description, :text, null: false 6 | add_column :organizations, :email, :string 7 | add_column :organizations, :legal_status, :string 8 | add_column :organizations, :tax_id, :string 9 | add_column :organizations, :tax_status, :string 10 | add_column :organizations, :website, :string 11 | 12 | remove_column :organizations, :urls, :string 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20141007144757_add_fields_to_location.rb: -------------------------------------------------------------------------------- 1 | class AddFieldsToLocation < ActiveRecord::Migration 2 | def change 3 | add_column :locations, :alternate_name, :string 4 | add_column :locations, :virtual, :boolean, default: false 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20141009185459_add_fields_to_address.rb: -------------------------------------------------------------------------------- 1 | class AddFieldsToAddress < ActiveRecord::Migration 2 | def change 3 | add_column :addresses, :country_code, :string, null: false 4 | add_column :addresses, :street_2, :string 5 | rename_column :addresses, :zip, :postal_code 6 | rename_column :addresses, :street, :street_1 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20141009204519_add_fields_to_mail_address.rb: -------------------------------------------------------------------------------- 1 | class AddFieldsToMailAddress < ActiveRecord::Migration 2 | def change 3 | add_column :mail_addresses, :country_code, :string, null: false 4 | add_column :mail_addresses, :street_2, :string 5 | rename_column :mail_addresses, :zip, :postal_code 6 | rename_column :mail_addresses, :street, :street_1 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20141010031124_add_country_prefix_to_phones.rb: -------------------------------------------------------------------------------- 1 | class AddCountryPrefixToPhones < ActiveRecord::Migration 2 | def change 3 | add_column :phones, :country_prefix, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141010155451_drop_faxes_table.rb: -------------------------------------------------------------------------------- 1 | class DropFaxesTable < ActiveRecord::Migration 2 | def up 3 | drop_table :faxes 4 | end 5 | 6 | def down 7 | create_table :faxes do |t| 8 | t.belongs_to :location 9 | t.text :number 10 | t.text :department 11 | 12 | t.timestamps 13 | end 14 | add_index :faxes, :location_id 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20141010171020_update_contact_fields.rb: -------------------------------------------------------------------------------- 1 | class UpdateContactFields < ActiveRecord::Migration 2 | def change 3 | add_column :contacts, :department, :string 4 | remove_column :contacts, :fax, :string 5 | remove_column :contacts, :phone, :string 6 | remove_column :contacts, :extension, :string 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20141010171817_add_contact_ref_to_phones.rb: -------------------------------------------------------------------------------- 1 | class AddContactRefToPhones < ActiveRecord::Migration 2 | def change 3 | add_reference :phones, :contact, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141021195019_update_service_status.rb: -------------------------------------------------------------------------------- 1 | class UpdateServiceStatus < ActiveRecord::Migration 2 | def change 3 | change_column_null :services, :status, false 4 | change_column_default :services, :status, 'active' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20141023040419_add_active_to_locations.rb: -------------------------------------------------------------------------------- 1 | class AddActiveToLocations < ActiveRecord::Migration 2 | def change 3 | add_column :locations, :active, :boolean, default: true 4 | add_index :locations, :active 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20141024022657_create_programs.rb: -------------------------------------------------------------------------------- 1 | class CreatePrograms < ActiveRecord::Migration 2 | def change 3 | create_table :programs do |t| 4 | t.belongs_to :organization 5 | t.string :name 6 | t.string :alternate_name 7 | 8 | t.timestamps 9 | end 10 | add_index :programs, :organization_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20141024025404_add_program_ref_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddProgramRefToServices < ActiveRecord::Migration 2 | def change 3 | add_reference :services, :program, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141027154101_create_regular_schedules.rb: -------------------------------------------------------------------------------- 1 | class CreateRegularSchedules < ActiveRecord::Migration 2 | def change 3 | create_table :regular_schedules do |t| 4 | t.integer :weekday 5 | t.time :opens_at 6 | t.time :closes_at 7 | t.references :service, index: true 8 | t.references :location, index: true 9 | end 10 | add_index :regular_schedules, :weekday 11 | add_index :regular_schedules, :opens_at 12 | add_index :regular_schedules, :closes_at 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20141029170109_remove_hours_from_locations.rb: -------------------------------------------------------------------------------- 1 | class RemoveHoursFromLocations < ActiveRecord::Migration 2 | def change 3 | remove_column :locations, :hours, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141030012617_create_holiday_schedules.rb: -------------------------------------------------------------------------------- 1 | class CreateHolidaySchedules < ActiveRecord::Migration 2 | def change 3 | create_table :holiday_schedules do |t| 4 | t.references :location, index: true 5 | t.references :service, index: true 6 | t.boolean :closed, null: false 7 | t.date :start_date, null: false 8 | t.date :end_date, null: false 9 | t.time :opens_at 10 | t.time :closes_at 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20141030204742_add_array_fields_to_organization.rb: -------------------------------------------------------------------------------- 1 | class AddArrayFieldsToOrganization < ActiveRecord::Migration 2 | def change 3 | add_column :organizations, :funding_sources, :string, array: true, default: [] 4 | add_column :organizations, :accreditations, :string, array: true, default: [] 5 | add_column :organizations, :licenses, :string, array: true, default: [] 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20141106215928_add_organization_ref_to_contacts.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationRefToContacts < ActiveRecord::Migration 2 | def change 3 | add_reference :contacts, :organization, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141107161835_add_service_ref_to_contacts.rb: -------------------------------------------------------------------------------- 1 | class AddServiceRefToContacts < ActiveRecord::Migration 2 | def change 3 | add_reference :contacts, :service, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141108042551_add_organization_ref_to_phones.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationRefToPhones < ActiveRecord::Migration 2 | def change 3 | add_reference :phones, :organization, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141108183056_add_service_ref_to_phones.rb: -------------------------------------------------------------------------------- 1 | class AddServiceRefToPhones < ActiveRecord::Migration 2 | def change 3 | add_reference :phones, :service, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141108194838_replace_location_urls_with_website.rb: -------------------------------------------------------------------------------- 1 | class ReplaceLocationUrlsWithWebsite < ActiveRecord::Migration 2 | def change 3 | add_column :locations, :website, :string 4 | remove_column :locations, :urls, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20141108214741_add_website_index_to_locations.rb: -------------------------------------------------------------------------------- 1 | class AddWebsiteIndexToLocations < ActiveRecord::Migration 2 | def up 3 | execute "CREATE INDEX locations_website_with_varchar_pattern_ops ON locations (website varchar_pattern_ops);" 4 | end 5 | 6 | def down 7 | execute "DROP INDEX locations_website_with_varchar_pattern_ops" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20141109021540_replace_location_emails_with_email.rb: -------------------------------------------------------------------------------- 1 | class ReplaceLocationEmailsWithEmail < ActiveRecord::Migration 2 | def change 3 | add_column :locations, :email, :string 4 | remove_column :locations, :emails, :text 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20141109022202_add_email_index_to_locations.rb: -------------------------------------------------------------------------------- 1 | class AddEmailIndexToLocations < ActiveRecord::Migration 2 | def up 3 | execute "CREATE INDEX locations_email_with_varchar_pattern_ops ON locations (email varchar_pattern_ops);" 4 | end 5 | 6 | def down 7 | execute "DROP INDEX locations_email_with_varchar_pattern_ops" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20141118132537_rename_wait_to_wait_time.rb: -------------------------------------------------------------------------------- 1 | class RenameWaitToWaitTime < ActiveRecord::Migration 2 | def change 3 | rename_column :services, :wait, :wait_time 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20141208165502_rename_oe_id_to_taxonomy_id.rb: -------------------------------------------------------------------------------- 1 | class RenameOeIdToTaxonomyId < ActiveRecord::Migration 2 | def change 3 | rename_column :categories, :oe_id, :taxonomy_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150107163352_add_interpretation_services_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddInterpretationServicesToServices < ActiveRecord::Migration 2 | def change 3 | add_column :services, :interpretation_services, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150314204202_rename_fields.rb: -------------------------------------------------------------------------------- 1 | class RenameFields < ActiveRecord::Migration 2 | def change 3 | rename_column :services, :how_to_apply, :application_process 4 | rename_column :addresses, :street_1, :address_1 5 | rename_column :addresses, :street_2, :address_2 6 | rename_column :addresses, :state, :state_province 7 | rename_column :addresses, :country_code, :country 8 | rename_column :mail_addresses, :street_1, :address_1 9 | rename_column :mail_addresses, :street_2, :address_2 10 | rename_column :mail_addresses, :state, :state_province 11 | rename_column :mail_addresses, :country_code, :country 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20150315202808_update_null_constraints.rb: -------------------------------------------------------------------------------- 1 | class UpdateNullConstraints < ActiveRecord::Migration 2 | def change 3 | change_column_null :services, :application_process, true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | web: 4 | build: . 5 | volumes: 6 | - .:/ohana-api 7 | ports: 8 | - "8080:8080" 9 | environment: 10 | DATABASE_URL: "postgres://postgres@db" 11 | depends_on: 12 | - db 13 | db: 14 | image: postgres 15 | -------------------------------------------------------------------------------- /lib/address_extractor.rb: -------------------------------------------------------------------------------- 1 | AddressExtractor = Struct.new(:path) do 2 | def self.extract_addresses(path) 3 | new(path).csv_entries 4 | end 5 | 6 | def csv_entries 7 | @csv_entries ||= SmarterCSV.process(path, convert_values_to_numeric: false) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/api_constraints.rb: -------------------------------------------------------------------------------- 1 | class ApiConstraints 2 | def initialize(options) 3 | @version = options[:version] 4 | end 5 | 6 | def matches?(request) 7 | versioned_accept_header?(request) || @version == 1 8 | end 9 | 10 | private 11 | 12 | def versioned_accept_header?(request) 13 | accept = request.headers['Accept'] 14 | return false if accept.blank? 15 | 16 | mime_type, version = accept.gsub(/\s/, '').split(';') 17 | mime_type.match(/vnd\.ohanapi\+json/) && version == "version=#{@version}" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/lib/assets/.gitkeep -------------------------------------------------------------------------------- /lib/category_id_collector.rb: -------------------------------------------------------------------------------- 1 | module CategoryIdCollector 2 | def cat_ids(taxonomy_ids) 3 | return [] if taxonomy_ids.blank? 4 | 5 | Category.where(taxonomy_id: taxonomy_ids).pluck(:id) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/category_importer.rb: -------------------------------------------------------------------------------- 1 | class CategoryImporter < EntityImporter 2 | def valid? 3 | @valid ||= categories.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(categories) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | categories.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[taxonomy_id name parent_id parent_name] 18 | end 19 | 20 | protected 21 | 22 | def categories 23 | @categories ||= csv_entries.each_with_object([]) do |chunks, result| 24 | chunks.each { |row| result << CategoryPresenter.new(row).to_category } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/category_presenter.rb: -------------------------------------------------------------------------------- 1 | CategoryPresenter = Struct.new(:row) do 2 | def to_category 3 | return Category.create(category_params) if row[:parent_id].blank? 4 | return parent_category unless parent_category.valid? 5 | 6 | child_category 7 | end 8 | 9 | def parent_category 10 | @parent_category ||= Category.find_or_create_by(name: row[:parent_name], 11 | taxonomy_id: row[:parent_id]) 12 | end 13 | 14 | def child_category 15 | parent_category.children.create(category_params) 16 | end 17 | 18 | private 19 | 20 | def category_params 21 | raw_params = ActionController::Parameters.new(row) 22 | raw_params.permit(:taxonomy_id, :name) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/contact_importer.rb: -------------------------------------------------------------------------------- 1 | class ContactImporter < EntityImporter 2 | def valid? 3 | @valid ||= contacts.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(contacts) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | contacts.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id location_id organization_id service_id department email name 18 | title] 19 | end 20 | 21 | protected 22 | 23 | def contacts 24 | @contacts ||= csv_entries.each_with_object([]) do |chunks, result| 25 | chunks.each { |row| result << ContactPresenter.new(row).to_contact } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/contact_presenter.rb: -------------------------------------------------------------------------------- 1 | ContactPresenter = Struct.new(:row) do 2 | include ParentAssigner 3 | 4 | def to_contact 5 | contact = Contact.find_or_initialize_by(id: row[:id].to_i) 6 | contact.attributes = row 7 | assign_parents_for(contact, row) 8 | contact 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/default_host.rb: -------------------------------------------------------------------------------- 1 | class DefaultHost 2 | def call(request) 3 | host = request.host 4 | return host if host.end_with?(default_host) 5 | 6 | default_host 7 | end 8 | 9 | private 10 | 11 | def default_host 12 | @default_host ||= Figaro.env.domain_name 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/entity_importer.rb: -------------------------------------------------------------------------------- 1 | require 'csv' 2 | 3 | EntityImporter = Struct.new(:path) do 4 | def self.import_file(path) 5 | new(path).tap(&:import) 6 | end 7 | 8 | def self.check_and_import_file(path) 9 | check = FileChecker.new(path, required_headers).validate 10 | 11 | return if check == 'skip import' 12 | 13 | Kernel.puts("\n===> Importing #{path.to_s.split('/').last}") 14 | process_import(path) 15 | end 16 | 17 | def self.process_import(path) 18 | importer = import_file(path) 19 | importer.errors.each { |e| Kernel.puts(e) } unless importer.valid? 20 | end 21 | 22 | protected 23 | 24 | def csv_entries 25 | @csv_entries ||= SmarterCSV.process(path, chunk_size: 100) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/entity_presenter.rb: -------------------------------------------------------------------------------- 1 | module EntityPresenter 2 | def to_array(row, *fields) 3 | fields.each do |field| 4 | row[field] = 5 | if row[field].blank? 6 | [] 7 | else 8 | row[field].split(',').map(&:squish) 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Exceptions 2 | class InvalidRadius < ArgumentError 3 | end 4 | 5 | class InvalidLatLon < ArgumentError 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/holiday_schedule_importer.rb: -------------------------------------------------------------------------------- 1 | class HolidayScheduleImporter < EntityImporter 2 | def valid? 3 | @valid ||= holiday_schedules.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(holiday_schedules) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | holiday_schedules.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id location_id service_id closed start_date end_date opens_at 18 | closes_at] 19 | end 20 | 21 | protected 22 | 23 | def holiday_schedules 24 | @holiday_schedules ||= csv_entries.each_with_object([]) do |chunks, result| 25 | chunks.each { |row| result << HolidaySchedulePresenter.new(row).to_holiday_schedule } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/holiday_schedule_presenter.rb: -------------------------------------------------------------------------------- 1 | HolidaySchedulePresenter = Struct.new(:row) do 2 | include ParentAssigner 3 | 4 | def to_holiday_schedule 5 | holiday_schedule = HolidaySchedule.find_or_initialize_by(id: row[:id].to_i) 6 | holiday_schedule.attributes = row 7 | assign_parents_for(holiday_schedule, row) 8 | holiday_schedule 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/importer_errors.rb: -------------------------------------------------------------------------------- 1 | ImporterErrors = Struct.new(:record, :line_number) do 2 | def self.messages_for(records = []) 3 | records.each_with_index.filter_map do |record, index| 4 | new(record, index + 2).message 5 | end 6 | end 7 | 8 | def message 9 | "Line #{line_number}: #{error_messages}" unless record.valid? 10 | end 11 | 12 | protected 13 | 14 | def error_messages 15 | record.errors.full_messages.uniq.join(', ') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/mail_address_importer.rb: -------------------------------------------------------------------------------- 1 | class MailAddressImporter < EntityImporter 2 | def valid? 3 | @valid ||= mail_addresses.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(mail_addresses) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | mail_addresses.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id location_id attention address_1 address_2 city state_province postal_code 18 | country] 19 | end 20 | 21 | protected 22 | 23 | def mail_addresses 24 | @mail_addresses ||= csv_entries.each_with_object([]) do |chunks, result| 25 | chunks.each { |row| result << MailAddressPresenter.new(row).to_mail_address } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/mail_address_presenter.rb: -------------------------------------------------------------------------------- 1 | MailAddressPresenter = Struct.new(:row) do 2 | include ParentAssigner 3 | 4 | def to_mail_address 5 | mail_address = MailAddress.find_or_initialize_by(id: row[:id].to_i) 6 | mail_address.attributes = row 7 | assign_parents_for(mail_address, row) 8 | mail_address 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/organization_importer.rb: -------------------------------------------------------------------------------- 1 | class OrganizationImporter < EntityImporter 2 | def valid? 3 | @valid ||= organizations.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(organizations) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | organizations.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id accreditations alternate_name date_incorporated 18 | description email funding_sources legal_status 19 | licenses name tax_id tax_status website] 20 | end 21 | 22 | private 23 | 24 | def organizations 25 | @organizations ||= csv_entries.each_with_object([]) do |chunks, orgs| 26 | chunks.each { |row| orgs << OrganizationPresenter.new(row).to_org } 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/organization_presenter.rb: -------------------------------------------------------------------------------- 1 | OrganizationPresenter = Struct.new(:row) do 2 | include EntityPresenter 3 | 4 | def to_org 5 | org = Organization.find_or_initialize_by(id: row[:id].to_i) 6 | to_array(row, :accreditations, :licenses, :funding_sources) 7 | org.attributes = row 8 | org.id = row[:id].to_i 9 | org 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/parent_assigner.rb: -------------------------------------------------------------------------------- 1 | module ParentAssigner 2 | private 3 | 4 | def assign_parents_for(record, row) 5 | foreign_keys_in(row).each do |key| 6 | next if row[key].nil? 7 | 8 | record[key] = row[key].to_i 9 | end 10 | end 11 | 12 | def foreign_keys_in(row) 13 | row.keys.select { |key| key.to_s == 'id' || key.to_s.include?('_id') } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/phone_importer.rb: -------------------------------------------------------------------------------- 1 | class PhoneImporter < EntityImporter 2 | def valid? 3 | @valid ||= phones.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(phones) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | phones.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id location_id organization_id service_id contact_id department 18 | extension number number_type vanity_number country_prefix] 19 | end 20 | 21 | protected 22 | 23 | def phones 24 | @phones ||= csv_entries.each_with_object([]) do |chunks, result| 25 | chunks.each { |row| result << PhonePresenter.new(row).to_phone } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/phone_presenter.rb: -------------------------------------------------------------------------------- 1 | PhonePresenter = Struct.new(:row) do 2 | include ParentAssigner 3 | 4 | def to_phone 5 | phone = Phone.find_or_initialize_by(id: row[:id].to_i) 6 | phone.attributes = row 7 | assign_parents_for(phone, row) 8 | phone 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/program_importer.rb: -------------------------------------------------------------------------------- 1 | class ProgramImporter < EntityImporter 2 | def valid? 3 | @valid ||= programs.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(programs) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | programs.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id organization_id name alternate_name] 18 | end 19 | 20 | protected 21 | 22 | def programs 23 | @programs ||= csv_entries.each_with_object([]) do |chunks, result| 24 | chunks.each { |row| result << ProgramPresenter.new(row).to_program } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/program_presenter.rb: -------------------------------------------------------------------------------- 1 | ProgramPresenter = Struct.new(:row) do 2 | include ParentAssigner 3 | 4 | def to_program 5 | program = Program.find_or_initialize_by(id: row[:id].to_i) 6 | program.attributes = row 7 | assign_parents_for(program, row) 8 | program 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/regular_schedule_importer.rb: -------------------------------------------------------------------------------- 1 | class RegularScheduleImporter < EntityImporter 2 | def valid? 3 | @valid ||= regular_schedules.all?(&:valid?) 4 | end 5 | 6 | def errors 7 | ImporterErrors.messages_for(regular_schedules) 8 | end 9 | 10 | def import 11 | ActiveRecord::Base.no_touching do 12 | regular_schedules.each(&:save) 13 | end 14 | end 15 | 16 | def self.required_headers 17 | %w[id location_id service_id weekday opens_at closes_at] 18 | end 19 | 20 | protected 21 | 22 | def regular_schedules 23 | @regular_schedules ||= csv_entries.each_with_object([]) do |chunks, result| 24 | chunks.each { |row| result << RegularSchedulePresenter.new(row).to_regular_schedule } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/regular_schedule_presenter.rb: -------------------------------------------------------------------------------- 1 | RegularSchedulePresenter = Struct.new(:row) do 2 | include ParentAssigner 3 | 4 | def to_regular_schedule 5 | regular_schedule = RegularSchedule.find_or_initialize_by(id: row[:id].to_i) 6 | regular_schedule.attributes = row 7 | assign_parents_for(regular_schedule, row) 8 | regular_schedule 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/service_presenter.rb: -------------------------------------------------------------------------------- 1 | ServicePresenter = Struct.new(:row) do 2 | include EntityPresenter 3 | include ParentAssigner 4 | include CategoryIdCollector 5 | 6 | # rubocop:disable Metrics/AbcSize 7 | def to_service 8 | service = Service.find_or_initialize_by(id: row[:id].to_i) 9 | to_array(row, :accepted_payments, :funding_sources, :keywords, :languages, 10 | :required_documents, :service_areas, :taxonomy_ids) 11 | service.attributes = row.except(:taxonomy_ids) 12 | assign_categories_to(service, row[:taxonomy_ids]) 13 | assign_parents_for(service, row.except(:taxonomy_ids)) 14 | service 15 | end 16 | # rubocop:enable Metrics/AbcSize 17 | 18 | private 19 | 20 | def assign_categories_to(service, taxonomy_ids) 21 | service.category_ids = cat_ids(taxonomy_ids) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/subdomain_constraints.rb: -------------------------------------------------------------------------------- 1 | class SubdomainConstraints 2 | def initialize(options) 3 | @subdomain = options[:subdomain] 4 | end 5 | 6 | def matches?(request) 7 | if @subdomain.present? 8 | request.subdomain == @subdomain 9 | else 10 | request.subdomain.blank? || request.subdomain == 'www' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/remove_test_users_and_admins.rake: -------------------------------------------------------------------------------- 1 | task remove_test_users_and_admins: :environment do 2 | User.includes(:api_applications). 3 | where(email: %w[user@example.com user2@example.com]).destroy_all 4 | 5 | Admin. 6 | where(email: %w[ohana@samaritanhouse.com ohana@gmail.com masteradmin@ohanapi.org]). 7 | destroy_all 8 | end 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/public/favicon.ico -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | /* the humans responsible & colophon */ 2 | /* humanstxt.org */ 3 | 4 | 5 | /* TEAM */ 6 | : 7 | Site: 8 | Twitter: 9 | Location: 10 | 11 | /* THANKS */ 12 | Daniel Kehoe (@rails_apps) for the RailsApps project 13 | 14 | /* SITE */ 15 | Standards: HTML5, CSS3 16 | Components: jQuery 17 | Software: Ruby on Rails 18 | 19 | /* GENERATED BY */ 20 | RailsApps application template: http://railsapps.github.io/ 21 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | bin/setup --docker 6 | 7 | echo "===> Building the Docker image..." 8 | docker-compose build 9 | 10 | echo "===> Setting up the database on the Docker image..." 11 | docker-compose run --rm web bundle exec rake db:reset RAILS_ENV=development 12 | docker-compose run --rm web bundle exec rails db:environment:set RAILS_ENV=test 13 | docker-compose run --rm web bundle exec rake db:reset RAILS_ENV=test 14 | docker-compose run --rm web pg_restore -c --no-owner -d ohana_api_development /ohana-api/data/ohana_api_development.dump -U postgres -h db 15 | 16 | echo "===> Starting the Docker image..." 17 | docker-compose up 18 | -------------------------------------------------------------------------------- /script/export: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "===> Exporting the ohana_api_development DB into data/ohana_api_development.dump" 6 | pg_dump -Fc --no-acl --no-owner -h localhost ohana_api_development > data/ohana_api_development.dump 7 | echo "===> Done exporting the DB." -------------------------------------------------------------------------------- /script/export_prod_db: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo 'Removing test users and admins' 6 | bin/rake remove_test_users_and_admins 7 | 8 | echo Exporting the ohana_api_development DB into data/ohana_api_production.dump... 9 | pg_dump -Fc --no-acl --no-owner -h localhost ohana_api_development > data/ohana_api_production.dump 10 | 11 | echo Done exporting the DB. 12 | -------------------------------------------------------------------------------- /script/geocode: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "===> Geocoding locations. This will take several minutes..." 6 | rake geocode:all CLASS=Location sleep=0.25 -------------------------------------------------------------------------------- /script/import: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | sleep_time="$1" 6 | 7 | bin/rake import:all sleep=$sleep_time 8 | echo 'All done, rock on!' 9 | -------------------------------------------------------------------------------- /script/reset: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo Resetting the database... 6 | bin/rake db:reset 7 | echo Done resetting the database. 8 | -------------------------------------------------------------------------------- /script/reset_test_db: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | dropdb ohana_api_test 6 | bin/rake db:create:all 7 | bin/rake db:migrate 8 | -------------------------------------------------------------------------------- /script/restore: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "===> Restoring the DB from data/ohana_api_development.dump..." 6 | pg_restore -c --no-owner -d ohana_api_development data/ohana_api_development.dump 7 | echo "===> Done restoring the DB." 8 | -------------------------------------------------------------------------------- /script/restore_prod_db: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "===> Restoring the DB from data/ohana_api_production.dump..." 6 | pg_restore -c --no-owner -d ohana_api_development data/ohana_api_production.dump 7 | echo "===> Done restoring the DB." 8 | -------------------------------------------------------------------------------- /script/setup_db: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "===> Creating ohana_api_development and ohana_api_test databases..." 6 | bin/rake db:create:all 7 | 8 | echo "===> Setting up the ohana_api_development database..." 9 | bin/rake db:structure:load 10 | 11 | echo "===> Populating the ohana_api_development database..." 12 | script/restore 13 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | spring rspec 6 | rubocop 7 | haml-lint app/views 8 | -------------------------------------------------------------------------------- /script/users: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "===> Seeding the DB with test users..." 6 | bin/rake db:seed -------------------------------------------------------------------------------- /spec/active_record_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | connection_info = YAML.load_file('config/database.yml')['test'] 4 | ActiveRecord::Base.establish_connection(connection_info) 5 | 6 | RSpec.configure do |config| 7 | config.around do |example| 8 | ActiveRecord::Base.transaction do 9 | example.run 10 | raise ActiveRecord::Rollback 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/api/services/update_service_categories_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'PUT /services/:service_id/categories' do 4 | before(:all) do 5 | create_service 6 | end 7 | 8 | after(:all) do 9 | Organization.find_each(&:destroy) 10 | end 11 | 12 | it 'returns 200 when validations pass' do 13 | create(:category) 14 | create(:health) 15 | put( 16 | api_service_categories_url(@service, subdomain: ENV.fetch('API_SUBDOMAIN', nil)), 17 | taxonomy_ids: %w[101 102] 18 | ) 19 | 20 | expect(response).to have_http_status(:ok) 21 | expect(json['categories'][0]['name']).to eq 'Food' 22 | expect(json['categories'][1]['name']).to eq 'Health' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/controllers/home_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe HomeController do 4 | describe "GET 'index'" do 5 | it 'returns http success' do 6 | get 'index' 7 | expect(response).to be_successful 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/api_applications.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryBot.define do 4 | factory :api_application do 5 | name { 'test app' } 6 | main_url { 'http://cfa.org' } 7 | callback_url { 'http://cfa.org/callback' } 8 | 9 | factory :app_with_extra_whitespace do 10 | name { ' app with extra whitespace ' } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/factories/categories.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryBot.define do 4 | factory :category do 5 | name { 'Food' } 6 | taxonomy_id { '101' } 7 | end 8 | 9 | factory :health, class: 'Category' do 10 | name { 'Health' } 11 | taxonomy_id { '102' } 12 | end 13 | 14 | factory :jobs, class: 'Category' do 15 | name { 'Jobs' } 16 | taxonomy_id { '105' } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/factories/contacts.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryBot.define do 4 | factory :contact do 5 | name { 'Moncef Belyamani' } 6 | title { 'CTO' } 7 | 8 | factory :foobar do 9 | name { 'Foo' } 10 | title { 'Bar' } 11 | end 12 | 13 | factory :contact_with_extra_whitespace do 14 | name { 'Foo ' } 15 | title { ' Bar' } 16 | email { ' foo@bar.com ' } 17 | department { 'Screening ' } 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/factories/holiday_schedules.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :holiday_schedule do 3 | closed { true } 4 | start_date { 'December 24, 2014' } 5 | end_date { 'December 24, 2014' } 6 | 7 | trait :open do 8 | closed { false } 9 | opens_at { '9am' } 10 | closes_at { '17:00' } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/factories/mail_addresses.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryBot.define do 4 | factory :mail_address do 5 | attention { 'Monfresh' } 6 | address_1 { '1 davis dr' } 7 | city { 'Belmont' } 8 | state_province { 'CA' } 9 | postal_code { '90210' } 10 | country { 'US' } 11 | association :location, factory: :no_address 12 | end 13 | 14 | factory :mail_address_with_extra_whitespace, class: 'MailAddress' do 15 | attention { ' Moncef ' } 16 | address_1 { '8875 La Honda Road' } 17 | city { 'La Honda ' } 18 | state_province { ' CA ' } 19 | postal_code { ' 94020' } 20 | country { ' US ' } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/phones.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryBot.define do 4 | factory :phone do 5 | number { '650 851-1210' } 6 | number_type { 'voice' } 7 | extension { '200' } 8 | end 9 | 10 | factory :phone_with_extra_whitespace, class: 'Phone' do 11 | country_prefix { '33 ' } 12 | number { '650 851-1210 ' } 13 | department { ' Information ' } 14 | extension { '2000 ' } 15 | vanity_number { ' 800-FLY-AWAY ' } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/factories/programs.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :program do 3 | name { 'Collection of Services ' } 4 | alternate_name { ' Also Known As' } 5 | organization 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/regular_schedules.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :regular_schedule do 3 | weekday { 7 } 4 | opens_at { '9:30' } 5 | closes_at { '5pm' } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryBot.define do 4 | factory :user do 5 | name { 'Test User' } 6 | email { 'valid@example.com' } 7 | password { 'mong01dtest' } 8 | password_confirmation { 'mong01dtest' } 9 | # required if the Devise Confirmable module is used 10 | confirmed_at { Time.zone.now } 11 | 12 | factory :user_with_app do 13 | after(:create) do |user| 14 | create(:api_application, user: user) 15 | end 16 | end 17 | end 18 | 19 | factory :unconfirmed_user, class: :user do 20 | name { 'Unconfirmed User' } 21 | email { 'invalid@example.com' } 22 | password { 'mong01dtest' } 23 | password_confirmation { 'mong01dtest' } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/features/admin/contacts/delete_contact_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Delete contact' do 4 | before do 5 | @location = create(:location) 6 | @location.contacts.create!(attributes_for(:contact)) 7 | login_super_admin 8 | visit '/admin/locations/vrs-services' 9 | click_link 'Moncef Belyamani' 10 | end 11 | 12 | it 'when deleting contact' do 13 | find_link(I18n.t('admin.buttons.delete_contact')).click 14 | using_wait_time 5 do 15 | expect(page).to have_current_path admin_location_path(@location), ignore_query: true 16 | expect(page).not_to have_link 'Moncef Belyamani' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/admin/csv/download_addresses_csv_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Downloading Addresses CSV' do 4 | before do 5 | login_super_admin 6 | @address = create(:address, location_id: 1) 7 | visit admin_csv_addresses_path(format: 'csv') 8 | end 9 | 10 | it 'contains the same headers as in the import Wiki' do 11 | expect(csv.first).to eq %w[id location_id address_1 address_2 city 12 | state_province postal_code country] 13 | end 14 | 15 | it 'populates address attribute values' do 16 | expect(csv.second).to eq [ 17 | @address.id.to_s, @address.location_id.to_s, '1800 Easton Drive', nil, 18 | 'Burlingame', 'CA', '94010', 'US' 19 | ] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/csv/download_mail_addresses_csv_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Downloading Mail Addresses CSV' do 4 | before do 5 | login_super_admin 6 | @mail_address = create(:mail_address) 7 | visit admin_csv_mail_addresses_path(format: 'csv') 8 | end 9 | 10 | it 'contains the same headers as in the import Wiki' do 11 | expect(csv.first).to eq %w[id location_id attention address_1 address_2 12 | city state_province postal_code country] 13 | end 14 | 15 | it 'populates mail address attribute values' do 16 | expect(csv.second).to eq [ 17 | @mail_address.id.to_s, @mail_address.location_id.to_s, 'Monfresh', 18 | '1 davis dr', nil, 'Belmont', 'CA', '90210', 'US' 19 | ] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/csv/download_programs_csv_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Downloading Programs CSV' do 4 | before do 5 | login_super_admin 6 | @program = create(:program) 7 | visit admin_csv_programs_path(format: 'csv') 8 | end 9 | 10 | it 'contains the same headers as in the import Wiki' do 11 | expect(csv.first).to eq %w[id organization_id alternate_name name] 12 | end 13 | 14 | it 'populates program attribute values' do 15 | expect(csv.second).to eq [ 16 | @program.id.to_s, @program.organization_id.to_s, 'Also Known As', 17 | 'Collection of Services' 18 | ] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/features/admin/csv/download_regular_schedules_csv_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Downloading Regular Schedules CSV' do 4 | before do 5 | login_super_admin 6 | create_service 7 | @regular_schedule = @location.regular_schedules. 8 | create!(attributes_for(:regular_schedule)) 9 | visit admin_csv_regular_schedules_path(format: 'csv') 10 | end 11 | 12 | it 'contains the same headers as in the import Wiki' do 13 | expect(csv.first).to eq %w[id location_id service_id weekday opens_at 14 | closes_at] 15 | end 16 | 17 | it 'formats the date and time values' do 18 | expect(csv.second).to eq [ 19 | @regular_schedule.id.to_s, @location.id.to_s, nil, 'Sunday', '09:30', 20 | '17:00' 21 | ] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/edit_account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Editing Admin account' do 4 | before do 5 | login_admin 6 | visit '/admin/edit' 7 | end 8 | 9 | it 'allows the name to be changed' do 10 | fill_in 'admin_name', with: 'New Admin name' 11 | fill_in 'admin_current_password', with: @admin.password 12 | click_button I18n.t('buttons.update') 13 | visit '/admin/edit' 14 | expect(find_field('admin_name').value).to eq 'New Admin name' 15 | end 16 | 17 | it 'redirects to the admin edit page after update' do 18 | fill_in 'admin_name', with: 'New Admin name' 19 | fill_in 'admin_current_password', with: @admin.password 20 | click_button I18n.t('buttons.update') 21 | expect(page).to have_current_path edit_admin_registration_path, ignore_query: true 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_alternate_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update alternate name' do 4 | before do 5 | create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with alternate name' do 11 | fill_in 'location_alternate_name', with: 'Juvenile Sexual Responsibility Program' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('location_alternate_name').value). 14 | to eq 'Juvenile Sexual Responsibility Program' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_description_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update description' do 4 | before do 5 | create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with empty description' do 11 | fill_in 'location_description', with: '' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(page).to have_content "Description can't be blank for Location" 14 | end 15 | 16 | it 'with valid description' do 17 | fill_in 'location_description', with: 'This is a description' 18 | click_button I18n.t('admin.buttons.save_changes') 19 | expect(find_field('location_description').value).to eq 'This is a description' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_email_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update email' do 4 | before do 5 | @location = create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid location email' do 11 | fill_in 'location_email', with: 'foo@bar.com' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('location_email').value).to eq 'foo@bar.com' 14 | end 15 | 16 | it 'with invalid location email' do 17 | fill_in 'location_email', with: 'foobar.com' 18 | click_button I18n.t('admin.buttons.save_changes') 19 | expect(page).to have_content 'foobar.com is not a valid email' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update name' do 4 | before do 5 | create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with empty location name' do 11 | fill_in 'location_name', with: '' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(page).to have_content "Name can't be blank for Location" 14 | expect(page).to have_css('.field_with_errors') 15 | end 16 | 17 | it 'with valid location name' do 18 | fill_in 'location_name', with: 'Juvenile Sexual Responsibility Program' 19 | click_button I18n.t('admin.buttons.save_changes') 20 | expect(find_field('location_name').value). 21 | to eq 'Juvenile Sexual Responsibility Program' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_short_description_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update short description' do 4 | before do 5 | create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with empty description' do 11 | fill_in 'location_short_desc', with: '' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('location_short_desc').value).to eq '' 14 | end 15 | 16 | it 'with valid description' do 17 | fill_in 'location_short_desc', with: 'This is a short description' 18 | click_button I18n.t('admin.buttons.save_changes') 19 | expect(find_field('location_short_desc').value). 20 | to eq 'This is a short description' 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_transportation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update transportation options' do 4 | before do 5 | create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with empty transportation options' do 11 | fill_in 'location_transportation', with: '' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('location_transportation').value).to eq '' 14 | end 15 | 16 | it 'with non-empty transportation options' do 17 | fill_in 'location_transportation', with: 'SAMTRANS stops within 1/2 mile.' 18 | click_button I18n.t('admin.buttons.save_changes') 19 | expect(find_field('location_transportation').value). 20 | to eq 'SAMTRANS stops within 1/2 mile.' 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_virtual_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update virtual attribute' do 4 | before do 5 | create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'setting to true' do 11 | select('Does not have a physical address', from: 'location_virtual') 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('location_virtual').value).to eq 'true' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/admin/locations/update_website_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update website' do 4 | before do 5 | @location = create(:location) 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with invalid website' do 11 | fill_in 'location_website', with: 'www.monfresh.com' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(page).to have_content 'www.monfresh.com is not a valid URL' 14 | expect(page).to have_css('.field_with_errors') 15 | end 16 | 17 | it 'with valid website' do 18 | fill_in 'location_website', with: 'http://codeforamerica.org' 19 | click_button I18n.t('admin.buttons.save_changes') 20 | expect(find_field('location_website').value). 21 | to eq 'http://codeforamerica.org' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/delete_contact_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Delete contact' do 4 | before do 5 | @org = create(:organization) 6 | @org.contacts.create!(attributes_for(:contact)) 7 | login_super_admin 8 | visit '/admin/organizations/parent-agency' 9 | click_link 'Moncef Belyamani' 10 | end 11 | 12 | it 'when deleting contact' do 13 | find_link(I18n.t('admin.buttons.delete_contact')).click 14 | using_wait_time 5 do 15 | expect(page).to have_current_path admin_organization_path(@org), ignore_query: true 16 | expect(page).not_to have_link 'Moncef Belyamani' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_alternate_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update alternate name' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with alternate name' do 11 | fill_in 'organization_alternate_name', with: 'Juvenile Sexual Responsibility Program' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('organization_alternate_name').value). 14 | to eq 'Juvenile Sexual Responsibility Program' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_date_incorporated_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update date incorporated' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with date incorporated' do 11 | select_date(Time.zone.today, from: 'organization_date_incorporated') 12 | click_button I18n.t('admin.buttons.save_changes') 13 | 14 | expect(find_field('organization_date_incorporated_1i').value). 15 | to eq Time.zone.today.year.to_s 16 | expect(find_field('organization_date_incorporated_2i').value). 17 | to eq Time.zone.today.month.to_s 18 | expect(find_field('organization_date_incorporated_3i').value). 19 | to eq Time.zone.today.day.to_s 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_email_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update email' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with valid organization email' do 11 | fill_in 'organization_email', with: 'foo@bar.com' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('organization_email').value).to eq 'foo@bar.com' 14 | end 15 | 16 | it 'with invalid organization email' do 17 | fill_in 'organization_email', with: 'foobar.com' 18 | click_button I18n.t('admin.buttons.save_changes') 19 | expect(page).to have_content 'foobar.com is not a valid email' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_legal_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update legal status' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with legal status' do 11 | fill_in 'organization_legal_status', with: 'non-profit' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('organization_legal_status').value).to eq 'non-profit' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update name' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with empty organization name' do 11 | fill_in 'organization_name', with: '' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(page).to have_content "Name can't be blank for Organization" 14 | end 15 | 16 | it 'with valid organization name' do 17 | fill_in 'organization_name', with: 'Juvenile Sexual Responsibility Program' 18 | click_button I18n.t('admin.buttons.save_changes') 19 | expect(find_field('organization_name').value). 20 | to eq 'Juvenile Sexual Responsibility Program' 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_tax_id_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update tax id' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with tax id' do 11 | fill_in 'organization_tax_id', with: '12-1234567' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('organization_tax_id').value).to eq '12-1234567' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/admin/organizations/update_tax_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update tax status' do 4 | before do 5 | create(:organization) 6 | login_super_admin 7 | visit '/admin/organizations/parent-agency' 8 | end 9 | 10 | it 'with tax status' do 11 | fill_in 'organization_tax_status', with: '501(c)(3)' 12 | click_button I18n.t('admin.buttons.save_changes') 13 | expect(find_field('organization_tax_status').value).to eq '501(c)(3)' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/admin/programs/update_alternate_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update alternate name' do 4 | before do 5 | loc = create(:location) 6 | program = loc.organization.programs.create!(attributes_for(:program)) 7 | login_super_admin 8 | visit admin_program_path(program) 9 | end 10 | 11 | it 'with valid alternate_name' do 12 | fill_in 'program_alternate_name', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content t('admin.notices.program_updated') 15 | expect(find_field('program_alternate_name').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/programs/update_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update name' do 4 | before do 5 | loc = create(:location) 6 | program = loc.organization.programs.create!(attributes_for(:program)) 7 | login_super_admin 8 | visit admin_program_path(program) 9 | end 10 | 11 | it 'with empty name' do 12 | fill_in 'program_name', with: '' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content "Name can't be blank for Program" 15 | end 16 | 17 | it 'with valid name' do 18 | fill_in 'program_name', with: 'Youth Counseling' 19 | click_button I18n.t('admin.buttons.save_changes') 20 | expect(page).to have_content t('admin.notices.program_updated') 21 | expect(find_field('program_name').value).to eq 'Youth Counseling' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/services/delete_contact_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Delete contact' do 4 | before do 5 | create_service 6 | @service.contacts.create!(attributes_for(:contact)) 7 | login_super_admin 8 | visit '/admin/locations/vrs-services' 9 | click_link 'Literacy Program' 10 | click_link 'Moncef Belyamani' 11 | end 12 | 13 | it 'when deleting contact' do 14 | find_link(I18n.t('admin.buttons.delete_contact')).click 15 | using_wait_time 5 do 16 | expect(page).to have_current_path admin_location_service_path(@location, @service), 17 | ignore_query: true 18 | expect(page).not_to have_link 'Moncef Belyamani' 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_alternate_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update alternate_name' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | click_link 'Literacy Program' 9 | end 10 | 11 | it 'with valid alternate_name' do 12 | fill_in 'service_alternate_name', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_alternate_name').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_audience_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update audience' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid audience' do 11 | click_link 'Literacy Program' 12 | fill_in 'service_audience', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_audience').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_eligibility_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update eligibility' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid eligibility' do 11 | click_link 'Literacy Program' 12 | fill_in 'service_eligibility', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_eligibility').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_email_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update email' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | click_link 'Literacy Program' 9 | end 10 | 11 | it 'with invalid email' do 12 | fill_in 'service_email', with: 'foobar.com' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'is not a valid email' 15 | end 16 | 17 | it 'with valid email' do 18 | fill_in 'service_email', with: 'ruby@good.com' 19 | click_button I18n.t('admin.buttons.save_changes') 20 | expect(page).to have_content 'Service was successfully updated.' 21 | expect(find_field('service_email').value).to eq 'ruby@good.com' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_fees_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update fees' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid fees' do 11 | click_link 'Literacy Program' 12 | fill_in 'service_fees', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_fees').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_how_to_apply_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update application_process' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid application_process' do 11 | click_link 'Literacy Program' 12 | fill_in 'service_application_process', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_application_process').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_interpretation_services_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update interpretation_services' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid interpretation_services' do 11 | click_link 'Literacy Program' 12 | fill_in 'service_interpretation_services', with: 'CTS LanguageLink' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_interpretation_services').value).to eq 'CTS LanguageLink' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_name_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update name' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | click_link 'Literacy Program' 9 | end 10 | 11 | it 'with empty name' do 12 | fill_in 'service_name', with: '' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content "Name can't be blank for Service" 15 | end 16 | 17 | it 'with valid name' do 18 | fill_in 'service_name', with: 'Youth Counseling' 19 | click_button I18n.t('admin.buttons.save_changes') 20 | expect(page).to have_content 'Service was successfully updated.' 21 | expect(find_field('service_name').value).to eq 'Youth Counseling' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update status' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid status' do 11 | click_link 'Literacy Program' 12 | select 'Defunct', from: 'service_status' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_status').value).to eq 'defunct' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/services/update_wait_time_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Update wait_time' do 4 | before do 5 | create_service 6 | login_super_admin 7 | visit '/admin/locations/vrs-services' 8 | end 9 | 10 | it 'with valid wait_time' do 11 | click_link 'Literacy Program' 12 | fill_in 'service_wait_time', with: 'Youth Counseling' 13 | click_button I18n.t('admin.buttons.save_changes') 14 | expect(page).to have_content 'Service was successfully updated.' 15 | expect(find_field('service_wait_time').value).to eq 'Youth Counseling' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/features/admin/sign_out_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Signing out' do 4 | before do 5 | login_admin 6 | visit edit_admin_registration_path 7 | end 8 | 9 | it 'redirects to the admin sign in page' do 10 | click_link I18n.t('navigation.sign_out') 11 | expect(page).to have_current_path(new_admin_session_path, ignore_query: true) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/features/delete_api_application_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Delete an API Application' do 4 | before do 5 | user = create(:user_with_app) 6 | login_as(user, scope: :user) 7 | name = user.api_applications.first.name 8 | main_url = user.api_applications.first.main_url 9 | visit_app(name, main_url) 10 | end 11 | 12 | it 'deletes the application when the delete button is clicked' do 13 | click_link I18n.t('buttons.delete_application') 14 | 15 | expect(page).to have_content 'Application was successfully deleted.' 16 | expect(page).to have_current_path api_applications_path, ignore_query: true 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/features/sign_out_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Signing out' do 4 | before do 5 | login_user 6 | visit edit_user_registration_path 7 | end 8 | 9 | it 'redirects to the user home page' do 10 | click_link I18n.t('navigation.sign_out') 11 | expect(page).to have_current_path(root_path, ignore_query: true) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/importer_errors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ImporterErrors do 4 | subject(:importer_errors) { ImporterErrors.new(invalid, 6) } 5 | 6 | let(:errors) { double(:errors, full_messages: ['not enough cowbell']) } 7 | let(:invalid) { double(:record, valid?: false, errors: errors) } 8 | 9 | its(:line_number) { is_expected.to eq 6 } 10 | its(:message) { is_expected.to eq 'Line 6: not enough cowbell' } 11 | 12 | describe '.messages_for' do 13 | let(:valid) { double(:record, valid?: true) } 14 | 15 | it 'collects the messages for all records by order of appearance' do 16 | messages = ImporterErrors.messages_for([valid, invalid]) 17 | expect(messages).to eq ['Line 3: not enough cowbell'] 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/category_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Category do 4 | subject { build(:category) } 5 | 6 | it { is_expected.to be_valid } 7 | 8 | it { is_expected.to have_and_belong_to_many(:services) } 9 | 10 | it do 11 | expect(subject).to validate_presence_of(:name). 12 | with_message("can't be blank for Category") 13 | end 14 | 15 | it do 16 | expect(subject).to validate_presence_of(:taxonomy_id). 17 | with_message("can't be blank for Category") 18 | end 19 | 20 | it do 21 | expect(subject).to validate_uniqueness_of(:taxonomy_id). 22 | case_insensitive.with_message('id has already been taken') 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/models/program_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Program do 4 | subject { build(:program) } 5 | 6 | it { is_expected.to be_valid } 7 | 8 | it { is_expected.to validate_presence_of(:name).with_message("can't be blank for Program") } 9 | 10 | it { is_expected.to belong_to(:organization).required } 11 | it { is_expected.to have_many(:services).dependent(:destroy) } 12 | 13 | describe 'auto_strip_attributes' do 14 | it 'strips extra whitespace before validation' do 15 | program = build(:program) 16 | program.valid? 17 | expect(program.name).to eq('Collection of Services') 18 | expect(program.alternate_name).to eq('Also Known As') 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/requests/api_applications_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'ApiApplications' do 4 | describe 'GET /api_applications' do 5 | context 'when not signed in' do 6 | it "redirects to 'users/sign_in'" do 7 | get api_applications_path 8 | expect(response.status).to be(302) 9 | expect(response).to redirect_to(new_user_session_path) 10 | end 11 | end 12 | 13 | context 'when signed in' do 14 | it 'returns a 200' do 15 | user = create(:user) 16 | post( 17 | user_session_path, 18 | params: { user: { email: user.email, password: user.password } } 19 | ) 20 | get api_applications_url 21 | follow_redirect! 22 | expect(response.status).to be(200) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/admin_controller_helper.rb: -------------------------------------------------------------------------------- 1 | module AdminControllerHelper 2 | def log_in_as_admin(admin) 3 | @request.env['devise.mapping'] = Devise.mappings[:admin] 4 | sign_in FactoryBot.create(admin) 5 | end 6 | end 7 | 8 | RSpec.configure do |config| 9 | config.include Devise::Test::ControllerHelpers, type: :controller 10 | config.include AdminControllerHelper, type: :controller 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/api/model_helpers.rb: -------------------------------------------------------------------------------- 1 | def create_service 2 | @location = create(:location) 3 | @service = @location.services.create!(attributes_for(:service)) 4 | end 5 | -------------------------------------------------------------------------------- /spec/support/api_constraints_spec_helper.rb: -------------------------------------------------------------------------------- 1 | def header_for_version(version) 2 | "application/vnd.ohanapi+json;version=#{version}" 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/bullet.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | if Bullet.enable? 3 | config.before do 4 | Bullet.start_request 5 | end 6 | 7 | config.after do 8 | Bullet.perform_out_of_channel_notifications if Bullet.notification? 9 | Bullet.end_request 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/capybara.rb: -------------------------------------------------------------------------------- 1 | require 'webdrivers/chromedriver' 2 | require 'selenium/webdriver' 3 | 4 | Capybara.configure do |config| 5 | config.default_max_wait_time = 5 6 | config.always_include_port = true 7 | end 8 | 9 | Capybara.register_driver :headless_chrome do |app| 10 | browser_options = Selenium::WebDriver::Chrome::Options.new 11 | browser_options.add_argument('--headless') 12 | browser_options.add_argument('--disable-gpu') 13 | 14 | Capybara::Selenium::Driver.new app, 15 | browser: :chrome, 16 | capabilities: [browser_options] 17 | end 18 | 19 | Capybara.javascript_driver = :headless_chrome 20 | Capybara.default_driver = :rack_test 21 | 22 | Webdrivers.cache_time = 86_400 23 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.clean_with(:truncation) 4 | end 5 | 6 | config.before do 7 | DatabaseCleaner.strategy = :transaction 8 | end 9 | 10 | config.before(:each, js: true) do 11 | DatabaseCleaner.strategy = :truncation 12 | end 13 | 14 | config.before do 15 | DatabaseCleaner.start 16 | end 17 | 18 | config.after do 19 | DatabaseCleaner.clean 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/support/fixtures/contact_with_no_parent.csv: -------------------------------------------------------------------------------- 1 | id,location_id,organization_id,service_id,name,title,email,department 2 | 1,,,,John Smith,Food Pantry Manager,john@example.org,Food Pantry 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/existing_category.csv: -------------------------------------------------------------------------------- 1 | taxonomy_id,name,parent_id,parent_name 2 | 101,Emergency,, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/holiday_schedule_with_no_parent.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,,,11/27/2014,11/27/2014,FALSE,10:00,3:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/hs_with_2_digit_year.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,1,,1/2/14,1/12/14,TRUE,, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/hs_with_invalid_date.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,1,,1/11/2014,13/27/2014,FALSE,10:00,3:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/hs_with_spelled_out_date.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,1,,"December 24, 2014","December 24, 2014",TRUE,, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_address.csv: -------------------------------------------------------------------------------- 1 | id,location_id,address_1,address_2,city,state_province,postal_code,country 2 | 1,1,123 Main Street,Suite 101,,VA,22031,US 3 | 2,2,123 Main Street,Suite 101,Fairfax,VA,22031,US 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_category.csv: -------------------------------------------------------------------------------- 1 | taxonomy_id,name,parent_id,parent_name 2 | 101-01,,, 3 | 102,Food,, 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_contact.csv: -------------------------------------------------------------------------------- 1 | id,location_id,organization_id,service_id,name,title,email,department 2 | 1,1,,,,Food Pantry Manager,john@example.org,Food Pantry 3 | 2,,1,,Jane Doe,Food Pantry Manager,jane@example.org,Food Pantry 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_holiday_schedule.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,1,,12/24/2014,12/24/2014,,9:30, 3 | 3,1,,11/27/2014,11/27/2014,FALSE,10:00,3:00 PM 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_location.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,accessibility,admin_emails,alternate_name,description,email,languages,latitude,longitude,name,transportation,virtual,website 2 | 1,1,"disabled_parking, elevator, wheelchair","admin@example.org, john@example.org",,The Palo Alto location of the Harvest Food Bank distributes canned goods only.,info@location.org,"English, Spanish, Tagalog",37.7726402,-122.4099154,,,,http://www.example.org 3 | 3,1,,,,The Atherton location of the Harvest Food Bank distributes canned goods only.,info@location.org,,,,Harvest Food Bank of Atherton,SAMTrans stops 1 block away,false,http://www.example.org 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_location_org.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,accessibility,admin_emails,alternate_name,description,email,languages,latitude,longitude,name,transportation,virtual,website 2 | 1,100,,,,The Palo Alto location of the Harvest Food Bank distributes canned goods only.,,,37.7726402,-122.4099154,Harvest Food Bank,,, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_mail_address.csv: -------------------------------------------------------------------------------- 1 | id,location_id,attention,address_1,address_2,city,state_province,postal_code,country 2 | 1,1,John Smith,,Suite 101,Fairfax,VA,22031,US 3 | 2,1,John Smith,123 Main Street,Suite 101,Fairfax,VA,22031,US 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_mail_address_location.csv: -------------------------------------------------------------------------------- 1 | id,location_id,attention,address_1,address_2,city,state_province,postal_code,country 2 | 1,100,John Smith,123 Main Street,Suite 101,Fairfax,VA,22031,US 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_org.csv: -------------------------------------------------------------------------------- 1 | id,accreditations,alternate_name,date_incorporated,description,email,funding_sources,legal_status,licenses,name,tax_id,tax_status,website 2 | 1,"BBB, State Board of Education",HFB,1/1/1970,"Harvest Food Bank provides fresh produce, dairy, and canned goods to food pantries throughout the city.",info@example.org,"Donations, Grants",Nonprofit,State Health Inspection License,,12-456789,501(c)3,http://www.example.org 3 | 2,,,1/2/2014,Example org description,info@fakeorg.org,Donations,Nonprofit,,Example Agency,,,http://www.example.org 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_parent_category.csv: -------------------------------------------------------------------------------- 1 | taxonomy_id,name,parent_id,parent_name 2 | 101-01,Disaster,0, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_phone.csv: -------------------------------------------------------------------------------- 1 | id,contact_id,location_id,organization_id,service_id,number,extension,department,number_type,vanity_number,country_prefix 2 | 1,,1,,,703-555-1212,123,Food Pantry,,703-555-FOOD,1 3 | 2,,1,,,202-555-1212,,,fax,, 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_program.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,name,alternate_name 2 | 1,1,, 3 | 2,1,Foo, 4 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_program_headers.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,name 2 | 1,1,Defeat Hunger, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_regular_schedule.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,weekday,opens_at,closes_at 2 | 1,1,,Monday,9:30, 3 | 2,1,,Tuesday,9:30,5:00 PM 4 | 3,1,,Wednesday,9:30,5:00 PM 5 | 4,1,,Thursday,9:30,5:00 PM 6 | 5,1,,Friday,9:00,4:00 PM 7 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_service.csv: -------------------------------------------------------------------------------- 1 | id,location_id,program_id,accepted_payments,alternate_name,description,eligibility,email,fees,funding_sources,application_process,keywords,languages,name,required_documents,service_areas,status,wait_time,website 2 | 1,1,,"Cash, Check, Credit Card",,Provides free hot meals to the homeless.,Homeless and low-income residents.,info@service.org,,"DC Government, Donations",Call or apply in person,"hot meels, hungry","English, Spanish, Tagalog",,,"Atherton, Belmont",active,No wait.,http://example.org/service 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/invalid_service_location.csv: -------------------------------------------------------------------------------- 1 | id,location_id,program_id,accepted_payments,alternate_name,description,eligibility,email,fees,funding_sources,application_process,keywords,languages,name,required_documents,service_areas,status,wait_time,website 2 | 1,100,,"Cash, Check, Credit Card",,Provides free hot meals to the homeless.,Homeless and low-income residents.,info@service.org,,"DC Government, Donations",Call or apply in person,"hot meels, hungry","English, Spanish, Tagalog",Example Service,,"Atherton, Belmont",active,No wait.,http://example.org/service 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/missing_address.csv: -------------------------------------------------------------------------------- 1 | id,location_id,address_1,address_2,city,state_province,postal_code,country 2 | 1,100,123 Main Street,Suite 101,,VA,22031,US 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/org_with_2_digit_year.csv: -------------------------------------------------------------------------------- 1 | id,accreditations,alternate_name,date_incorporated,description,email,funding_sources,legal_status,licenses,name,tax_id,tax_status,website 2 | 1,"BBB, State Board of Education",HFB,1/24/70,"Harvest Food Bank provides fresh produce, dairy, and canned goods to food pantries throughout the city.",info@example.org,"Donations, Grants",Nonprofit,State Health Inspection License,Parent Agency,12-456789,501(c)3,http://www.example.org 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/org_with_invalid_date.csv: -------------------------------------------------------------------------------- 1 | id,accreditations,alternate_name,date_incorporated,description,email,funding_sources,legal_status,licenses,name,tax_id,tax_status,website 2 | 1,"BBB, State Board of Education",HFB,24/2/70,"Harvest Food Bank provides fresh produce, dairy, and canned goods to food pantries throughout the city.",info@example.org,"Donations, Grants",Nonprofit,State Health Inspection License,Parent Agency,12-456789,501(c)3,http://www.example.org 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/org_with_spelled_out_date.csv: -------------------------------------------------------------------------------- 1 | id,accreditations,alternate_name,date_incorporated,description,email,funding_sources,legal_status,licenses,name,tax_id,tax_status,website 2 | 1,"BBB, State Board of Education",HFB,"January 20, 1970","Harvest Food Bank provides fresh produce, dairy, and canned goods to food pantries throughout the city.",info@example.org,"Donations, Grants",Nonprofit,State Health Inspection License,Parent Agency,12-456789,501(c)3,http://www.example.org 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/phone_with_no_parent.csv: -------------------------------------------------------------------------------- 1 | id,contact_id,location_id,organization_id,service_id,number,extension,department,number_type,vanity_number,country_prefix 2 | 1,,,,,703-555-1212,123,Food Pantry,voice,703-555-FOOD,1 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/program_with_no_parent.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,name,alternate_name 2 | 1,,Defeat Hunger, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/programs.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,name,alternate_name 2 | 1,1,Defeat Hunger, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/regular_schedule_with_no_parent.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,weekday,opens_at,closes_at 2 | 1,,,Monday,9:30,5:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/services.csv: -------------------------------------------------------------------------------- 1 | id,location_id,program_id,accepted_payments,alternate_name,description,eligibility,email,fees,funding_sources,application_process,keywords,languages,name,required_documents,service_areas,status,wait_time,website 2 | -------------------------------------------------------------------------------- /spec/support/fixtures/taxonomy.csv: -------------------------------------------------------------------------------- 1 | taxonomy_id,name,parent_id,parent_name 2 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_address.csv: -------------------------------------------------------------------------------- 1 | id,location_id,address_1,address_2,city,state_province,postal_code,country 2 | 1,2,123 Main Street,Suite 101,Fairfax,VA,22031,US 3 | 2,3,123 Main Street,Suite 101,Fairfax,VA,22031,US 4 | 4,1,123 Main Street,Suite 101,Fairfax,VA,22031,US 5 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_category.csv: -------------------------------------------------------------------------------- 1 | taxonomy_id,name,parent_id,parent_name 2 | 101,Emergency,, 3 | 101-01,Disaster Response,101,Emergency 4 | 101-02,Emergency Cash,101,Emergency 5 | 101-02-01,Help Pay for Food,101-02,Emergency Cash 6 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_contact_phone.csv: -------------------------------------------------------------------------------- 1 | id,contact_id,location_id,organization_id,service_id,number,extension,department,number_type,vanity_number,country_prefix 2 | 1,1,,,,703-555-1212,123,Food Pantry,voice,703-555-FOOD,1 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_location.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,accessibility,admin_emails,alternate_name,description,email,languages,latitude,longitude,name,transportation,virtual,website 2 | 1,1,"cd, ramp","test@test.com, foo@bar.com",,The Palo Alto location of the Harvest Food Bank distributes canned goods only.,,"French, spanish",37.7726402,-122.4099154,Harvest Food Bank,,, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_location_contact.csv: -------------------------------------------------------------------------------- 1 | id,location_id,organization_id,service_id,name,title,email,department 2 | 2,1,,,John Smith,Food Pantry Manager,john@example.org,Food Pantry 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_location_holiday_schedule.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 2,1,,1/11/2014,11/27/2014,FALSE,10:00,3:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_location_phone.csv: -------------------------------------------------------------------------------- 1 | id,contact_id,location_id,organization_id,service_id,number,extension,department,number_type,vanity_number,country_prefix 2 | 2,,1,,,703-555-1212,123,Food Pantry,voice,703-555-FOOD,1 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_location_regular_schedule.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,weekday,opens_at,closes_at 2 | 2,1,,Monday,9:30,5:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_mail_address.csv: -------------------------------------------------------------------------------- 1 | id,location_id,attention,address_1,address_2,city,state_province,postal_code,country 2 | 2,1,John Smith,123 Main Street,Suite 101,Fairfax,VA,22031,US 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_org.csv: -------------------------------------------------------------------------------- 1 | id,accreditations,alternate_name,date_incorporated,description,email,funding_sources,legal_status,licenses,name,tax_id,tax_status,website 2 | 2,"BBB, State Board of Education",HFB,1/2/1970,"Harvest Food Bank provides fresh produce, dairy, and canned goods to food pantries throughout the city.",info@example.org,"Donations, Grants",Nonprofit,State Health Inspection License,Parent Agency,12-456789,501(c)3,http://www.example.org 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_org_contact.csv: -------------------------------------------------------------------------------- 1 | id,location_id,organization_id,service_id,name,title,email,department 2 | 1,,1,,John Smith,Food Pantry Manager,john@example.org,Food Pantry 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_org_phone.csv: -------------------------------------------------------------------------------- 1 | id,contact_id,location_id,organization_id,service_id,number,extension,department,number_type,vanity_number,country_prefix 2 | 1,,,1,,703-555-1212,123,Food Pantry,voice,703-555-FOOD,1 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_program.csv: -------------------------------------------------------------------------------- 1 | id,organization_id,name,alternate_name 2 | 2,1,Defeat Hunger, 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_service.csv: -------------------------------------------------------------------------------- 1 | id,location_id,program_id,accepted_payments,alternate_name,description,eligibility,email,fees,funding_sources,application_process,interpretation_services,keywords,languages,name,required_documents,service_areas,status,wait_time,website,taxonomy_ids 2 | 1,1,1,"Cash, Check, Credit Card",,Provides free hot meals to the homeless.,Homeless and low-income residents.,info@service.org,,"DC Government, Donations",Call or apply in person,We offer 3-way interpretation services over the phone via Propio Language Services (http://propio-ls.com).,"hot meels, hungry","English, Spanish, Tagalog",Harvest Food Bank of Palo Alto,"Passport,Driver's License","Atherton, Belmont",active,No wait.,http://example.org/service,"101, 102" 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_service_contact.csv: -------------------------------------------------------------------------------- 1 | id,location_id,organization_id,service_id,name,title,email,department 2 | 1,,,1,John Smith,Food Pantry Manager,john@example.org,Food Pantry 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_service_holiday_schedule.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,start_date,end_date,closed,opens_at,closes_at 2 | 1,,1,11/27/2014,11/27/2014,FALSE,10:00,3:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_service_phone.csv: -------------------------------------------------------------------------------- 1 | id,contact_id,location_id,organization_id,service_id,number,extension,department,number_type,vanity_number,country_prefix 2 | 1,,,,1,703-555-1212,123,Food Pantry,voice,703-555-FOOD,1 3 | -------------------------------------------------------------------------------- /spec/support/fixtures/valid_service_regular_schedule.csv: -------------------------------------------------------------------------------- 1 | id,location_id,service_id,weekday,opens_at,closes_at 2 | 1,,1,Monday,9:30,5:00 PM 3 | -------------------------------------------------------------------------------- /spec/support/mailer_macros.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:each, email: true) do 3 | ActionMailer::Base.deliveries = [] 4 | end 5 | end 6 | 7 | module MailerMacros 8 | def first_email 9 | ActionMailer::Base.deliveries.first 10 | end 11 | 12 | def last_email 13 | ActionMailer::Base.deliveries.last 14 | end 15 | 16 | def reset_email 17 | ActionMailer::Base.deliveries = [] 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/request_helpers.rb: -------------------------------------------------------------------------------- 1 | module Requests 2 | # This module allows you to use 'json' within specs as a shortcut for 3 | # 'JSON.parse(response.body)' 4 | module RequestHelpers 5 | def json 6 | @json ||= JSON.parse(response.body) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/rake.rb: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | 3 | shared_context 'rake' do 4 | subject { rake[task_name] } 5 | 6 | let(:rake) { Rake::Application.new } 7 | let(:task_name) { self.class.top_level_description } 8 | let(:task_path) { "lib/tasks/#{task_name.split(':').first}" } 9 | 10 | def loaded_files_excluding_current_rake_file 11 | $LOADED_FEATURES.reject { |file| file == Rails.root.join("#{task_path}.rake").to_s } 12 | end 13 | 14 | before do 15 | Rake.application = rake 16 | Rake.application.rake_require( 17 | task_path, [Rails.root.to_s], loaded_files_excluding_current_rake_file 18 | ) 19 | 20 | Rake::Task.define_task(:environment) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/shared_examples_for_schedules.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'validating opens_at and closes_at values' do |model, attribute| 2 | it 'converts 18:00' do 3 | rs = build(model, attribute => '18:00') 4 | rs.valid? 5 | expect(rs.send(attribute)).to eq(Time.utc(2000, 1, 1, 18, 00, 0)) 6 | end 7 | 8 | it 'converts 5am' do 9 | rs = build(model, attribute => '5am') 10 | rs.valid? 11 | expect(rs.send(attribute)).to eq(Time.utc(2000, 1, 1, 5, 00, 0)) 12 | end 13 | 14 | it 'converts 7pm' do 15 | rs = build(model, attribute => '7pm') 16 | rs.valid? 17 | expect(rs.send(attribute)).to eq(Time.utc(2000, 1, 1, 19, 00, 0)) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/shoulda_matchers.rb: -------------------------------------------------------------------------------- 1 | Shoulda::Matchers.configure do |config| 2 | config.integrate do |with| 3 | # Choose a test framework: 4 | with.test_framework :rspec 5 | 6 | # Choose one or more libraries: 7 | with.library :active_record 8 | with.library :active_model 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/warden.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include Warden::Test::Helpers, type: :feature 3 | 4 | config.before do 5 | Warden.test_mode! 6 | end 7 | 8 | config.after do 9 | Warden.test_reset! 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/webmock.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | 3 | WebMock.disable_net_connect!( 4 | allow: [ 5 | /localhost/, 6 | /127\.0\.0\.1/, 7 | /codeclimate.com/, # For uploading coverage reports 8 | /chromedriver\.storage\.googleapis\.com/ # For fetching a chromedriver binary 9 | ] 10 | ) 11 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/vendor/assets/javascripts/.gitkeep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/vendor/assets/stylesheets/.gitkeep -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeforamerica/ohana-api/e3ab28075a5fc2f2e15ce06403db5c1e785a7778/vendor/plugins/.gitkeep --------------------------------------------------------------------------------