├── .envrc ├── .gemrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── new_user_onboarding.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rspec ├── .rspec_parallel ├── .rubocop.yml ├── .rubocop_todo.yml ├── .ruby-version ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── fonts │ │ ├── Amble-Bold-webfont.woff │ │ ├── Amble-Light-webfont.woff │ │ ├── Amble-Regular-webfont.woff │ │ └── freehand.woff │ ├── images │ │ ├── background.png │ │ ├── dropdown_arrows.svg │ │ ├── logo.svg │ │ ├── troll.svg │ │ └── troll_plus_logo.svg │ ├── javascripts │ │ ├── application.js │ │ ├── bridgetroll.js │ │ ├── checkins.js.coffee │ │ ├── enums │ │ │ └── role.js.erb │ │ ├── event_email_addresser.js.coffee │ │ ├── events.js.coffee │ │ ├── font_analytics.js.erb │ │ ├── forms.js.coffee │ │ ├── helpers │ │ │ └── capitalize.js │ │ ├── imported_event.js │ │ ├── rsvps.js.coffee │ │ ├── section_organizer.js │ │ ├── section_organizer │ │ │ ├── collections │ │ │ │ ├── attendee_collection.js │ │ │ │ ├── section_collection.js │ │ │ │ └── session_collection.js │ │ │ ├── dialogs │ │ │ │ ├── attendee_detail.js │ │ │ │ ├── auto_arrange_choices.js │ │ │ │ ├── base_dialog.js │ │ │ │ ├── edit_section.js │ │ │ │ └── help_dialog.js │ │ │ ├── models │ │ │ │ ├── attendee.js │ │ │ │ ├── section.js │ │ │ │ └── session.js │ │ │ └── views │ │ │ │ ├── base_view.js │ │ │ │ ├── section_organizer_view.js │ │ │ │ └── section_view.js │ │ ├── services │ │ │ ├── footer.js │ │ │ ├── matchHeights.js │ │ │ └── poller.js │ │ ├── sign_in.js.coffee │ │ ├── tables.js.coffee │ │ ├── toggling.js.coffee │ │ └── views │ │ │ └── events_filter_view.js.coffee │ ├── stylesheets │ │ ├── _alerts.scss │ │ ├── _base.scss │ │ ├── _checkins.scss │ │ ├── _constants.scss │ │ ├── _external_pages.scss │ │ ├── _font_mixins.scss │ │ ├── _fonts.scss │ │ ├── _footer.scss │ │ ├── _form.scss.erb │ │ ├── _gravatars.scss │ │ ├── _header.scss │ │ ├── _levels.scss │ │ ├── _mixins.scss │ │ ├── _profile.scss │ │ ├── _section_organizer.scss │ │ ├── _style_guide.scss.erb │ │ ├── _tables.scss │ │ ├── application.css │ │ ├── bootstrap_with_overrides.scss │ │ ├── devise.scss │ │ ├── events │ │ │ ├── _email.scss │ │ │ ├── _form.scss │ │ │ ├── _index.scss │ │ │ ├── _organize.scss │ │ │ └── _show.scss │ │ ├── font_awesome_with_extensions.scss │ │ └── utilities.scss │ └── templates │ │ ├── class_levels_popover.hbs │ │ ├── email_attendees_popover.hbs │ │ ├── imported_event_popover.hbs │ │ └── section_organizer │ │ ├── attendee_detail.hbs │ │ ├── auto_arrange_choices.hbs │ │ ├── edit_section.hbs │ │ ├── help_dialog.hbs │ │ ├── section.hbs │ │ └── section_organizer.hbs ├── controllers │ ├── admin_pages_controller.rb │ ├── application_controller.rb │ ├── chapters │ │ └── leaders_controller.rb │ ├── chapters_controller.rb │ ├── checkiners_controller.rb │ ├── checkins_controller.rb │ ├── courses_controller.rb │ ├── devise_overrides │ │ ├── omniauth_callbacks_controller.rb │ │ └── registrations_controller.rb │ ├── event_sessions_controller.rb │ ├── events │ │ ├── attendee_names_controller.rb │ │ ├── attendees_controller.rb │ │ ├── emails_controller.rb │ │ ├── organizer_tools_controller.rb │ │ ├── students_controller.rb │ │ ├── surveys_controller.rb │ │ └── unpublished_events_controller.rb │ ├── events_controller.rb │ ├── external_events_controller.rb │ ├── locations_controller.rb │ ├── meetup_users_controller.rb │ ├── organizations_controller.rb │ ├── organizers_controller.rb │ ├── profiles_controller.rb │ ├── regions │ │ └── leaders_controller.rb │ ├── regions_controller.rb │ ├── rsvps_controller.rb │ ├── sections_controller.rb │ ├── static_pages_controller.rb │ ├── surveys_controller.rb │ ├── users │ │ └── events_controller.rb │ ├── users_controller.rb │ └── volunteers_controller.rb ├── helpers │ ├── application_helper.rb │ ├── events_helper.rb │ ├── locations_helper.rb │ ├── navigation_helper.rb │ ├── profiles_helper.rb │ └── responsive_helper.rb ├── jobs │ ├── reminder_sender.rb │ └── survey_sender.rb ├── mailers │ ├── admin_mailer.rb │ ├── application_mailer.rb │ ├── event_mailer.rb │ ├── rsvp_mailer.rb │ └── survey_mailer.rb ├── models │ ├── application_record.rb │ ├── authentication.rb │ ├── chapter.rb │ ├── chapter_leadership.rb │ ├── concerns │ │ └── presence_tracking_boolean.rb │ ├── course.rb │ ├── dietary_restriction.rb │ ├── event.rb │ ├── event_details │ │ ├── default_details.html │ │ ├── default_student_details.html │ │ ├── default_survey_greeting.html │ │ └── default_volunteer_details.html │ ├── event_email.rb │ ├── event_email_recipient.rb │ ├── event_session.rb │ ├── external_event.rb │ ├── level.rb │ ├── location.rb │ ├── meetup_user.rb │ ├── operating_system.rb │ ├── organization.rb │ ├── organization_leadership.rb │ ├── organization_subscription.rb │ ├── profile.rb │ ├── region.rb │ ├── region_leadership.rb │ ├── regions_user.rb │ ├── role.rb │ ├── rsvp.rb │ ├── rsvp_session.rb │ ├── section.rb │ ├── survey.rb │ ├── user.rb │ ├── volunteer_assignment.rb │ └── volunteer_preference.rb ├── policies │ ├── application_policy.rb │ ├── chapter_policy.rb │ ├── course_policy.rb │ ├── event_policy.rb │ ├── event_session_policy.rb │ ├── external_event_policy.rb │ ├── level_policy.rb │ ├── location_policy.rb │ ├── organization_policy.rb │ ├── profile_policy.rb │ ├── region_policy.rb │ ├── rsvp_policy.rb │ ├── section_policy.rb │ ├── survey_policy.rb │ └── user_policy.rb ├── presenters │ ├── event_email_presenter.rb │ └── past_event_emails_presenter.rb ├── services │ ├── account_merger.rb │ ├── database_anonymizer.rb │ ├── event_csv_reporter.rb │ ├── event_editor.rb │ ├── event_list.rb │ ├── external_event_importer.rb │ ├── ics_generator.rb │ ├── meetup_importer.rb │ ├── omniauth_providers.rb │ ├── rsvp_sorter.rb │ ├── section_arranger.rb │ ├── user_list.rb │ ├── user_searcher.rb │ └── waitlist_manager.rb ├── validators │ └── array_of_ids_validator.rb └── views │ ├── admin_pages │ └── admin_dashboard.html.erb │ ├── chapters │ ├── _form.html.erb │ ├── _table.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── leaders │ │ └── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── checkiners │ └── index.html.erb │ ├── checkins │ └── index.html.erb │ ├── courses │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ └── new.html.erb │ ├── devise │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ └── reset_password_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ └── new.html.erb │ └── sessions │ │ └── new.html.erb │ ├── event_sessions │ └── index.html.erb │ ├── events │ ├── _attendee_list.html.erb │ ├── _form.html.erb │ ├── _guidelines.html.erb │ ├── _location_modal.html.erb │ ├── _organizer_preworkshop_buttons.html.erb │ ├── _upcoming_event.html.erb │ ├── attendees │ │ └── index.html.erb │ ├── edit.html.erb │ ├── emails │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── feed.atom.builder │ ├── feed.rss.builder │ ├── index.html.erb │ ├── levels.html.erb │ ├── new.html.erb │ ├── organizer_tools │ │ ├── _checkin_counts.html.erb │ │ ├── _childcare_requests.html.erb │ │ ├── _operating_system_breakdown.erb │ │ ├── diets.html.erb │ │ ├── index.html.erb │ │ └── organize_sections.html.erb │ ├── past_events.html.erb │ ├── show.html.erb │ ├── students │ │ └── index.html.erb │ ├── surveys │ │ └── edit.html.erb │ └── unpublished_events │ │ ├── _mark_as_spam_button.html.erb │ │ ├── _publish_event_button.html.erb │ │ ├── _unpublished_event.html.erb │ │ └── index.html.erb │ ├── external_events │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ └── new.html.erb │ ├── layouts │ ├── application.html.erb │ └── mailer.html.erb │ ├── locations │ ├── _form.html.erb │ ├── create.js.erb │ ├── create_failed.js.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── mailers │ ├── event_mailer │ │ ├── _new_event_detail.html.erb │ │ ├── _session_location_detail.html.erb │ │ ├── event_has_been_approved.html.erb │ │ ├── event_pending_approval.html.erb │ │ ├── from_organizer.html.erb │ │ ├── new_event.html.erb │ │ ├── new_organizer_alert.erb │ │ └── unpublished_event.html.erb │ ├── rsvp_mailer │ │ ├── _event_details.html.erb │ │ ├── childcare_notification.html.erb │ │ ├── email.html.erb │ │ └── off_waitlist.html.erb │ └── survey_mailer │ │ └── notification.html.erb │ ├── meetup_users │ └── show.html.erb │ ├── organizations │ ├── _chapter_infowindow.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── organizers │ └── index.html.erb │ ├── profiles │ └── show.html.erb │ ├── regions │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── leaders │ │ └── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── rsvps │ ├── _class_levels.html.erb │ ├── _dietary_restrictions.html.erb │ ├── _form.html.erb │ ├── edit.html.erb │ ├── new.html.erb │ └── quick_destroy_confirm.html.erb │ ├── shared │ ├── _actions.html.erb │ ├── _chapter_select.html.erb │ ├── _checkin_event_sessions.html.erb │ ├── _devise_error_messages.html.erb │ ├── _events_table.html.erb │ ├── _google_analytics.html.erb │ ├── _locations_table.html.erb │ ├── _map.html.erb │ ├── _model_error_messages.html.erb │ ├── _navbar.html.erb │ ├── _organizer_action.erb │ ├── _organizer_breadcrumb.erb │ ├── _past_events_table.erb │ ├── _plus_one_disclaimer.html.erb │ ├── _profile_gravatars.html.erb │ ├── _region_select.html.erb │ ├── _rsvp_actions.html.erb │ ├── _rsvp_actions_unauthenticated.html.erb │ ├── _rsvps.html.erb │ ├── _sign_in.html.erb │ ├── _sign_in_dialog.html.erb │ ├── _user_chooser.html.erb │ └── _welcome.html.erb │ ├── static_pages │ ├── about.html.erb │ └── style_guide.html.erb │ ├── surveys │ ├── _results.html.erb │ ├── index.html.erb │ └── new.html.erb │ ├── users │ └── index.html.erb │ └── volunteers │ └── index.html.erb ├── bin ├── bundle ├── dev ├── find_spec_or_impl ├── rails ├── rake ├── rerun_failures ├── rspec ├── rubocop ├── setup ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ ├── staging.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── constants.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── cors.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── per_form_csrf_tokens.rb │ ├── permissions_policy.rb │ ├── rack_profiler.rb │ ├── request_forgery_protection.rb │ ├── sentry.rb │ ├── session_store.rb │ ├── simple_form.rb │ ├── simple_form_bootstrap.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ ├── en.yml │ └── simple_form.en.yml ├── newrelic.yml ├── puma.rb ├── routes.rb ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20151210035820_init_schema.rb │ ├── 20160110000811_add_location_to_event_sessions.rb │ ├── 20160211061631_add_chapter_leaderships_again.rb │ ├── 20160312224628_create_organization_leaderships.rb │ ├── 20160620044512_add_unaccent_extension_for_postgres.rb │ ├── 20160710221707_create_organization_subscriptions.rb │ ├── 20160807055624_add_twitter_username_to_profiles.rb │ ├── 20161002052810_add_external_event_editor_to_users.rb │ ├── 20161204040052_rename_external_event_data_to_imported_event_data.rb │ ├── 20161206055208_regenerate_canonical_sfruby_meetup_urls.rb │ ├── 20161228162402_create_courses.rb │ ├── 20161228162440_create_levels.rb │ ├── 20170110062210_set_id_sequence_for_new_courses.rb │ ├── 20170202035358_add_food_provided_to_events.rb │ ├── 20170319191233_add_custom_question_to_events.rb │ ├── 20170319192836_add_custom_question_answer_to_rsvps.rb │ ├── 20170707161519_add_appropriate_class_for_skill_to_survey.rb │ ├── 20200102135517_add_primary_key_to_regions_users.rb │ └── 20200329093106_sprinkle_indeces_where_we_do_uniqueness_checks.rb ├── schema.rb ├── seeds.rb └── seeds │ ├── admin_user.rb │ ├── course_populator.rb │ ├── courses.yaml │ ├── seed_chapter.rb │ ├── seed_courses.rb │ ├── seed_event.rb │ └── seed_region.rb ├── devenv.lock ├── devenv.nix ├── devenv.yaml ├── doc ├── admin.md └── deploy.md ├── lib ├── assets │ └── .gitkeep └── tasks │ ├── db.rake │ ├── external_event_import.rake │ ├── jasmine.rake │ ├── populate_courses.rake │ ├── rspec_with_retries.rake │ ├── scheduler.rake │ └── support │ ├── failure_file_parser.rb │ └── rspec_rerunner.rb ├── package.json ├── public ├── 400.html ├── 404.html ├── 406-unsupported-browser.html ├── 422.html ├── 500.html ├── email_images │ └── confirm_account.gif ├── favicon.ico ├── robots.txt └── troll_icon.png ├── script └── rubymine_open_spec_or_impl.sh ├── spec ├── controllers │ ├── admin_pages_controller_spec.rb │ ├── chapters │ │ └── leaders_controller_spec.rb │ ├── chapters_controller_spec.rb │ ├── checkiners_controller_spec.rb │ ├── checkins_controller_spec.rb │ ├── courses_controller_spec.rb │ ├── devise_overrides │ │ └── registrations_controller_spec.rb │ ├── event_sessions_controller_spec.rb │ ├── events │ │ ├── attendees_controller_spec.rb │ │ ├── emails_controller_spec.rb │ │ ├── organizer_tools_controller_spec.rb │ │ ├── students_controller_spec.rb │ │ └── unpublished_events_controller_spec.rb │ ├── events_controller_spec.rb │ ├── external_events_controller_spec.rb │ ├── locations_controller_spec.rb │ ├── organizations_controller_spec.rb │ ├── organizers_controller_spec.rb │ ├── profiles_controller_spec.rb │ ├── regions │ │ └── leaders_controller_spec.rb │ ├── regions_controller_spec.rb │ ├── rsvps_controller_spec.rb │ ├── sections_controller_spec.rb │ ├── static_pages_controller_spec.rb │ ├── surveys_controller_spec.rb │ ├── users │ │ └── events_controller_spec.rb │ ├── users_controller_spec.rb │ └── volunteers_controller_spec.rb ├── factories.rb ├── features │ ├── admin │ │ ├── admin_dashboard_request_spec.rb │ │ ├── course_request_spec.rb │ │ └── publish_event_request_spec.rb │ ├── chapters │ │ └── chapter_request_spec.rb │ ├── events │ │ ├── edit_event_request_spec.rb │ │ ├── event_checkiner_request_spec.rb │ │ ├── event_detail_request_spec.rb │ │ ├── event_listing_request_spec.rb │ │ ├── event_rsvp_request_spec.rb │ │ ├── event_survey_request_spec.rb │ │ ├── new_event_request_spec.rb │ │ ├── organizer │ │ │ ├── announcing_an_event_request_spec.rb │ │ │ ├── attendee_details_request_spec.rb │ │ │ ├── event_email_request_spec.rb │ │ │ ├── event_organizer_dashboard_request_spec.rb │ │ │ ├── event_organizers_request_spec.rb │ │ │ └── organizer_closes_rsvps_request_spec.rb │ │ └── rsvp │ │ │ └── event_rsvp_coc_request_spec.rb │ ├── external_events │ │ └── external_event_request_spec.rb │ ├── homepage_request_spec.rb │ ├── locations_request_spec.rb │ ├── organizations │ │ └── organizations_request_spec.rb │ ├── section_organizer │ │ ├── section_arranger_request_spec.rb │ │ └── section_organizer_request_spec.rb │ ├── style_guide_request_spec.rb │ └── users │ │ ├── edit_account_details_request_spec.rb │ │ ├── omniauth_request_spec.rb │ │ ├── profiles_request_spec.rb │ │ ├── sign_in_menu_request_spec.rb │ │ └── users_request_spec.rb ├── helpers │ └── events_helper_spec.rb ├── javascripts │ ├── helpers │ │ ├── ModelFactories.js │ │ ├── SpecHelper.js │ │ └── whenReady.js │ └── views │ │ ├── section_organizer_view_spec.js │ │ └── section_view_spec.js ├── jobs │ ├── reminder_sender_spec.rb │ └── survey_sender_spec.rb ├── mailers │ ├── devise │ │ └── mailer_previews_spec.rb │ ├── event_mailer_spec.rb │ ├── previews │ │ ├── admin_preview.rb │ │ ├── devise │ │ │ └── mailer_preview.rb │ │ ├── event_preview.rb │ │ ├── rsvp_preview.rb │ │ └── survey_preview.rb │ ├── rsvp_mailer_spec.rb │ └── survey_mailer_spec.rb ├── models │ ├── chapter_leadership_spec.rb │ ├── chapter_spec.rb │ ├── course_spec.rb │ ├── event_email_spec.rb │ ├── event_session_spec.rb │ ├── event_spec.rb │ ├── factory_spec.rb │ ├── level_spec.rb │ ├── location_spec.rb │ ├── meetup_user_spec.rb │ ├── profile_spec.rb │ ├── region_leadership_spec.rb │ ├── region_spec.rb │ ├── rsvp_session_spec.rb │ ├── rsvp_spec.rb │ └── user_spec.rb ├── policies │ ├── course_policy_spec.rb │ ├── event_policy_spec.rb │ ├── location_policy_spec.rb │ └── organization_policy_spec.rb ├── presenters │ ├── event_email_presenter_spec.rb │ └── past_event_emails_presenter_spec.rb ├── rails_helper.rb ├── seeds │ └── seeder_spec.rb ├── services │ ├── account_merger_spec.rb │ ├── database_anonymizer_spec.rb │ ├── event_list_spec.rb │ ├── ics_generator_spec.rb │ ├── meetup_importer_spec.rb │ ├── omniauth_responses.rb │ ├── rsvp_sorter_spec.rb │ ├── section_arranger_spec.rb │ ├── user_searcher_spec.rb │ └── waitlist_manager_spec.rb ├── spec_helper.rb └── support │ ├── authorization_shared_behaviors.rb │ ├── event_form_helper.rb │ ├── form_helper.rb │ ├── jasmine-browser.mjs │ ├── mailers.rb │ ├── row_counting.rb │ ├── sign_in_helper.rb │ └── waiting_helper.rb ├── vendor └── assets │ ├── javascripts │ ├── backbone-super.js │ ├── jquery.event.drag.js │ └── jquery.event.drop.js │ └── stylesheets │ └── .gitkeep └── yarn.lock /.envrc: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | 3 | # if you're wondering what this file is: 4 | # Bridge Troll uses devenv to setup local dependencies (ruby, postgres, etc.) 5 | # with devenv alone, when you want to use your dev environment, you have to type `devenv shell` 6 | # 7 | # https://direnv.net/ makes it better! this file is the entrypoint for direnv 8 | # with devenv + direnv, whenever you enter the bridge troll directory, direnv will automatically drop you into your dev environment 9 | 10 | export DIRENV_WARN_TIMEOUT=20s 11 | 12 | eval "$(devenv direnvrc)" 13 | 14 | # --impure only necessary because of old ruby 15 | use devenv 16 | -------------------------------------------------------------------------------- /.gemrc: -------------------------------------------------------------------------------- 1 | gem: --no-rdoc --no-ri 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: bridgefoundry 4 | open_collective: bridge-foundry-inc 5 | custom: ["https://paypal.me/bridgefoundry"] 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | ## Current Behavior 7 | 8 | 9 | ## Possible Solution 10 | 11 | 12 | ## Steps to Reproduce 13 | 14 | 15 | 1. 16 | 2. 17 | 3. 18 | 4. 19 | 20 | ## Context (Environment) 21 | 22 | 23 | 24 | 25 | 26 | ## Detailed Description 27 | 28 | 29 | ## Possible Implementation 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_user_onboarding.md: -------------------------------------------------------------------------------- 1 | Welcome to the Bridge Troll project! We're excited to have your help. In order to get you started, we've made a helpful checklist of how to get started contributing to the project. 2 | 3 | - [ ] :eyes: Read [Contributing](https://github.com/railsbridge/bridge_troll/blob/master/CONTRIBUTING.md) guidelines 4 | - [ ] :fork_and_knife: Fork the repo and get a local development environment working per the [Quickstart Guide](https://github.com/nerual/bridge_troll#quickstart) 5 | - [ ] Assign yourself a [Beginner Friendly](https://github.com/railsbridge/bridge_troll/labels/Beginner%20Friendly) issue 6 | - [ ] :tada: Create your first Pull Request 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore zeus stuff 8 | zeus.json 9 | custom_plan.rb 10 | 11 | # Ignore bundler config 12 | /.bundle 13 | 14 | # Ignore the default SQLite database. 15 | /db/*.sqlite3* 16 | 17 | # Ignore database dumps 18 | *.dump 19 | 20 | # Ignore all logfiles and tempfiles. 21 | /log/*.log 22 | /tmp 23 | 24 | # Ignore Rubymine created folder 25 | .idea 26 | 27 | # Ignore Redcar directory 28 | .redcar/* 29 | 30 | # Development environment variable files 31 | .env 32 | 33 | # OSX 34 | .DS_Store 35 | 36 | # Vim swap files 37 | **/*.swp 38 | 39 | # Byebug 40 | .byebug_history 41 | 42 | # yarn 43 | node_modules 44 | 45 | coverage 46 | db/*sqlite3-journal 47 | .devenv 48 | 49 | # Devenv 50 | .devenv* 51 | devenv.local.nix 52 | 53 | # direnv 54 | .direnv 55 | 56 | # pre-commit 57 | .pre-commit-config.yaml 58 | public/assets 59 | log/ 60 | 61 | /config/master.key 62 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rspec_parallel: -------------------------------------------------------------------------------- 1 | --format progress 2 | --format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log 3 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.4 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | release: rails db:migrate -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # frozen_string_literal: true 3 | 4 | # Add your own tasks in files placed in lib/tasks ending in .rake, 5 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 6 | 7 | require File.expand_path('config/application', __dir__) 8 | 9 | Rails.application.load_tasks 10 | 11 | if Rails.env.local? 12 | require 'rubocop/rake_task' 13 | RuboCop::RakeTask.new.tap do |task| 14 | task.options = %w[--parallel] 15 | end 16 | 17 | Rake::Task['default'].clear 18 | task default: [:rubocop, :rspec_with_retries, 'jasmine:ci'] 19 | end 20 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link section_organizer.js 2 | //= link markerclustererplus/dist/markerclusterer.min.js 3 | //= link application.js 4 | //= link application.css 5 | //= link logo.svg 6 | //= link troll.svg 7 | //= link troll_plus_logo.svg -------------------------------------------------------------------------------- /app/assets/fonts/Amble-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/app/assets/fonts/Amble-Bold-webfont.woff -------------------------------------------------------------------------------- /app/assets/fonts/Amble-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/app/assets/fonts/Amble-Light-webfont.woff -------------------------------------------------------------------------------- /app/assets/fonts/Amble-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/app/assets/fonts/Amble-Regular-webfont.woff -------------------------------------------------------------------------------- /app/assets/fonts/freehand.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/app/assets/fonts/freehand.woff -------------------------------------------------------------------------------- /app/assets/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/app/assets/images/background.png -------------------------------------------------------------------------------- /app/assets/images/dropdown_arrows.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | //= require jquery2 8 | //= require jquery_ujs 9 | //= require jquery-ui/widgets/datepicker 10 | //= require bootstrap/collapse 11 | //= require bootstrap/transition 12 | //= require bootstrap/modal 13 | //= require bootstrap/tooltip 14 | //= require bootstrap/popover 15 | //= require select2/dist/js/select2.js 16 | //= require handlebars.runtime 17 | //= require underscore 18 | //= require gmaps/google 19 | //= require backbone 20 | //= require backbone-super 21 | //= require bridgetroll 22 | //= require_tree ../templates 23 | //= require_tree ./enums 24 | //= require datatables.net/js/jquery.dataTables 25 | //= require datatables.net-bs/js/dataTables.bootstrap 26 | //= stub section_organizer 27 | //= require imported_event 28 | //= require_tree . 29 | //= require jquery_nested_form 30 | -------------------------------------------------------------------------------- /app/assets/javascripts/bridgetroll.js: -------------------------------------------------------------------------------- 1 | Bridgetroll = { 2 | Models: {}, 3 | Collections: {}, 4 | Views: {}, 5 | Dialogs: {}, 6 | Enums: {}, 7 | Services: {}, 8 | modalContainerSelector: 'body' 9 | }; 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/enums/role.js.erb: -------------------------------------------------------------------------------- 1 | Bridgetroll.Enums.Role = { 2 | STUDENT: <%= Role::STUDENT.id %>, 3 | VOLUNTEER: <%= Role::VOLUNTEER.id %>, 4 | ORGANIZER: <%= Role::ORGANIZER.id %> 5 | }; -------------------------------------------------------------------------------- /app/assets/javascripts/font_analytics.js.erb: -------------------------------------------------------------------------------- 1 | <% if Rails.env.production? %> 2 | window.whenReady(function () { 3 | var fontTracking = _.extend(document.createElement('link'), { 4 | type: 'text/css', 5 | rel: 'stylesheet', 6 | href: '//hello.myfonts.net/count/262127' 7 | }); 8 | document.getElementsByTagName('head')[0].appendChild(fontTracking); 9 | }); 10 | <% end %> -------------------------------------------------------------------------------- /app/assets/javascripts/forms.js.coffee: -------------------------------------------------------------------------------- 1 | jQuery -> 2 | $('input[type=submit][data-custom-action]').click (e) -> 3 | $input = $(e.target) 4 | $input.closest('form').attr('action', $input.data('custom-action')) 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/helpers/capitalize.js: -------------------------------------------------------------------------------- 1 | Handlebars.registerHelper('capitalize', function(str) { 2 | if (str) { 3 | return str.charAt(0).toUpperCase() + str.slice(1); 4 | } else { 5 | return str; 6 | } 7 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/rsvps.js.coffee: -------------------------------------------------------------------------------- 1 | toggleVolunteerPreferenceVisibility = -> 2 | if $(".volunteer_preference:checked").length > 0 3 | $(".volunteer_preference_panel").show(); 4 | else 5 | $(".volunteer_preference_panel").hide(); 6 | 7 | $ -> 8 | toggleVolunteerPreferenceVisibility() 9 | 10 | $(document).on 'click', '.volunteer_preference', toggleVolunteerPreferenceVisibility 11 | -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer.js: -------------------------------------------------------------------------------- 1 | //= require masonry-layout/dist/masonry.pkgd.js 2 | //= require jquery.event.drag 3 | //= require jquery.event.drop 4 | //= require ./section_organizer/views/base_view 5 | //= require ./section_organizer/dialogs/base_dialog 6 | //= require_tree ./section_organizer/dialogs 7 | //= require_tree ./section_organizer/models 8 | //= require_tree ./section_organizer/collections 9 | //= require_tree ./section_organizer/views 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/collections/attendee_collection.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Collections.Attendee = Backbone.Collection.extend({ 2 | model: Bridgetroll.Models.Attendee 3 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/collections/section_collection.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Collections.Section = Backbone.Collection.extend({ 2 | model: Bridgetroll.Models.Section 3 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/collections/session_collection.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Collections.Session = Backbone.Collection.extend({ 2 | model: Bridgetroll.Models.Session 3 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/dialogs/attendee_detail.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Views.AttendeeDetail = (function () { 2 | return Bridgetroll.Dialogs.Base.extend({ 3 | className: function () { 4 | return this._super('className', arguments) + ' bridgetroll-attendee-detail'; 5 | }, 6 | template: 'section_organizer/attendee_detail', 7 | 8 | context: function () { 9 | return _.extend({ 10 | student: this.model.get('role_id') == Bridgetroll.Enums.Role.STUDENT, 11 | volunteer: this.model.get('role_id') == Bridgetroll.Enums.Role.VOLUNTEER, 12 | organizer: this.model.get('role_id') == Bridgetroll.Enums.Role.ORGANIZER 13 | }, this.model.attributes); 14 | } 15 | }); 16 | })(); 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/dialogs/base_dialog.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Dialogs.Base = (function () { 2 | return Bridgetroll.Views.Base.extend({ 3 | className: function () { 4 | if (Bridgetroll.railsEnv === 'test') { 5 | return 'modal'; 6 | } else { 7 | return 'modal fade'; 8 | } 9 | }, 10 | showModally: function () { 11 | this.render(); 12 | $(Bridgetroll.modalContainerSelector).append(this.el); 13 | $(this.el).modal(); 14 | 15 | this.$el.on('modal.bs.shown', function () { 16 | $(this).find('.btn').focus(); 17 | $(this).find('[autofocus]').focus(); 18 | }); 19 | 20 | $(this.el).on('hidden', _.bind(function () { 21 | this.destroy(); 22 | }, this)); 23 | } 24 | }); 25 | })(); -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/dialogs/help_dialog.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Views.HelpDialog = (function () { 2 | return Bridgetroll.Dialogs.Base.extend({ 3 | template: 'section_organizer/help_dialog' 4 | }); 5 | })(); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/models/section.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Models.Section = Backbone.Model.extend({ 2 | urlRoot: function () { 3 | return '/events/' + this.get('event_id') + '/sections'; 4 | }, 5 | 6 | toJSON: function () { 7 | return { 8 | section: this.attributes 9 | }; 10 | }, 11 | 12 | classLevel: function () { 13 | return parseInt(this.get('class_level'), 10); 14 | }, 15 | 16 | isUnassigned: function () { 17 | return this.get('id') === null; 18 | } 19 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/section_organizer/models/session.js: -------------------------------------------------------------------------------- 1 | Bridgetroll.Models.Session = Backbone.Model.extend({ 2 | toJSON: function () { 3 | return this.attributes; 4 | }, 5 | 6 | checkedInVolunteers: function (volunteers) { 7 | return volunteers.filter(_.bind(function (a) { 8 | return a.checkedInTo(this.get('id')); 9 | }, this)).length; 10 | }, 11 | 12 | checkedInStudents: function (students) { 13 | return students.filter(_.bind(function (a) { 14 | return a.checkedInTo(this.get('id')); 15 | }, this)).length; 16 | } 17 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/services/footer.js: -------------------------------------------------------------------------------- 1 | window.whenReady(function () { 2 | if ($(window).height() < $('html').height()) { 3 | $('footer').removeClass('hide'); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/services/matchHeights.js: -------------------------------------------------------------------------------- 1 | window.resizeHeightMatchingItems = function ($element) { 2 | var items = $element.find('.js-match-height-item:not(.hide)'); 3 | items.css('min-height', 0); 4 | var $items = items.map(function (ix, item) { return $(item); }); 5 | 6 | var itemsByTop = {}; 7 | _.each($items, function($item, ix) { 8 | var top = $item.offset().top; 9 | itemsByTop[top] = itemsByTop[top] || []; 10 | itemsByTop[top].push($item); 11 | }); 12 | 13 | _.each(itemsByTop, function (items, top) { 14 | var maxHeight = Math.max.apply(Math, _.map(items, function($item) { 15 | return $item.outerHeight(); 16 | })); 17 | _.each(items, function($item, ix) { 18 | $item.css('min-height', maxHeight); 19 | }); 20 | }); 21 | }; 22 | 23 | window.whenReady(function () { 24 | function setupHeightMatching ($element) { 25 | $(window).resize(_.debounce(function() { 26 | if ($(document).width() > 768) { 27 | resizeHeightMatchingItems($element); 28 | } 29 | }, 100)); 30 | 31 | setTimeout(function () { 32 | resizeHeightMatchingItems($element); 33 | }); 34 | } 35 | 36 | setupHeightMatching($('body')); 37 | }); 38 | -------------------------------------------------------------------------------- /app/assets/javascripts/sign_in.js.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $("#sign_in_dialog").modal('hide') 3 | 4 | $(document).on 'click', '.sign-in-button', (e) -> 5 | $link = $(e.target) 6 | 7 | $modal = $($link.attr('href')) 8 | $modal.on 'shown.bs.modal', () -> 9 | $(this).find('#user_email').focus() 10 | 11 | url = document.createElement('a'); 12 | url.href = $modal.find('form').attr('action'); 13 | 14 | buildReturnParam = (key, href) -> 15 | key + '=' + encodeURIComponent(href) 16 | 17 | return_to_link = $link.data('returnTo') 18 | url.search = if return_to_link then buildReturnParam('return_to', return_to_link) else null 19 | 20 | $modal.find('.external-auth a').each (ix, el) -> 21 | el.search = buildReturnParam('origin', return_to_link || '/') 22 | 23 | sign_up_return_to_link = $link.data('signUpReturnTo') 24 | if sign_up_return_to_link 25 | sign_up_link = $modal.find('.sign_up_link').attr('href') 26 | $modal.find('.sign_up_link').attr('href', sign_up_link + '?' + buildReturnParam('return_to', sign_up_return_to_link)) 27 | 28 | $modal.find('form').attr('action', url.pathname + url.search) 29 | $modal.modal(); 30 | false 31 | -------------------------------------------------------------------------------- /app/assets/javascripts/toggling.js.coffee: -------------------------------------------------------------------------------- 1 | setUpToggles = -> 2 | getSelectedValue = ($input) -> 3 | if $input.attr('type') == 'radio' 4 | $("input[type=radio][name='#{$input.attr('name')}']:checked").val() 5 | else if $input.attr('type') == 'checkbox' 6 | $input.filter(':checked').val() 7 | 8 | inputChanged = -> 9 | $input = $(this) 10 | selector = $input.data('toggle-target') 11 | $togglables = $(selector) 12 | 13 | if $input.data('toggle-show-when') 14 | visibleValue = $input.data('toggle-show-when').toString() 15 | checked = getSelectedValue($input) == visibleValue 16 | 17 | $togglables.toggle(checked) 18 | $togglables.find('input, select').prop('disabled', !checked) 19 | 20 | if $input.data('toggle-enable-when-checked') 21 | $togglables.prop('disabled', !$input.prop('checked')) 22 | 23 | $('[data-toggle-target]').on('change', inputChanged) 24 | $('[data-toggle-target]').each (ix, el) -> 25 | inputChanged.call(el) 26 | 27 | jQuery -> 28 | setUpToggles() 29 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_alerts.scss: -------------------------------------------------------------------------------- 1 | @import 'constants'; 2 | 3 | .alert.alert-danger { 4 | h2 { 5 | margin: 0; 6 | } 7 | } 8 | 9 | .alert-action { 10 | border: 1px dashed $red-color; 11 | border-radius: 5px; 12 | padding: 15px; 13 | margin: 10px 0; 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_checkins.scss: -------------------------------------------------------------------------------- 1 | .popover-content { 2 | ul { 3 | margin: 0 0 10px 10px; 4 | list-style-type: circle; 5 | } 6 | 7 | li { 8 | margin-bottom: 6px; 9 | } 10 | 11 | li:last-child { 12 | margin-bottom: 0; 13 | } 14 | } 15 | 16 | .checkins-action { 17 | &.saving { 18 | .toggle_rsvp_session { 19 | display: none !important; 20 | } 21 | } 22 | 23 | .destroy { 24 | display: none; 25 | } 26 | 27 | &.checked-in { 28 | .create { 29 | display: none; 30 | } 31 | .destroy { 32 | display: block; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/_constants.scss: -------------------------------------------------------------------------------- 1 | $level-blue: #2F96B4; 2 | $level-green: #51A351; 3 | $level-gold: #fcaa2f; 4 | $level-purple: #843178; 5 | $level-orange: #e34d0b; 6 | $level-pink: #ff0490; 7 | 8 | $red-color: #e34d0b; 9 | $orange-color: #ffcc66; 10 | $yellow-color: #fff17f; 11 | $green-color: #a5e587; 12 | $blue-color: #4290cb; 13 | $darkblue-color: #23507a; 14 | $black-color: #333; 15 | $darkgray-color: #777; 16 | 17 | $content-background-color: white; 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_font_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin font-amble-light() { 2 | font-family: 'AmbleLight', sans-serif; 3 | } 4 | 5 | @mixin font-amble-regular() { 6 | font-family: 'AmbleRegular', sans-serif; 7 | } 8 | 9 | @mixin font-amble-bold() { 10 | font-family: 'AmbleBold', sans-serif; 11 | } 12 | 13 | @mixin font-freehand() { 14 | font-family: 'Freehand521BT-RegularC', sans-serif; 15 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/_fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Freehand521BT-RegularC'; 3 | src: font_url('freehand.woff') format('woff'); 4 | } 5 | 6 | @font-face { 7 | font-family: 'AmbleLight'; 8 | src: font_url('Amble-Light-webfont.woff') format('woff'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'AmbleRegular'; 15 | src: font_url('Amble-Regular-webfont.woff') format('woff'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | 20 | @font-face { 21 | font-family: 'AmbleBold'; 22 | src: font_url('Amble-Bold-webfont.woff') format('woff'); 23 | font-weight: normal; 24 | font-style: normal; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_footer.scss: -------------------------------------------------------------------------------- 1 | .form-date { 2 | label { 3 | display: block; 4 | } 5 | 6 | select.form-control { 7 | width: 33%; 8 | display: inline-block; 9 | } 10 | } 11 | 12 | footer { 13 | 14 | .footerlinks { 15 | margin: 0 auto; 16 | padding: 1em 0; 17 | background-color: #f32b00; 18 | width: 100%; 19 | text-align: center; 20 | } 21 | 22 | .footerlinks a { 23 | margin: 0 0.6em; 24 | color: #fff; 25 | } 26 | 27 | .trollholder { 28 | height: 113px; 29 | width: 224px; 30 | margin: 0 auto; 31 | position: relative; 32 | overflow: hidden; 33 | } 34 | 35 | img { 36 | box-sizing: border-box; 37 | position: absolute; 38 | height: 133px; 39 | width: 224px; 40 | bottom: -19px; 41 | } 42 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/_gravatars.scss: -------------------------------------------------------------------------------- 1 | #gravatar { 2 | width: 115px; 3 | padding: 15px 0 15px 0; 4 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin column-count($count) { 2 | -moz-column-count: $count; 3 | -webkit-column-count: $count; 4 | column-count: $count; 5 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/_profile.scss: -------------------------------------------------------------------------------- 1 | .user-profile { 2 | section { 3 | margin-bottom: 15px; 4 | } 5 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/_style_guide.scss.erb: -------------------------------------------------------------------------------- 1 | @import "constants"; 2 | 3 | .style-guide { 4 | ul.swatches { 5 | margin: 10px 0; 6 | 7 | li { 8 | display: inline-block; 9 | margin-right: 20px; 10 | } 11 | 12 | .swatch { 13 | width: 60px; 14 | height: 60px; 15 | 16 | <% %w(red orange yellow green blue black).each do |color| %> 17 | &.<%= color %>-color { 18 | background-color: $<%= color %>-color; 19 | } 20 | <% end %> 21 | } 22 | } 23 | 24 | .background-swatch { 25 | width: 200px; 26 | height: 200px; 27 | display: inline-block; 28 | background-image: image_url('background.png'); 29 | } 30 | 31 | .paragraph-sample { 32 | margin-top: 30px; 33 | } 34 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require jquery-ui/datepicker 3 | *= require bootstrap_with_overrides 4 | *= require datatables.net-bs/css/dataTables.bootstrap 5 | *= require select2/dist/css/select2 6 | *= require font_awesome_with_extensions 7 | *= require _alerts 8 | *= require _fonts 9 | *= require _style_guide 10 | *= require _base 11 | *= require _header 12 | *= require _footer 13 | *= require _tables 14 | *= require _section_organizer 15 | *= require _checkins 16 | *= require events/_index 17 | *= require events/_show 18 | *= require events/_organize 19 | *= require events/_email 20 | *= require events/_form 21 | 22 | *= require _gravatars 23 | 24 | *= require devise 25 | *= require _levels 26 | *= require _form 27 | *= require _profile 28 | 29 | *= require _external_pages 30 | *= require utilities 31 | */ 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap_with_overrides.scss: -------------------------------------------------------------------------------- 1 | $tableBorder: #BFBFBF; 2 | 3 | @import "bootstrap"; 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/events/_email.scss: -------------------------------------------------------------------------------- 1 | .event_email { 2 | .mail-count { 3 | margin: 20px 0; 4 | } 5 | 6 | textarea { 7 | height: 275px; 8 | } 9 | 10 | label { 11 | line-height: 20px; 12 | margin: 0; 13 | } 14 | 15 | input[type=text], .select2-container { 16 | margin-bottom: 20px; 17 | } 18 | 19 | input[type=radio], input[type=checkbox] { 20 | height: 20px; 21 | margin: 5px 5px 5px 0; 22 | } 23 | 24 | .recipients-selection { 25 | label { 26 | display: block; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font_awesome_with_extensions.scss: -------------------------------------------------------------------------------- 1 | @import "font-awesome"; 2 | 3 | .fa-before:before { 4 | @extend .fa; 5 | } 6 | 7 | .btn.fa-before:before { 8 | margin-right: 10px; 9 | } 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/utilities.scss: -------------------------------------------------------------------------------- 1 | .mb20 { 2 | margin-bottom: 20px; 3 | } 4 | 5 | .mt20 { 6 | margin-top: 20px; 7 | } 8 | 9 | .mt10 { 10 | margin-top: 10px; 11 | } 12 | 13 | .p20 { 14 | padding: 20px; 15 | } 16 | 17 | .inline-block { 18 | display: inline-block; 19 | } 20 | 21 | .whitespace-nowrap { 22 | white-space: nowrap; 23 | } 24 | -------------------------------------------------------------------------------- /app/assets/templates/class_levels_popover.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/templates/email_attendees_popover.hbs: -------------------------------------------------------------------------------- 1 |
2 | 7 |
-------------------------------------------------------------------------------- /app/assets/templates/imported_event_popover.hbs: -------------------------------------------------------------------------------- 1 |
2 | This event originally took RSVPs on {{type}} 3 | 4 |
5 | Original Student Event 6 |
7 | 8 |
9 | Original Volunteer Event 10 |
11 |
12 | -------------------------------------------------------------------------------- /app/assets/templates/section_organizer/edit_section.hbs: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | protect_from_forgery 5 | 6 | include Pundit::Authorization 7 | rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized 8 | after_action :verify_authorized, unless: :devise_controller? 9 | 10 | before_action :configure_permitted_parameters, if: :devise_controller? 11 | 12 | before_action do 13 | Rack::MiniProfiler.authorize_request if current_user.try(:admin?) 14 | end 15 | 16 | rescue_from(ActionView::MissingTemplate) do |_e| 17 | raise if request.format == :html 18 | 19 | head(:not_acceptable) 20 | end 21 | 22 | def after_sign_in_path_for(resource) 23 | params[:return_to] || super 24 | end 25 | 26 | protected 27 | 28 | def configure_permitted_parameters 29 | devise_parameter_sanitizer.permit(:sign_up) do |u| 30 | u.permit(policy(User).permitted_attributes + [{ region_ids: [] }]) 31 | end 32 | end 33 | 34 | def user_not_authorized 35 | flash[:error] = 'You are not authorized to perform this action.' 36 | redirect_to(request.referer || root_path) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/event_sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EventSessionsController < ApplicationController 4 | before_action :authenticate_user!, only: %i[index destroy] 5 | before_action :find_event 6 | 7 | def index 8 | authorize @event, :checkin? 9 | @checkin_counts = @event.checkin_counts 10 | end 11 | 12 | def show 13 | skip_authorization 14 | event_session = @event.event_sessions.find(params[:id]) 15 | ics = IcsGenerator.new.event_session_ics(event_session) 16 | 17 | respond_to do |format| 18 | format.ics { render body: ics, layout: false } 19 | format.all { head :not_found } 20 | end 21 | end 22 | 23 | def destroy 24 | authorize @event, :edit? 25 | event_session = @event.event_sessions.find(params[:id]) 26 | if @event.event_sessions.count > 1 && !event_session.any_rsvps? 27 | event_session.destroy 28 | flash[:notice] = "Session #{event_session.name} deleted!" 29 | else 30 | flash[:notice] = "Can't delete that session!" 31 | end 32 | redirect_to edit_event_path(@event) 33 | end 34 | 35 | private 36 | 37 | def find_event 38 | @event = Event.find(params[:event_id]) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/controllers/events/attendee_names_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'csv' 4 | 5 | module Events 6 | class AttendeeNamesController < ApplicationController 7 | before_action :authenticate_user! 8 | before_action :find_event 9 | 10 | def index 11 | authorize @event, :edit? 12 | 13 | rsvps = @event.rsvps.where(role_id: Role.attendee_role_ids_with_organizers) 14 | 15 | respond_to do |format| 16 | format.csv do 17 | send_data(attendee_csv_data(rsvps), type: :csv) 18 | end 19 | end 20 | end 21 | 22 | private 23 | 24 | def find_event 25 | @event = Event.find_by(id: params[:event_id]) 26 | end 27 | 28 | def attendee_csv_data(rsvps) 29 | CSV.generate do |csv| 30 | csv << ['Last Name', 'First Name'] 31 | 32 | rsvps.includes(:user).joins(:bridgetroll_user).order('users.last_name ASC, users.first_name ASC').each do |rsvp| 33 | csv << [rsvp.user.last_name, rsvp.user.first_name] 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/events/students_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'csv' 4 | 5 | module Events 6 | class StudentsController < ApplicationController 7 | before_action :authenticate_user! 8 | before_action :find_event 9 | 10 | def index 11 | authorize @event, :edit? 12 | @event = Event.find(params[:event_id]) 13 | @students = @event.student_rsvps 14 | respond_to do |format| 15 | format.csv { send_data student_csv_data(@students), type: :csv } 16 | format.html 17 | end 18 | end 19 | 20 | private 21 | 22 | def find_event 23 | @event = Event.find_by(id: params[:event_id]) 24 | end 25 | 26 | def student_csv_data(rsvps) 27 | CSV.generate do |csv| 28 | csv << [ 29 | 'Student Name', 30 | 'Class Level', 31 | 'Operating System', 32 | 'Occupation', 33 | 'Gender' 34 | ] 35 | 36 | rsvps.each do |rsvp| 37 | csv << [ 38 | rsvp.user.full_name, 39 | rsvp.class_level, 40 | rsvp.operating_system.name, 41 | rsvp.job_details, 42 | rsvp.user.gender 43 | ] 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/controllers/events/surveys_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Events 4 | class SurveysController < ApplicationController 5 | before_action :authenticate_user! 6 | before_action :find_event 7 | 8 | def edit 9 | authorize @event, :edit? 10 | end 11 | 12 | private 13 | 14 | def find_event 15 | @event = Event.find(params[:event_id]) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/meetup_users_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MeetupUsersController < ApplicationController 4 | before_action :authenticate_user! 5 | 6 | def show 7 | skip_authorization 8 | @user = MeetupUser.find(params[:id]) 9 | @rsvps = @user.rsvps.includes(:event) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/profiles_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProfilesController < ApplicationController 4 | before_action :authenticate_user! 5 | before_action :load_user_and_profile 6 | 7 | def show 8 | skip_authorization 9 | end 10 | 11 | protected 12 | 13 | def load_user_and_profile 14 | @user = User.includes(:profile).references(:profile).find(params[:user_id]) 15 | @profile = @user.profile 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/sections_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SectionsController < ApplicationController 4 | before_action :authenticate_user! 5 | before_action :find_event 6 | 7 | def create 8 | authorize @event, :edit? 9 | section = @event.sections.create!(name: 'New Section') 10 | render json: section 11 | end 12 | 13 | def update 14 | authorize @event, :edit? 15 | section = @event.sections.find(params[:id]) 16 | if section.update(section_params) 17 | render json: section 18 | else 19 | render json: {}, status: :unprocessable_entity 20 | end 21 | end 22 | 23 | def destroy 24 | authorize @event, :edit? 25 | section = @event.sections.find(params[:id]) 26 | section.destroy 27 | render json: {} 28 | end 29 | 30 | def arrange 31 | authorize @event, :edit? 32 | SectionArranger.new(@event).arrange(params[:checked_in_to]) 33 | redirect_to event_organize_sections_path(@event) 34 | end 35 | 36 | private 37 | 38 | def find_event 39 | @event = Event.find_by(id: params[:event_id]) 40 | end 41 | 42 | def section_params 43 | permitted_attributes(Section) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class StaticPagesController < ApplicationController 4 | before_action :skip_authorization 5 | 6 | def style_guide; end 7 | 8 | def about; end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/users/events_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | class EventsController < ApplicationController 5 | # Provides a simple, public, mini-api to allow third parties to check to 6 | # see if a user has attended any classes 7 | def index 8 | skip_authorization 9 | @user = User.find(params[:user_id]) 10 | @event_count = @user.rsvps.where('checkins_count > 0').count 11 | render json: { event_count: @event_count }, status: :ok 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UsersController < ApplicationController 4 | before_action :authenticate_user! 5 | 6 | def index 7 | skip_authorization 8 | respond_to do |format| 9 | format.html 10 | format.json do 11 | render json: UserList.new(params) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/volunteers_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VolunteersController < ApplicationController 4 | before_action :authenticate_user! 5 | before_action :find_event 6 | 7 | def index 8 | authorize @event, :edit? 9 | @volunteer_rsvps = @event.volunteer_rsvps 10 | end 11 | 12 | private 13 | 14 | def find_event 15 | @event = Event.find(params[:event_id]) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | def gravatar_image_tag(email, size:, **) 5 | hash = Digest::MD5.hexdigest(email) 6 | tag.img(**, src: "https://secure.gravatar.com/avatar/#{hash}.png?s=#{size}") 7 | end 8 | 9 | def resource_name 10 | :user 11 | end 12 | 13 | def resource 14 | User.new 15 | end 16 | 17 | def devise_mapping 18 | Devise.mappings[:user] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/helpers/locations_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LocationsHelper 4 | def pretty_print_address(location) 5 | safe_join(location_array(location).map { |line| tag.div(line) }, '') 6 | end 7 | 8 | def location_map_link(location) 9 | "http://maps.google.com/?q=#{Rack::Utils.escape(location.full_address)}" 10 | end 11 | 12 | private 13 | 14 | def location_array(location) 15 | [ 16 | location.name, 17 | location.address_1, 18 | location.address_2.presence, 19 | "#{location.city}, #{location.state} #{location.zip}" 20 | ].compact 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/helpers/profiles_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ProfilesHelper 4 | def skills 5 | [ 6 | [:childcaring, 'Childcare'], 7 | [:writing, 'Writer'], 8 | [:designing, 'Designer'], 9 | [:mentoring, 'Mentor'], 10 | [:outreach], 11 | [:macosx, 'Mac OS X'], 12 | [:windows], 13 | [:linux] 14 | ] 15 | end 16 | 17 | def skills?(user) 18 | skills.any? { |(skill_symbol, _)| user.profile[skill_symbol] } 19 | end 20 | 21 | def skill_title(skill) 22 | skill[1].presence || skill[0].to_s.titlecase 23 | end 24 | 25 | def skill_symbol(skill) 26 | skill[0] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/helpers/responsive_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ResponsiveHelper 4 | def content_tag_maybe_hidden(tag, content, params = {}) 5 | params[:class] = "#{params[:class]} hide-on-phone" if content.blank? 6 | content_tag tag, params do 7 | content 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/jobs/survey_sender.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SurveySender 4 | FIRST_AUTO_SEND_DATE = Date.parse('2015-11-15') 5 | 6 | def self.send_all_surveys 7 | Event.where('ends_at > ?', FIRST_AUTO_SEND_DATE) 8 | .where(ends_at: ...1.day.ago) 9 | .where(survey_sent_at: nil).each do |event| 10 | send_surveys(event) 11 | end 12 | end 13 | 14 | def self.send_surveys(event) 15 | return if event.survey_sent_at.present? 16 | 17 | attendee_rsvps = event.attendee_rsvps.where('checkins_count > 0') 18 | attendee_rsvps.each do |rsvp| 19 | SurveyMailer.notification(rsvp).deliver_now 20 | end 21 | event.update_attribute(:survey_sent_at, DateTime.now) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/mailers/admin_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminMailer < ApplicationMailer 4 | def test_group_mail(options) 5 | set_recipients([options[:to]]) 6 | 7 | mail( 8 | from: 'events@bridgefoundry.org', 9 | subject: '[Bridge Troll] Group Email Test' 10 | ) do |format| 11 | format.html { render html: mail_content('group') } 12 | end 13 | end 14 | 15 | def test_individual_mail(options) 16 | mail( 17 | to: options[:to], 18 | from: 'events@bridgefoundry.org', 19 | subject: '[Bridge Troll] Individual Email Test' 20 | ) do |format| 21 | format.html { render html: mail_content('individual') } 22 | end 23 | end 24 | 25 | private 26 | 27 | def mail_content(type) 28 | <<~MAIL_CONTENT 29 | Hey there! 30 | 31 | This is a test message from events.bridgefoundry.org! 32 | 33 | If you received it, it means that #{type} emails can probably be sent successfully from #{Rails.configuration.action_mailer.default_url_options[:host]} 34 | MAIL_CONTENT 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | layout 'mailer' 5 | append_view_path Rails.root.join('app/views/mailers') 6 | 7 | private 8 | 9 | def set_recipients(recipients, cc = nil) 10 | # Sendgrid API allows a single SMTP request to send multiple 11 | # email messages. Change this to something else if we move 12 | # away from Sendgrid. 13 | # http://sendgrid.com/docs/API_Reference/SMTP_API/ 14 | 15 | recipients += cc unless cc.nil? 16 | 17 | headers['X-SMTPAPI'] = { 18 | to: recipients 19 | }.to_json 20 | headers[:to] = 'events@bridgefoundry.org' # supposedly required even with X-SMTPAPI 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/mailers/survey_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SurveyMailer < ApplicationMailer 4 | helper(EventsHelper) 5 | def notification(rsvp) 6 | @rsvp = rsvp 7 | mail( 8 | to: rsvp.user.email, 9 | subject: "How was #{rsvp.event.title}?" 10 | ) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/authentication.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Authentication < ApplicationRecord 4 | validates :provider, uniqueness: { scope: [:uid] } 5 | 6 | belongs_to :user, inverse_of: :authentications, counter_cache: true 7 | 8 | after_commit :authentication_created, on: :create 9 | 10 | def authentication_created 11 | OmniauthProviders.finish_auth_for(self) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/chapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Chapter < ApplicationRecord 4 | belongs_to :organization, inverse_of: :chapters 5 | has_many :events, dependent: :nullify 6 | has_many :external_events, dependent: :nullify 7 | has_many :chapter_leaderships, dependent: :destroy 8 | has_many :leaders, through: :chapter_leaderships, source: :user 9 | 10 | validates :name, presence: true 11 | validates :name, uniqueness: true 12 | 13 | def leader?(user) 14 | return false unless user 15 | 16 | user.admin? || user.chapter_leaderships.map(&:chapter_id).include?(id) 17 | end 18 | 19 | def destroyable? 20 | (events_count + external_events_count).zero? 21 | end 22 | 23 | def code_of_conduct_url 24 | organization.code_of_conduct_url || Event::DEFAULT_CODE_OF_CONDUCT_URL 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/models/chapter_leadership.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ChapterLeadership < ApplicationRecord 4 | belongs_to :chapter 5 | belongs_to :user, inverse_of: :chapter_leaderships 6 | 7 | validates :user, uniqueness: { scope: :chapter } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/course.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Course < ApplicationRecord 4 | has_many :levels, dependent: :destroy 5 | has_many :events, dependent: :nullify 6 | validates :name, presence: true 7 | validates :title, presence: true 8 | validates :description, presence: true 9 | 10 | accepts_nested_attributes_for :levels, allow_destroy: true 11 | validates :levels, length: { maximum: 5 } 12 | validate :unique_level_positions 13 | validate :unique_level_colors 14 | 15 | def unique_level_positions 16 | unique_level_values(:num, 'position must be unique') 17 | end 18 | 19 | def unique_level_colors 20 | unique_level_values(:color, 'color must be unique') 21 | end 22 | 23 | private 24 | 25 | def unique_level_values(field, message) 26 | return if levels.map(&field).length == levels.map(&field).uniq.length 27 | 28 | errors.add(:level, message) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/models/dietary_restriction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DietaryRestriction < ApplicationRecord 4 | belongs_to :rsvp 5 | 6 | DIETS = %w[vegetarian vegan gluten-free dairy-free].freeze 7 | 8 | validates :restriction, uniqueness: { scope: :rsvp_id } 9 | 10 | validates :restriction, inclusion: { in: DIETS } 11 | end 12 | -------------------------------------------------------------------------------- /app/models/event_details/default_details.html: -------------------------------------------------------------------------------- 1 |

Workshop Description

2 | 3 |

Sponsors

4 | 5 |

Transportation and Parking

6 | 7 |

Food and Drinks

8 | 9 |

Childcare

10 | 11 |

Afterparty

12 | -------------------------------------------------------------------------------- /app/models/event_details/default_student_details.html: -------------------------------------------------------------------------------- 1 | All students need to bring their own laptop and powercord. 2 | 3 | Since bandwidth is usually at a premium at the Installfest, please download RailsInstaller (for PCs and most Mac installations) or XCode (if you're going that route). 4 | 5 | You can find more information on what to download by getting started with the Installfest instructions: http://docs.railsbridge.org/installfest 6 | -------------------------------------------------------------------------------- /app/models/event_details/default_survey_greeting.html: -------------------------------------------------------------------------------- 1 | Thanks so much for attending! 2 | 3 | We love feedback, so let us know how things went for you and anything you think we could make better in the future! -------------------------------------------------------------------------------- /app/models/event_details/default_volunteer_details.html: -------------------------------------------------------------------------------- 1 | Be sure to review the curriculum before the workshop. We have several curricula available at http://docs.railsbridge.org. 2 | -------------------------------------------------------------------------------- /app/models/event_email.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EventEmail < ApplicationRecord 4 | attr_accessor :attendee_group, :include_waitlisted, :only_checked_in, :cc_organizers 5 | 6 | belongs_to :event 7 | belongs_to :sender, class_name: 'User' 8 | 9 | has_many :event_email_recipients, dependent: :destroy 10 | has_many :recipient_rsvps, through: :event_email_recipients 11 | has_many :recipients, through: :recipient_rsvps, source: :user, source_type: 'User' 12 | 13 | validates :subject, :body, presence: true 14 | end 15 | -------------------------------------------------------------------------------- /app/models/event_email_recipient.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EventEmailRecipient < ApplicationRecord 4 | belongs_to :event_email 5 | belongs_to :recipient_rsvp, class_name: 'Rsvp', inverse_of: :event_email_recipients 6 | end 7 | -------------------------------------------------------------------------------- /app/models/level.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Level < ApplicationRecord 4 | COLORS = %w[blue green gold purple orange pink].freeze 5 | 6 | default_scope { order(:num) } 7 | 8 | belongs_to :course, optional: true 9 | validates :num, presence: true, 10 | inclusion: { 11 | in: (1..5), 12 | message: 'Must be between 1 and 5' 13 | } 14 | validates :color, presence: true, 15 | inclusion: { 16 | in: COLORS, 17 | message: "Must be one of: #{COLORS.sort.join(', ')}" 18 | } 19 | validates :title, presence: true 20 | validates :level_description, presence: true 21 | serialize :level_description, type: Array, coder: YAML 22 | alias_attribute :description, :level_description 23 | 24 | def level_description_bullets 25 | level_description.map { |line| "* #{line}" }.join("\n") 26 | end 27 | 28 | def level_description_bullets=(value) 29 | self.level_description = value.split("\n").map { |line| line.gsub(/^\s*\*\s*/, '').strip } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/models/meetup_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MeetupUser < ApplicationRecord 4 | has_many :rsvps, -> { where user_type: 'MeetupUser' }, foreign_key: 'user_id', inverse_of: :meetup_user 5 | has_many :events, through: :rsvps 6 | 7 | def email 8 | '(email not available)' 9 | end 10 | 11 | def profile 12 | @profile ||= Profile.new.tap do |p| 13 | Profile.attribute_names.each { |attrib| p[attrib] = false } 14 | p.readonly! 15 | end 16 | end 17 | 18 | def profile_path 19 | "/meetup_users/#{id}" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/organization.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'csv' 4 | 5 | class Organization < ApplicationRecord 6 | has_many :chapters, dependent: :destroy, inverse_of: :organization 7 | has_many :organization_leaderships, dependent: :destroy 8 | has_many :leaders, through: :organization_leaderships, source: :user 9 | has_many :organization_subscriptions, dependent: :destroy 10 | has_many :users, through: :organization_subscriptions 11 | 12 | def leader?(user) 13 | return false unless user 14 | 15 | user.admin? || user.organization_leaderships.map(&:organization_id).include?(id) 16 | end 17 | 18 | def subscription_csv 19 | CSV.generate do |csv| 20 | csv << %w[email first_name last_name] 21 | users.each do |user| 22 | csv << [user.email, user.first_name, user.last_name] 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/models/organization_leadership.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class OrganizationLeadership < ApplicationRecord 4 | belongs_to :organization 5 | belongs_to :user, inverse_of: :organization_leaderships 6 | 7 | validates :user, uniqueness: { scope: :organization } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/organization_subscription.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class OrganizationSubscription < ApplicationRecord 4 | belongs_to :user 5 | belongs_to :subscribed_organization, class_name: 'Organization', foreign_key: :organization_id, 6 | inverse_of: :organization_subscriptions 7 | end 8 | -------------------------------------------------------------------------------- /app/models/profile.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Profile < ApplicationRecord 4 | belongs_to :user, inverse_of: :profile 5 | 6 | after_initialize :remove_at_from_twitter_username 7 | 8 | validates :github_username, format: { with: /\A([a-z0-9-]+-)*[a-z0-9]+\Z/i, allow_blank: true } 9 | validates :twitter_username, format: { with: /\A@?\w{1,15}\Z/i, allow_blank: true } 10 | 11 | validates :user_id, uniqueness: true 12 | 13 | def remove_at_from_twitter_username 14 | self.twitter_username = twitter_username.try(:gsub, /\A@*/, '') 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/region.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Region < ApplicationRecord 4 | has_many :locations, dependent: :nullify 5 | has_many :events, through: :locations 6 | has_many :external_events, dependent: :nullify 7 | has_many :regions_users, dependent: :destroy 8 | has_many :users, through: :regions_users 9 | has_many :region_leaderships, dependent: :destroy 10 | has_many :leaders, through: :region_leaderships, source: :user 11 | 12 | validates :name, presence: true 13 | validates :name, uniqueness: true 14 | 15 | def leader?(user) 16 | return false unless user 17 | 18 | user.admin? || user.region_leaderships.map(&:region_id).include?(id) 19 | end 20 | 21 | def destroyable? 22 | (locations_count + external_events_count).zero? 23 | end 24 | 25 | def as_json(_options = {}) 26 | { 27 | name: name, 28 | users_subscribed_to_email_count: users.where(allow_event_email: true).count 29 | } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/models/region_leadership.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RegionLeadership < ApplicationRecord 4 | belongs_to :region, inverse_of: :region_leaderships 5 | belongs_to :user, inverse_of: :region_leaderships 6 | 7 | validates :user, uniqueness: { scope: :region } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/regions_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RegionsUser < ApplicationRecord 4 | belongs_to :user, inverse_of: :regions_users 5 | belongs_to :region, inverse_of: :regions_users 6 | end 7 | -------------------------------------------------------------------------------- /app/models/role.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Role < ActiveHash::Base 4 | include ActiveHash::Enum 5 | self.data = [ 6 | { id: 1, name: 'STUDENT', title: 'Student' }, 7 | { id: 2, name: 'VOLUNTEER', title: 'Volunteer' }, 8 | { id: 3, name: 'ORGANIZER', title: 'Organizer' } 9 | ] 10 | enum_accessor :name 11 | 12 | def self.attendee_role_ids 13 | [Role::VOLUNTEER.id, Role::STUDENT.id] 14 | end 15 | 16 | def self.attendee_role_ids_with_organizers 17 | [Role::VOLUNTEER.id, Role::STUDENT.id, Role::ORGANIZER.id] 18 | end 19 | 20 | def self.empty_attendance 21 | Role.all.each_with_object({}) do |role, hsh| 22 | hsh[role.id] = 0 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/rsvp_session.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RsvpSession < ApplicationRecord 4 | belongs_to :rsvp 5 | belongs_to :event_session 6 | 7 | validates :rsvp_id, uniqueness: { scope: :event_session_id } 8 | 9 | after_destroy :update_counter_cache 10 | after_save :update_counter_cache 11 | 12 | def user_full_name 13 | rsvp.user.full_name 14 | end 15 | 16 | def update_counter_cache 17 | rsvp.checkins_count = rsvp.rsvp_sessions.where('rsvp_sessions.checked_in' => true).count 18 | rsvp.save 19 | end 20 | 21 | def as_json(options = {}) 22 | super.merge(role_id: rsvp.role_id) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/section.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Section < ApplicationRecord 4 | belongs_to :event 5 | has_many :rsvps, dependent: :nullify 6 | 7 | def student_rsvps 8 | rsvps.where(role_id: Role::STUDENT.id) 9 | end 10 | 11 | def volunteer_rsvps 12 | rsvps.where(role_id: Role::VOLUNTEER.id) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/survey.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Survey < ApplicationRecord 4 | belongs_to :rsvp 5 | 6 | validates :rsvp_id, uniqueness: { message: 'Only one survey response allowed per user.' } 7 | validates :recommendation_likelihood, allow_blank: true, 8 | numericality: { only_integer: true, greater_than: 0, less_than: 11 } 9 | end 10 | -------------------------------------------------------------------------------- /app/models/volunteer_assignment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VolunteerAssignment < ActiveHash::Base 4 | include ActiveHash::Enum 5 | self.data = [ 6 | { id: 1, name: 'UNASSIGNED', title: 'Unassigned' }, 7 | { id: 2, name: 'TEACHER', title: 'Teacher' }, 8 | { id: 3, name: 'TA', title: 'TA' } 9 | ] 10 | enum_accessor :name 11 | end 12 | -------------------------------------------------------------------------------- /app/models/volunteer_preference.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VolunteerPreference < ActiveHash::Base 4 | include ActiveHash::Enum 5 | self.data = [ 6 | { id: 1, name: 'NEITHER', title: 'Non-teaching volunteer' }, 7 | { id: 2, name: 'TEACHER', title: 'Teacher' }, 8 | { id: 3, name: 'TA', title: 'TA' }, 9 | { id: 4, name: 'BOTH', title: 'No Preference' } 10 | ] 11 | enum_accessor :name 12 | end 13 | -------------------------------------------------------------------------------- /app/policies/application_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationPolicy 4 | class Scope 5 | attr_reader :user, :scope 6 | 7 | def initialize(user, scope) 8 | @user = user 9 | @scope = scope 10 | end 11 | end 12 | 13 | attr_reader :user, :record 14 | 15 | def initialize(user, record) 16 | @user = user 17 | @record = record 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/policies/chapter_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ChapterPolicy < ApplicationPolicy 4 | def new? 5 | user && (user.admin? || user.organization_leaderships.present?) 6 | end 7 | 8 | def update? 9 | record.leader?(user) || record.organization.leader?(user) 10 | end 11 | 12 | def create? 13 | update? 14 | end 15 | 16 | def destroy? 17 | user&.admin? 18 | end 19 | 20 | def modify_leadership? 21 | update? 22 | end 23 | 24 | def permitted_attributes 25 | %i[ 26 | name 27 | organization_id 28 | ] 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/policies/course_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CoursePolicy < ApplicationPolicy 4 | def index? 5 | user.admin? 6 | end 7 | 8 | def destroy? 9 | record.events.empty? && user.admin? 10 | end 11 | 12 | def edit? 13 | update? 14 | end 15 | 16 | def new? 17 | create? 18 | end 19 | 20 | def update? 21 | user.admin? 22 | end 23 | 24 | def create? 25 | user.admin? 26 | end 27 | 28 | def permitted_attributes 29 | return [] unless user&.admin? 30 | 31 | [ 32 | :name, 33 | :title, 34 | :description, 35 | { 36 | levels_attributes: LevelPolicy.new(user, Level).permitted_attributes + [:id] 37 | } 38 | ] 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/policies/event_session_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EventSessionPolicy < ApplicationPolicy 4 | def permitted_attributes 5 | %i[ 6 | starts_at 7 | ends_at 8 | name 9 | required_for_students 10 | volunteers_only 11 | location_overridden 12 | location_id 13 | ] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/policies/external_event_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ExternalEventPolicy < ApplicationPolicy 4 | def edit? 5 | user.admin? || user.external_event_editor? 6 | end 7 | 8 | def permitted_attributes 9 | %i[ 10 | city 11 | ends_at 12 | location 13 | name 14 | organizers 15 | starts_at 16 | url 17 | region_id 18 | chapter_id 19 | ] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/policies/level_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class LevelPolicy < ApplicationPolicy 4 | def permitted_attributes 5 | return [] unless user&.admin? 6 | 7 | %i[ 8 | num 9 | color 10 | title 11 | level_description_bullets 12 | _destroy 13 | ] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/policies/location_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class LocationPolicy < ApplicationPolicy 4 | def destroy? 5 | record.events.empty? 6 | end 7 | 8 | def archive? 9 | return false unless record.persisted? 10 | return false if record.archived? 11 | 12 | update? || edit_additional_details? 13 | end 14 | 15 | def update? 16 | record.events_count.zero? || user.admin? || 17 | record.notable_events.map(&:organizers).flatten.map(&:id).include?(user.id) 18 | end 19 | 20 | def edit_additional_details? 21 | record.region&.leader?(user) 22 | end 23 | 24 | def permitted_attributes 25 | attributes = %i[ 26 | name 27 | address_1 28 | address_2 29 | city 30 | state 31 | zip 32 | region_id 33 | ] 34 | if edit_additional_details? 35 | attributes += %i[ 36 | contact_info 37 | notes 38 | ] 39 | end 40 | attributes 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/policies/organization_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class OrganizationPolicy < ApplicationPolicy 4 | class Scope < Scope 5 | def resolve 6 | if user.admin? 7 | scope.all 8 | else 9 | scope.where(id: user.organization_leaderships.map(&:organization_id)) 10 | end 11 | end 12 | end 13 | 14 | def create? 15 | user&.admin? 16 | end 17 | 18 | def manage_organization? 19 | user && (user.admin? || record.leader?(user)) 20 | end 21 | 22 | def permitted_attributes 23 | [ 24 | :name 25 | ] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/policies/profile_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ProfilePolicy < ApplicationPolicy 4 | def permitted_attributes 5 | %i[ 6 | childcaring 7 | designing 8 | outreach 9 | linux 10 | macosx 11 | mentoring 12 | other 13 | windows 14 | writing 15 | bio 16 | github_username 17 | twitter_username 18 | ] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/policies/region_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RegionPolicy < ApplicationPolicy 4 | def update? 5 | record.leader?(user) 6 | end 7 | 8 | def modify_leadership? 9 | record.leader?(user) 10 | end 11 | 12 | def permitted_attributes 13 | [ 14 | :name 15 | ] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/policies/rsvp_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RsvpPolicy < ApplicationPolicy 4 | def survey? 5 | record.user == user 6 | end 7 | 8 | def permitted_attributes 9 | [ 10 | :subject_experience, 11 | :teaching, 12 | :taing, 13 | :teaching_experience, 14 | :teaching_experience, 15 | :childcare_info, 16 | :operating_system_id, 17 | :job_details, 18 | :class_level, 19 | :custom_question_answer, 20 | :dietary_info, 21 | :needs_childcare, 22 | :plus_one_host, 23 | { 24 | event_session_ids: [], 25 | dietary_restriction_diets: [] 26 | } 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/policies/section_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SectionPolicy < ApplicationPolicy 4 | def permitted_attributes 5 | %i[ 6 | name 7 | class_level 8 | ] 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/policies/survey_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SurveyPolicy < ApplicationPolicy 4 | def permitted_attributes 5 | %i[ 6 | good_things 7 | bad_things 8 | appropriate_for_skill 9 | other_comments 10 | rsvp_id 11 | recommendation_likelihood 12 | ] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/policies/user_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserPolicy < ApplicationPolicy 4 | def permitted_attributes 5 | [ 6 | :first_name, 7 | :last_name, 8 | :email, 9 | :password, 10 | :password_confirmation, 11 | :remember_me, 12 | :time_zone, 13 | :gender, 14 | :allow_event_email, 15 | :current_password, 16 | { 17 | region_ids: [], 18 | subscribed_organization_ids: [], 19 | profile_attributes: ProfilePolicy.new(user, Profile).permitted_attributes 20 | } 21 | ] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/presenters/event_email_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EventEmailPresenter < SimpleDelegator 4 | def rsvps 5 | @rsvps ||= event.rsvps.where(role_id: Role.attendee_role_ids).includes(:user) 6 | end 7 | 8 | def volunteers_rsvps 9 | rsvps.where(role_id: Role::VOLUNTEER.id) 10 | end 11 | 12 | def students_accepted_rsvps 13 | rsvps.where(role_id: Role::STUDENT.id).confirmed 14 | end 15 | 16 | def students_waitlisted_rsvps 17 | rsvps.where(role_id: Role::STUDENT.id).where.not('waitlist_position IS NULL') 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/presenters/past_event_emails_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PastEventEmailsPresenter 4 | attr_reader :event 5 | 6 | def initialize(event) 7 | @event = event 8 | end 9 | 10 | def emails 11 | event.event_emails.order(:created_at) 12 | end 13 | 14 | def recipient_counts 15 | counts = {} 16 | 17 | emails.each do |email| 18 | counts[email.id] = { 19 | total: email.recipient_rsvps.length, 20 | volunteers: 0, 21 | students: 0 22 | } 23 | 24 | email.recipient_rsvps.each do |rsvp| 25 | counts[email.id][:volunteers] += 1 if rsvp.role_volunteer? 26 | counts[email.id][:students] += 1 if rsvp.role_student? 27 | end 28 | end 29 | 30 | counts 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/services/account_merger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AccountMerger 4 | def initialize(user_to_keep, user_to_merge) 5 | @user_to_keep = user_to_keep 6 | @user_to_merge = user_to_merge 7 | end 8 | 9 | def merge! 10 | raise "Can't merge the same user onto itself!" if @user_to_merge.id == @user_to_keep.id 11 | 12 | to_destroy = Rsvp.where(event_id: ( 13 | @user_to_merge.rsvps.pluck('event_id') & @user_to_keep.rsvps.pluck('event_id') 14 | ), user_id: @user_to_merge.id, user_type: 'User') 15 | 16 | Rails.logger.info(<<~USER_PROMPT) 17 | 18 | Ready to merge #{user_desc(@user_to_merge)}'s data onto #{user_desc(@user_to_keep)}! 19 | 20 | #{to_destroy.count} RSVP(s) of #{user_desc(@user_to_merge)} will be DESTROYED 21 | #{@user_to_merge.rsvps.count - to_destroy.count} RSVP(s) will be adopted by #{user_desc(@user_to_keep)} 22 | 23 | Is this cool? (y/n) 24 | USER_PROMPT 25 | 26 | return unless $stdin.gets.chomp.casecmp('y') 27 | 28 | to_destroy.destroy_all 29 | 30 | @user_to_merge.rsvps.update_all(user_id: @user_to_keep.id) 31 | @user_to_merge.destroy 32 | 33 | true 34 | end 35 | 36 | private 37 | 38 | def user_desc(user) 39 | "#{user.full_name} (#{user.id})" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/services/external_event_importer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'net/http' 4 | require 'csv' 5 | 6 | class ExternalEventImporter 7 | def import(url) 8 | return unless url 9 | 10 | uri = URI.parse(url) 11 | http = Net::HTTP.new(uri.host, uri.port) 12 | resp = http.get(uri.request_uri) 13 | 14 | return unless resp.code == '200' 15 | 16 | CSV.parse(resp.body.force_encoding('UTF-8'), headers: :first_line) do |line| 17 | date_template = '%m/%d/%Y' 18 | start_date = Date.strptime(line['START DATE'], date_template) 19 | ExternalEvent.create!( 20 | name: line['TITLE'] || 'Ruby on Rails Outreach Workshop for Women', 21 | starts_at: start_date, 22 | ends_at: line['END DATE'] ? Date.strptime(line['END DATE'], date_template) : start_date, 23 | url: line['URL'], 24 | city: line['CITY'], 25 | location: line['LOCATION'], 26 | organizers: line['ORGANIZERS'] 27 | ) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/services/ics_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Generates downloadable calendar event files to support 'Export to iCal' feature 4 | class IcsGenerator 5 | require 'icalendar' 6 | require 'date' 7 | 8 | include Icalendar 9 | 10 | def event_session_ics(event_session) 11 | cal = Calendar.new 12 | cal.event do |e| 13 | e.dtstart = event_session.starts_at.to_datetime 14 | e.dtend = event_session.ends_at.to_datetime 15 | e.summary = "#{event_session.event.title}: #{event_session.name}" 16 | e.location = event_session.true_location.try(:name) 17 | end 18 | cal.to_ical 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/services/meetup_importer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MeetupImporter 4 | def associate_user(bridgetroll_user, meetup_id) 5 | authentication_meetup_id = bridgetroll_user.reload.meetup_id 6 | if authentication_meetup_id.to_s != meetup_id.to_s 7 | raise "User has no registered authentication with meetup UID #{meetup_id}" 8 | end 9 | 10 | meetup_user = MeetupUser.where(meetup_id: meetup_id).first 11 | return if meetup_user.blank? 12 | 13 | Rsvp.where(user_type: 'MeetupUser', user_id: meetup_user.id).find_each do |rsvp| 14 | rsvp.user = bridgetroll_user 15 | rsvp.save! 16 | end 17 | end 18 | 19 | def disassociate_user(bridgetroll_user) 20 | raise 'User is not associated with a meetup account!' if bridgetroll_user.meetup_id.blank? 21 | 22 | meetup_user = MeetupUser.where(meetup_id: bridgetroll_user.meetup_id).first 23 | Rsvp.where(user_type: 'User', user_id: bridgetroll_user.id).find_each do |rsvp| 24 | rsvp.user = meetup_user 25 | rsvp.save! 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/rsvp_sorter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RsvpSorter 4 | def initialize(event, rsvps) 5 | @event = event 6 | @rsvps = rsvps 7 | end 8 | 9 | def ordered 10 | if @event.historical? 11 | modern_rsvps + historical_rsvps 12 | else 13 | modern_rsvps 14 | end 15 | end 16 | 17 | private 18 | 19 | def modern_rsvps_order_clause 20 | name_clause = 'lower(users.first_name) ASC, lower(users.last_name) ASC' 21 | Arel.sql( 22 | if @event.past? 23 | "checkins_count > 0 DESC, #{name_clause}" 24 | else 25 | name_clause 26 | end 27 | ) 28 | end 29 | 30 | def modern_rsvps 31 | @rsvps 32 | .where(user_type: 'User') 33 | .includes(:bridgetroll_user) 34 | .order(modern_rsvps_order_clause) 35 | .references(:bridgetroll_users) 36 | end 37 | 38 | def historical_rsvps 39 | @rsvps 40 | .where(user_type: 'MeetupUser') 41 | .includes(:meetup_user) 42 | .order(Arel.sql('lower(meetup_users.full_name) ASC')) 43 | .references(:meetup_users) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /app/services/user_searcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserSearcher 4 | def initialize(relation, query) 5 | @relation = relation 6 | @query = query 7 | end 8 | 9 | def as_json(_options = {}) 10 | args = 'lower(first_name)', "' '", 'lower(last_name)' 11 | search_field = if Rails.application.using_postgres? 12 | "CONCAT(#{args * ', '})" 13 | else 14 | args * ' || ' 15 | end 16 | 17 | @relation 18 | .select(:id, :first_name, :last_name) 19 | .where("#{search_field} like ?", "%#{@query.downcase}%") 20 | .map { |u| { id: u.id, text: u.full_name } } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/validators/array_of_ids_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ArrayOfIdsValidator < ActiveModel::EachValidator 4 | def validate_each(record, attribute, value) 5 | return if value.nil? 6 | 7 | unless value.respond_to?(:each) 8 | record.errors.add(attribute, 'must be an array') 9 | return 10 | end 11 | 12 | if value.empty? 13 | record.errors.add(attribute, 'must have at least one value') 14 | return 15 | end 16 | 17 | value.each do |id| 18 | record.errors.add(attribute, 'must be in the list') unless options[:in].include?(id) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/chapters/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= simple_form_for(@chapter) do |f| %> 4 | <%= render 'shared/model_error_messages', model: @chapter %> 5 | 6 |
7 | <%= f.label :organization_id %> 8 | <%= f.collection_select(:organization_id, policy_scope(Organization).order(:name), :id, :name, {prompt: true}, {class: 'form-control'}) %> 9 |
10 | 11 | 12 | <%= f.input :name, placeholder: "ex: BridgeName CityName", hint: "We suggest naming your chapter after the organization and city or region." %> 13 | 14 |
15 | <%= f.submit class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %> 16 |
17 | <% end %> 18 |
19 |
20 | -------------------------------------------------------------------------------- /app/views/chapters/_table.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% if current_user.try(:admin?) %> 7 | 8 | <% end %> 9 | 10 | 11 | 12 | 13 | <% chapters.each do |chapter| %> 14 | 15 | 16 | 17 | 25 | 26 | <% end %> 27 | 28 |
NameEvents
<%= link_to chapter.name, chapter %><%= chapter.events_count + chapter.external_events_count %> 18 | <% if policy(chapter).update? %> 19 | <%= link_to 'Edit', edit_chapter_path(chapter), class: 'btn fa-before fa-edit' %> 20 | <% end %> 21 | <% if current_user.try(:admin?) && chapter.destroyable? %> 22 | <%= link_to 'Destroy', chapter, data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger' %> 23 | <% end %> 24 |
29 | -------------------------------------------------------------------------------- /app/views/chapters/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Editing chapter") %> 2 | 3 | <%= render 'form' %> 4 | 5 | <%= render 'shared/actions', links: [ 6 | ['Show', @chapter] 7 | ] %> 8 | 9 | -------------------------------------------------------------------------------- /app/views/chapters/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'Listing chapters') %> 2 | 3 | <%= render 'table', chapters: @chapters %> 4 | 5 | <%= render 'shared/actions', links: crud_object_nav_links(:chapter, policy(Chapter).new? ? ['New Chapter', new_chapter_path] : nil) %> 6 | -------------------------------------------------------------------------------- /app/views/chapters/leaders/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Chapter Leaders for #{@chapter.name}") %> 2 | 3 |

Potential Leaders

4 | <%= render 'shared/user_chooser', 5 | action: chapter_leaders_path(@chapter), 6 | select_id: 'chapter_leader', 7 | foreign_key: 'id', 8 | url: potential_chapter_leaders_path(@chapter), 9 | choices: [] 10 | %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <% @chapter.leaders.each do |leader| %> 23 | 24 | 25 | 26 | 27 | 33 | 34 | <% end %> 35 | 36 |
NameEmail
<%= user_gravatar(leader) %><%= leader.full_name %><%= leader.email %> 28 | <% if leader.id != current_user.id %> 29 | <%= button_to 'Remove', chapter_leader_path(@chapter, leader), method: :delete, 30 | form: {style: "margin-bottom : 0;"}, class: "btn btn-mini" %> 31 | <% end %> 32 |
37 | -------------------------------------------------------------------------------- /app/views/chapters/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'New chapter') %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/chapters/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @chapter.name) %> 2 | 3 | <% if @chapter.leaders.present? %> 4 |

Chapter Leaders

5 | 10 | <% end %> 11 | 12 | <% if @chapter.leader?(current_user) %> 13 | <%= link_to 'Edit Chapter Leaders', chapter_leaders_path(@chapter), class: "btn mb20" %> 14 | 15 |

Organizers

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <% @organizer_rsvps.each do |rsvp| %> 25 | 26 | 29 | 30 | 31 | <% end %> 32 | 33 |
Name# Events
27 | <%= link_to rsvp.user.full_name, user_profile_path(rsvp.user) %> 28 | <%= rsvp.events_count %>
34 | <% end %> 35 | 36 |

Events

37 | <%= render 'shared/events_table', events: @chapter_events %> 38 | 39 | <%= render 'shared/actions', links: [ 40 | current_user.try(:admin?) ? ['Edit', edit_chapter_path(@chapter)] : nil, 41 | ] %> 42 | -------------------------------------------------------------------------------- /app/views/courses/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Course

2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/courses/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'Listing courses') %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @courses.each do |course| %> 15 | 16 | 17 | 18 | 19 | 23 | 30 | 31 | <% end %> 32 | 33 |
NameTitleDescription
<%= link_to course.name, course %><%= course.title %><%= simple_format_with_html(course.description) %> 20 | <%= link_to 'Edit', edit_course_path(course), 21 | class: 'btn fa-before fa-edit' %> 22 | 24 | <%= link_to 'Remove', course, method: :delete, 25 | class: 'btn btn-danger', 26 | data: { 27 | confirm: "Are you sure you want to remove " + course.title + " and its' " + pluralize(@course_level_counts[course.id], 'level') + "?" 28 | } %> 29 |
34 | 35 | <%= link_to 'New Course', new_course_path, class: 'btn' %> 36 | -------------------------------------------------------------------------------- /app/views/courses/new.html.erb: -------------------------------------------------------------------------------- 1 |

New Course

2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 |
4 |
5 | <%= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 6 | <%= render "shared/devise_error_messages" if resource.errors.any? %> 7 | 8 | <%= f.input :email %> 9 | 10 |
<%= f.submit "Resend confirmation instructions", class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %>
11 | <% end %> 12 |
13 |
14 | 15 | <%= devise_links(:sign_up, :forgot_password) %> 16 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to Bridge Troll, <%= @resource.first_name %>!

3 | 4 |

Click the link below to begin a magical journey to an amazing, welcoming technology workshop.

5 | 6 |

7 | <%= link_to confirmation_url(@resource, confirmation_token: @token) do %> 8 | Confirm your account! 9 | <% end %> 10 |

11 | 12 |

13 | Should the image above not load for any reason, click here: 14 | <%= link_to "Sign up to Bridge Troll", confirmation_url(@resource, confirmation_token: @token) %> 15 |

16 |
17 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password, and you can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= render "shared/devise_error_messages" if resource.errors.any? %> 5 | 6 | <%= f.hidden_field :reset_password_token %> 7 | 8 |
9 | <%= f.label :password, "New password" %> 10 | <%= f.password_field :password %> 11 |
12 | 13 |
14 | <%= f.label :password_confirmation, "Confirm new password" %> 15 | <%= f.password_field :password_confirmation %> 16 |
17 | 18 |
<%= f.submit "Change my password", class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %>
19 | <% end %> 20 | 21 | <%= devise_links(:sign_up, :confirmation_instructions) %> 22 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 |
4 |
5 | <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 6 | <%= render "shared/devise_error_messages" if resource.errors.any? %> 7 | 8 | <%= f.input :email %> 9 | 10 |
<%= f.submit "Send me reset password instructions", class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %>
11 | <% end %> 12 |
13 |
14 | 15 | <%= devise_links(:sign_up, :confirmation_instructions) %> 16 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Edit #{resource_name.to_s.humanize}") %> 2 | 3 |
4 | <%= render 'shared/profile_gravatars'%> 5 | 6 | <%= link_to 'Change Gravatar', 'https://en.gravatar.com/emails', target: "_blank", class: 'btn fa-before fa-upload' %> 7 | 8 | <%= link_to 'View Public Profile', user_profile_path(resource), class: 'btn fa-before fa-user' %> 9 |
10 | 11 | <%= render 'form', button_text: 'Update', is_registered: true %> 12 | 13 |

<%= link_to "Delete my account", registration_path(resource_name), data: { confirm: "This will delete your Bridgetroll account forever. Are you sure?" }, method: :delete, class: 'btn btn-danger' %>

14 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Sign in") %> 2 | 3 |
4 | <%= render partial: "shared/sign_in" %> 5 | <%= devise_links(:forgot_password, :confirmation_instructions) %> 6 |
-------------------------------------------------------------------------------- /app/views/event_sessions/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_path(@event)) %> 3 | 4 | <%= render partial: 'shared/checkin_event_sessions' %> 5 | -------------------------------------------------------------------------------- /app/views/events/_attendee_list.html.erb: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /app/views/events/_location_modal.html.erb: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /app/views/events/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_path(@event)) %> 3 | 4 |
5 |
6 |

Editing event

7 | <%= render 'form' %> 8 |
9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /app/views/events/emails/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_organizer_tools_path(@event)) %> 3 | 4 | <%= render partial: 'shared/organizer_breadcrumb', locals: {current_page_title: 'Review Sent Email'} %> 5 | 6 |

From: <%= link_to @email.sender.full_name, user_profile_path(@email.sender) %>

7 | 8 |
9 | 10 |

Subject: <%= @email.subject %>

11 | 12 |
13 | 14 |

Body:

15 | 16 |
<%= @email.body %>
17 | -------------------------------------------------------------------------------- /app/views/events/feed.atom.builder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | atom_feed do |feed| 4 | feed.title('Bridge Troll Events') 5 | feed.link root_url 6 | feed.updated @events.last.try(:updated_at) 7 | feed.id root_url 8 | 9 | @events.each do |event| 10 | feed.entry(event) do |entry| 11 | entry.title(event.title) 12 | entry.summary(event.details, type: 'html') 13 | entry.link event_path(event) 14 | entry.id event_path(event) 15 | entry.updated event.updated_at 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/events/feed.rss.builder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | xml.instruct! :xml, version: '1.0' 4 | xml.rss version: '2.0' do 5 | xml.channel do 6 | xml.title 'Bridge Troll Events' 7 | xml.link root_url 8 | xml.language 'en' 9 | 10 | @events.each do |event| 11 | xml.item do 12 | xml.title event.title 13 | xml.pubDate event.created_at.to_fs(:rfc822) 14 | xml.link event_path(event) 15 | xml.guid event_path(event) 16 | xml.description event.details 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/events/levels.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_path(@event)) %> 3 | 4 |

Class Levels for <%= (@event.course || Course.find_by_name("RAILS")).title %>

5 | 6 | <%= render partial: 'rsvps/class_levels', locals: { 7 | levels: (@event.course || Course.find_by_name("RAILS")).levels, 8 | show_no_preference: false 9 | } %> 10 | -------------------------------------------------------------------------------- /app/views/events/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'Organize an Event') %> 2 | 3 |
4 |
5 | <%= render 'guidelines' %> 6 | <%= render 'form' %> 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /app/views/events/organizer_tools/_checkin_counts.html.erb: -------------------------------------------------------------------------------- 1 |

Current Checkin Counts

2 | <% @event.event_sessions.each do |session| %> 3 |
4 | <%= session.name %>: 5 | 21 |
22 | <% end %> 23 | -------------------------------------------------------------------------------- /app/views/events/organizer_tools/_childcare_requests.html.erb: -------------------------------------------------------------------------------- 1 |

Childcare Requests

2 | <% if !@event.has_childcare %> 3 | Childcare sign ups are currently disabled. 4 | <% end %> 5 | <% if @childcare_requests.length > 0 %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @childcare_requests.each do |request| %> 16 | 17 | 20 | 23 | 24 | 25 | <% end %> 26 | 27 |
Requester NameEmailChildren Info
18 | <%= request.user.full_name %> 19 | 21 | <%= request.user.email %> 22 | <%= request.childcare_info %>
28 | <% else %> 29 | No one has requested childcare for this event yet. 30 | <% end %> 31 | -------------------------------------------------------------------------------- /app/views/events/organizer_tools/_operating_system_breakdown.erb: -------------------------------------------------------------------------------- 1 |

Operating System Breakdown

2 | <% total = @os_counts.values.reduce(&:+) %> 3 | <% if total.present? %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% OperatingSystem.all.each do |os| %> 13 | <% count = @os_counts.fetch(os.id, 0) %> 14 | <% next unless count > 0 %> 15 | 16 | 19 | 22 | 23 | <% end %> 24 | 25 |
OSCount
17 | <%= os.title %> 18 | 20 | <%= count %> (<%= '%.2f' % ((count.to_f / total) * 100) %>%) 21 |
26 | <% else %> 27 | No student RSVPs yet... 28 | <% end %> -------------------------------------------------------------------------------- /app/views/events/past_events.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Past events

3 |

Hundreds of workshops in dozens of cities around the world have been 4 | organized and taught by volunteers, based on our open source curricula.

5 | <%= render 'shared/past_events_table' %> 6 |
7 | -------------------------------------------------------------------------------- /app/views/events/students/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_organizer_tools_path(@event)) %> 3 | 4 |

Students

5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @students.each do |rsvp| %> 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | <% end %> 26 |
NameClass levelOperating SystemWaitlistedJob Details
17 | <%= user_gravatar(rsvp.user) %> 18 | <%= rsvp.user.full_name %> 19 | <%= rsvp.class_level %><%= rsvp.operating_system.title %><%= if rsvp.waitlist_position then "yes" else "no" end %><%= rsvp.job_details %>
27 | 28 | 29 | -------------------------------------------------------------------------------- /app/views/events/surveys/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | 3 | <%= simple_form_for @event do |f| %> 4 | <%= render 'shared/model_error_messages', model: @event %> 5 | 6 | <%= render partial: 'shared/organizer_breadcrumb', locals: {current_page_title: 'Edit Email Body'} %> 7 | 8 |
9 |

10 | Use the field below to edit the email body that will be sent to RSVP'd participants. A link to the survey will be included at the bottom of your message. 11 |

12 | <%= f.input :survey_greeting, label: "Email Body:", input_html: {rows: 4} %> 13 |
14 | 15 |
16 | <%= f.submit "Update", class: "btn" %> 17 |
18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/events/unpublished_events/_mark_as_spam_button.html.erb: -------------------------------------------------------------------------------- 1 | <%= button_to 'Flag as Spam', unpublished_event_flag_path(event), class: 'btn btn-danger', method: :post, 2 | form_class: 'button_to inline-block', 3 | data: { 4 | confirm: "Are you sure? This will remove this event from the approval page, and flag #{event.organizers.first.full_name} as a spammer so that all subsequent events they create will immediately be flagged as spam." 5 | } %> -------------------------------------------------------------------------------- /app/views/events/unpublished_events/_publish_event_button.html.erb: -------------------------------------------------------------------------------- 1 | <% if event.location %> 2 | <%= button_to 'Publish', unpublished_event_publish_path(event), class: 'btn', id: "publish-event-#{event.id}", method: :post, form_class: 'button_to inline-block' %> 3 | 22 | <% else %> 23 | 24 | <% end %> -------------------------------------------------------------------------------- /app/views/events/unpublished_events/_unpublished_event.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to event.title, event %> 4 |
5 |
6 | <% event.event_sessions.each_with_index do |session, index| %> 7 | <%= formatted_session_fancy_date(session) %><% if index < event.event_sessions.length - 1 %>
<% end %> 8 | <% end %> 9 |
10 |
11 | <% if event.location %> 12 | <%= event.location.name %>, 13 | <%= [event.location.city, event.location.state].select(&:present?).join(', ') %> 14 | <% end %> 15 |
16 |
17 | <%= render 'publish_event_button', event: event %> 18 | <% if current_user.admin? || current_user.publisher? %> 19 | <%= render 'mark_as_spam_button', event: event %> 20 | <% end %> 21 |
22 |
23 | -------------------------------------------------------------------------------- /app/views/external_events/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= simple_form_for(@external_event) do |f| %> 4 | <%= render 'shared/model_error_messages', model: @external_event %> 5 | 6 | <%= content_tag :div, class: field_classes(@external_event, :region) do %> 7 | <%= render 'shared/region_select', f: f %> 8 | <% end %> 9 | 10 | <%= content_tag :div, class: field_classes(@external_event, :chapter) do %> 11 |
12 | <%= render 'shared/chapter_select', f: f %> 13 |
14 | <% end %> 15 | 16 | <%= f.input :name %> 17 | 18 | <%= f.input :url, label: 'URL' %> 19 | 20 |
21 | <%= f.input :starts_at, start_year: 2009 %> 22 |
23 | 24 |
25 | <%= f.input :ends_at, start_year: 2009 %> 26 |
27 | 28 | <%= f.input :city %> 29 | 30 | <%= f.input :location %> 31 | 32 | <%= f.input :organizers %> 33 | 34 |
35 | <%= f.submit class: :btn, data: {disable_with: 'Please wait...'} %> 36 |
37 | <% end %> 38 |
39 |
40 | -------------------------------------------------------------------------------- /app/views/external_events/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Editing External Event") %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/external_events/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "New External Event") %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= yield %> 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/views/locations/create.js.erb: -------------------------------------------------------------------------------- 1 | $('#new-location-modal').modal('hide'); 2 | $('#new-location-modal form')[0].reset(); 3 | 4 | id = '<%= @location.id %>' 5 | 6 | $location_select = $('#event_location_id') 7 | $location_select.append("") 8 | $location_select.val(id).trigger('change') 9 | -------------------------------------------------------------------------------- /app/views/locations/create_failed.js.erb: -------------------------------------------------------------------------------- 1 | $('#new-location-modal-body').html('<%= j render "form", modal: true %>') 2 | -------------------------------------------------------------------------------- /app/views/locations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Editing location") %> 2 | 3 | <% if @location.archived? %> 4 |
5 | This location is archived, and can no longer be used for events. 6 |
7 | <% end %> 8 | 9 | <%= render 'form' %> 10 | 11 | <%= render 'shared/actions', links: [ 12 | ['Show', @location] 13 | ] %> 14 | 15 | -------------------------------------------------------------------------------- /app/views/locations/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'Listing locations') %> 2 | 3 | <%= render 'shared/locations_table', locations: @locations %> 4 | 5 | <%= render 'shared/actions', links: crud_object_nav_links(:location, user_signed_in? ? ['New Location', new_location_path] : nil) 6 | %> 7 | -------------------------------------------------------------------------------- /app/views/locations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'New location') %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/_new_event_detail.html.erb: -------------------------------------------------------------------------------- 1 | <% if @event.location && !@event.multiple_locations? %> 2 |

<%= @event.title %> will be held at: 3 | <%= pretty_print_address(@event.location) %> 4 |

5 | <% end %> 6 | 7 | There will be <%= pluralize(@event.event_sessions.count, 'session') %>: 8 | 16 | 17 |

Visit the <%= link_to 'event page', event_url(@event) %> for more information about the event.

18 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/_session_location_detail.html.erb: -------------------------------------------------------------------------------- 1 |

2 |
Location:
3 | <%= pretty_print_address(location) %> 4 |

5 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/event_has_been_approved.html.erb: -------------------------------------------------------------------------------- 1 |

Dear <%= @event.organizers.first.first_name %>,

2 | 3 |

4 | Congratulations! Your event <%= @event.title %> has been approved. Our organization is entirely grass-roots and volunteer-run, so your contribution is really important. Thank you so much for organizing! 5 |

6 |

7 | Next Steps:
8 | What's next? You've just posted your event to Bridgetroll, so you're probably about three weeks out. You can Check out the workshop timeline to see what other organizers generally do around this time. You can find more details about using Bridgetroll here. 9 |

10 |

11 | You're also always welcome to email the organizers' mailing list with questions, requests, panic, or jokes. We're all here to help. 12 | 13 | 14 |

15 |

Sincerely,

16 |

The Troll

17 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/event_pending_approval.html.erb: -------------------------------------------------------------------------------- 1 |

Dear <%= @event.organizers.first.first_name %>,

2 | 3 |

4 | We're so excited that you're organizing <%= @event.title %>! For spam-prevention and quality assurance, your event needs to be approved by a Bridge Troll admin before it is announced to your region and visible on the site. 5 |

6 |

7 | Admins are looking particularly to make sure that your event specifically states what underrepresented populations it is reaching out to, and that the workshop is free. (And they might tell you about any typos they find, or things that don't make sense, if they find either.) 8 |

9 |

10 | If you have any questions, or if it's been a day or two and you haven't heard anyone, please email bridgetroll@railsbridge.org. 11 |

12 |

Sincerely,

13 |

The Troll

14 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/from_organizer.html.erb: -------------------------------------------------------------------------------- 1 | <%= @sender.full_name %> has something to say about <%= @event.upcoming? ? 'an upcoming event' : 'a past event' %>: 2 | 3 |

<%= sanitize(@body).gsub(/(\r)?\n/, "
").html_safe %>

4 | 5 |

<%= link_to 'Check out this event on Bridge Troll by clicking this link!', event_url(@event) %>

6 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/new_event.html.erb: -------------------------------------------------------------------------------- 1 |

Howdy!

2 | 3 |

A new event, "<%= @event.title %>", has been posted to Bridge Troll!

4 | 5 | <%= render 'new_event_detail' %> 6 | 7 |

You received this email because Bridge Troll thinks you're interested in events for the "<%= @region.name %>" region. If you want to change that preference, opt-out of emails entirely, or delete your account in a fit of rage, <%= link_to 'visit your preference page on Bridge Troll', edit_user_registration_url %>.

8 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/new_organizer_alert.erb: -------------------------------------------------------------------------------- 1 |

Howdy <%= @user.full_name %>!

2 | 3 |

You've been added as an organizer to "<%= @event.title %>".

4 | 5 |

If you think you shouldn't be an organizer, <%= link_to 'you can remove yourself on the Organizers page', event_organizers_url(@event) %>

6 | 7 |

The Troll

8 | -------------------------------------------------------------------------------- /app/views/mailers/event_mailer/unpublished_event.html.erb: -------------------------------------------------------------------------------- 1 |

Howdy, Bridge Troll administrator!

2 | 3 |

A new event, "<%= link_to @event.title, event_url(@event) %>", has been posted to Bridge Troll by <%= link_to @event.organizers.first.full_name, user_profile_url(@event.organizers.first) %>!

4 | 5 | <%= render 'new_event_detail' %> 6 | 7 | <% if @event.published? %> 8 |

<%= link_to 'This event is already published, so you should be able to find it on the list of upcoming events!', events_url %>

9 | <% else %> 10 |

<%= link_to 'Go see the list of unpublished events to see if this is worth approving!', unpublished_events_url %>

11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/mailers/rsvp_mailer/childcare_notification.html.erb: -------------------------------------------------------------------------------- 1 | Hey Organizers, 2 | 3 |

There is a new childcare request for <%= @rsvp.event.title %>!

4 | 5 |

The request was made by <%= @rsvp.user.full_name %>. The information about the request can be seen directly below:

6 |

<%= @rsvp.childcare_info %>

7 | 8 |

Visit the <%= link_to 'event page', event_url(@rsvp.event) %> for more information about the event.

9 | 10 |

- The Troll

-------------------------------------------------------------------------------- /app/views/mailers/rsvp_mailer/email.html.erb: -------------------------------------------------------------------------------- 1 | Hey <%= @rsvp.user.full_name %>, 2 | 3 |

Thanks for signing up as a <%= @rsvp.role.name.downcase %> for <%= @rsvp.event.title %>!

4 | 5 | <% if @rsvp.waitlisted? %> 6 |

You're at position <%= @rsvp.waitlist_position %> on the waitlist. You'll get an email if a slot opens up for you.

7 | <% end %> 8 | 9 | <%= render partial: 'event_details' %> 10 | 11 |

- The Troll

12 | -------------------------------------------------------------------------------- /app/views/mailers/rsvp_mailer/off_waitlist.html.erb: -------------------------------------------------------------------------------- 1 | Hey <%= @rsvp.user.full_name %>, 2 | 3 |

A spot opened up! You are now confirmed as a <%= @rsvp.role.name.downcase %> for <%= @rsvp.event.title %>!

4 | 5 | <%= render partial: 'event_details' %> 6 | 7 |

- The Troll

8 | -------------------------------------------------------------------------------- /app/views/mailers/survey_mailer/notification.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_format_with_html(@rsvp.event.survey_greeting) %> 2 | 3 |

<%= link_to "Click right here to take the survey!", new_event_rsvp_survey_url(@rsvp.event, @rsvp)%>

4 | 5 |

- The Troll

6 | -------------------------------------------------------------------------------- /app/views/meetup_users/show.html.erb: -------------------------------------------------------------------------------- 1 |

Events attended by <%= @user.full_name %>

2 | 3 | <%= render 'shared/rsvps', rsvps: @rsvps %> 4 | 5 | <%= render 'shared/actions', links: [ 6 | ['User Index', users_path] 7 | ] %> 8 | -------------------------------------------------------------------------------- /app/views/organizations/_chapter_infowindow.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to location.chapter.name, location.chapter %> (<%= pluralize(location.chapter.events_count, 'event') %>) 3 |
4 | 5 |
Last Event:
6 |
<%= link_to location.event.title, location.event %> @
7 |
<%= link_to location.name, location %>
8 | -------------------------------------------------------------------------------- /app/views/organizations/index.html.erb: -------------------------------------------------------------------------------- 1 |

Organizations

2 | 3 | <% @organizations.sort_by(&:name).each do |org| %> 4 | <% if org.chapters.count > 0 %> 5 |

<%= link_to org.name, organization_path(org) %>

6 |
<% org.chapters.count %><%= pluralize(org.chapters.count, 'chapter') %>
7 | <% if org.leaders.length > 0 %> 8 |
Leaders:
9 | 14 | <% end %> 15 | <% end %> 16 | <% end %> 17 | 18 |

Chapters

19 | <%= render "shared/map", 20 | locations: @chapter_locations, 21 | map_class: 'gmaps4rails_bigger_map', 22 | infowindow_template: 'chapter_infowindow' %> 23 | 24 | <% if user_signed_in? %> 25 | <% links = [] %> 26 | <% if policy(Organization).create? %> 27 | <% links << ['Add Organization', new_organization_path] %> 28 | <% end %> 29 | <%= render 'shared/actions', links: links, additional_class: 'no-margin' %> 30 | <% end %> 31 | -------------------------------------------------------------------------------- /app/views/organizations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'New organization') %> 2 | 3 |
4 |
5 | <%= simple_form_for(@organization) do |f| %> 6 | <%= render 'shared/model_error_messages', model: @organization %> 7 | 8 | <%= f.input :name, placeholder: "ex: SandwichBridge" %> 9 | 10 |
11 | <%= f.submit class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %> 12 |
13 | <% end %> 14 |
15 |
16 | -------------------------------------------------------------------------------- /app/views/organizations/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @organization.name %>

2 | 3 | <% if @organization.leaders.present? %> 4 |

Organization Leaders

5 | These are people who can approve Bridge Troll postings for any <%= @organization.name %> event. 6 | 11 | <% end %> 12 | 13 | <% if policy(@organization).manage_organization? %> 14 |
15 |

Organization Leader Tools

16 | This section is only visible to admins & organization leaders. 17 |

18 | <%= link_to "Download subscribed user email addresses", organization_download_subscriptions_path(@organization, format: :csv), class: "btn mt10" %> 19 |

20 |
21 | <% end %> 22 | 23 |

Chapters

24 | 25 | <%= render 'chapters/table', chapters: @organization.chapters %> 26 | -------------------------------------------------------------------------------- /app/views/regions/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= simple_form_for(@region) do |f| %> 4 | <%= render 'shared/model_error_messages', model: @region %> 5 | 6 | <%= f.input :name %> 7 | 8 |
9 | <%= f.submit class: 'btn btn-submit', data: {disable_with: 'Please wait...'} %> 10 |
11 | <% end %> 12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/regions/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Editing region") %> 2 | 3 | <%= render 'form' %> 4 | 5 | <%= render 'shared/actions', links: [ 6 | ['Show', @region] 7 | ] %> 8 | 9 | -------------------------------------------------------------------------------- /app/views/regions/leaders/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Region Leaders for #{@region.name}") %> 2 | 3 |

Potential Leaders

4 | <%= render 'shared/user_chooser', 5 | action: region_leaders_path(@region), 6 | select_id: 'region_leader', 7 | foreign_key: 'id', 8 | url: potential_region_leaders_path(@region), 9 | choices: [] 10 | %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <% @region.leaders.each do |leader| %> 23 | 24 | 25 | 26 | 27 | 33 | 34 | <% end %> 35 | 36 |
NameEmail
<%= user_gravatar(leader) %><%= leader.full_name %><%= leader.email %> 28 | <% if leader.id != current_user.id %> 29 | <%= button_to 'Remove', region_leaders_path(@region, leader), method: :delete, 30 | form: {style: "margin-bottom : 0;"}, class: "btn btn-mini" %> 31 | <% end %> 32 |
37 | -------------------------------------------------------------------------------- /app/views/regions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, 'New region') %> 2 | 3 | <%= render 'form' %> 4 | -------------------------------------------------------------------------------- /app/views/rsvps/_class_levels.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% levels.each do |level| %> 3 |
4 | 16 |
17 | <% end %> 18 | <% if show_no_preference %> 19 |
20 | 27 |
28 | <% end %> 29 |
30 | -------------------------------------------------------------------------------- /app/views/rsvps/_dietary_restrictions.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.collection_check_boxes(:dietary_restriction_diets, 2 | DietaryRestriction::DIETS, 3 | :to_s, 4 | :capitalize, 5 | { 6 | item_wrapper_class: 'checkbox_row', 7 | item_wrapper_tag: 'div', 8 | include_hidden: false 9 | } 10 | ) %> 11 | <%= f.input :dietary_info, label: "Other" %> -------------------------------------------------------------------------------- /app/views/rsvps/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | 3 |
4 |

Edit RSVP

5 | 6 | <%= render 'form' %> 7 |
8 | -------------------------------------------------------------------------------- /app/views/rsvps/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | 3 |
4 |

RSVP

5 | 6 | <% if @show_new_region_warning %> 7 |
8 |

9 | Reminder: this event is in 10 | 11 | <%= @event.location ? @event.location_city_and_state : @event.region.name %>. 12 | Since you've never attended an event in that location, please double check you can make it. 13 |

14 |
15 | <% end %> 16 | 17 |

AWESOME - you're almost signed up!

18 | 19 | <%= render 'rsvps/form' %> 20 |
21 | -------------------------------------------------------------------------------- /app/views/rsvps/quick_destroy_confirm.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Are you sure you want to cancel? 3 |

4 | 5 | 13 | -------------------------------------------------------------------------------- /app/views/shared/_actions.html.erb: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /app/views/shared/_chapter_select.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :chapter_id, local_assigns[:label] || 'Chapter', class: 'control-label', label: 'org' %> 3 |
4 | <%= f.collection_select(:chapter_id, 5 | Chapter.all, 6 | :id, 7 | :name, 8 | {prompt: true}) %> 9 | 19 | -------------------------------------------------------------------------------- /app/views/shared/_checkin_event_sessions.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Check in students and volunteers

3 | <% @event.event_sessions.each do |session| %> 4 | <%= render partial: 'shared/organizer_action', locals: { 5 | path: event_event_session_checkins_path(@event, session), 6 | icon: 'fa fa-check-square-o', 7 | text: "Check in for #{session.name}", 8 | tip: "Check those students and volunteers in! This will make for easy class sorting." 9 | } %> 10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /app/views/shared/_devise_error_messages.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

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

8 | 13 |
14 | -------------------------------------------------------------------------------- /app/views/shared/_google_analytics.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/shared/_map.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% 3 | markers = Gmaps4rails.build_markers(locations) do |location, marker| 4 | marker.lat location.latitude 5 | marker.lng location.longitude 6 | if defined?(infowindow_template) 7 | marker.infowindow render(infowindow_template, location: location) 8 | end 9 | end 10 | %> 11 | 12 | 13 | <%= javascript_include_tag 'markerclustererplus/dist/markerclusterer.min.js' %> 14 | 15 | -------------------------------------------------------------------------------- /app/views/shared/_model_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if model && model.errors.any? %> 2 |
3 |

<%= pluralize(model.errors.count, "error") %> prohibited this <%= model.to_model.class.name.underscore.humanize.downcase %> from being saved:

4 | 5 | 10 |
11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/shared/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /app/views/shared/_organizer_action.erb: -------------------------------------------------------------------------------- 1 |
2 | <% options = { 3 | class: "organizer-action", 4 | data: { } 5 | } %> 6 | <% options[:data][:confirm] = local_assigns[:confirm] if local_assigns[:confirm] %> 7 | <% options[:class] += ' organizer-action-inline' if local_assigns[:style] == :inline %> 8 | <% options[:method] = verb if local_assigns[:verb] %> 9 | <%= link_to path, options do %> 10 |
11 | 12 | <%= text %> 13 |
14 | <% end %> 15 | <% if defined?(tip) %> 16 |
17 | <%= tip %> 18 |
19 | <% end %> 20 |
21 | -------------------------------------------------------------------------------- /app/views/shared/_organizer_breadcrumb.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to 'Events', events_path %> 3 | » 4 | <%= link_to @event.title, event_path(@event) %> 5 | <% if defined?(current_page_title) %> 6 | » 7 | <%= current_page_title %> 8 | <% end %> 9 |
10 | <%= link_to 'Edit Event', edit_event_path(@event), class: 'btn fa-before fa-edit' %> 11 | <% unless @organizer_dashboard %> 12 | <%= link_to 'Organizer Console', event_organizer_tools_path(@event), class: 'btn fa-before fa-bullhorn' %> 13 | <% end %> 14 |
15 |
-------------------------------------------------------------------------------- /app/views/shared/_plus_one_disclaimer.html.erb: -------------------------------------------------------------------------------- 1 |

All workshops on Bridge Troll focus on providing opportunities for under-represented populations in tech. If you are not a member of an underrepresented group, you can only attend as a student if you're the guest of someone who is a member of the workshop's target population. (For example, men can come as the guest of a woman, a straight person can attend as the guest of a gay person, etc.)

2 |

If you are not a member of this workshop's target demographic, list the name of the person who is bringing you here:

3 | -------------------------------------------------------------------------------- /app/views/shared/_profile_gravatars.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= gravatar_image_tag(@user.email, alt: '', size: 115) %> 3 |
4 | -------------------------------------------------------------------------------- /app/views/shared/_region_select.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :region_id, class: 'control-label' %> 3 | <%= f.collection_select(:region_id, Region.order(:name), :id, :name, {prompt: true}, {class: 'form-control'}) %> 4 |

5 | If your region isn't in this list, head over to the <%= link_to 'regions page', regions_path %> and add it before you continue. 6 |

7 |
8 | -------------------------------------------------------------------------------- /app/views/shared/_rsvp_actions_unauthenticated.html.erb: -------------------------------------------------------------------------------- 1 | <% if event.open? %> 2 | <% if event.allow_student_rsvp? %> 3 | 7 | <%= student_attend_button_text(event) %> 8 | 9 | <% end %> 10 | 14 | <%= volunteer_attend_button_text(event) %> 15 | 16 | <% else %> 17 |
RSVPs are closed!
18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/shared/_rsvps.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% rsvps.each do |rsvp| %> 13 | <% event = rsvp.event %> 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | <% end %> 25 | 26 |
TitleDateLocationRoleSession Checkins
16 | <%= link_to event.title, event %> 17 | <%= imported_event_popover_trigger(event) %> 18 | <%= formatted_date(event.event_sessions.first) %><%= event.location.name rescue nil %><%= rsvp.role.title %><%= event.historical? ? 'N/A' : rsvp.checkins_count %>
-------------------------------------------------------------------------------- /app/views/shared/_sign_in.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Sign in with BridgeTroll

4 | <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name), wrapper_mappings: {boolean: :vertical_boolean}) do |f| %> 5 | <%= f.input :email %> 6 | 7 | <%= f.input :password %> 8 | 9 | <%= f.input :remember_me, as: :boolean %> 10 | 11 |
12 | <%= f.submit "Sign in", class: 'btn btn-submit' %> 13 | <%= link_to "Sign up", new_registration_path(resource_name), class: 'sign_up_link' %> 14 |
15 | <% end %> 16 |
17 |
18 |

Sign in with something else

19 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /app/views/shared/_sign_in_dialog.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/shared/_welcome.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | <%= image_tag asset_path('troll_plus_logo.svg'), alt: '' %> 6 |
7 |
8 |

Do you want to learn to code with fun people? <%= link_to "Sign up", new_user_registration_path %> and RSVP for an event.

9 |

Or, learn more <%= link_to "about Bridge Troll", 10 | about_path %>.

11 |

This is open source software, so feel free to <%= link_to "peruse our code.", "http://www.github.com/railsbridge/bridge_troll" %>

12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /app/views/surveys/_results.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% if student_results %> 7 | 8 | <% end %> 9 | 10 | 11 | 12 | 13 | <% surveys.each do |survey| %> 14 | 15 | 16 | 17 | <% if student_results %> 18 | 19 | <% end %> 20 | 21 | 22 | 23 | <% end %> 24 |
What was great?What could have been better?Was the class or section you were in appropriate to your skill level?Comments#
<%= survey.good_things%><%= survey.bad_things %><%= survey.appropriate_for_skill %><%= survey.other_comments %><%= survey.recommendation_likelihood %>
25 | -------------------------------------------------------------------------------- /app/views/surveys/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_organizer_tools_path(@event)) %> 3 | 4 | <%= render partial: 'shared/organizer_breadcrumb', locals: {current_page_title: 'Survey Results'} %> 5 | 6 |

Student Survey Results

7 | <%= render 'results', surveys: @event.student_surveys, student_results: true %> 8 | 9 |

Volunteer Survey Results

10 | <%= render 'results', surveys: @event.volunteer_surveys, student_results: false %> 11 | -------------------------------------------------------------------------------- /app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, "Users") %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
NameMeetup Profile# Attended as Student# Attended as Volunteer# Organized
14 | 15 | -------------------------------------------------------------------------------- /app/views/volunteers/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for(:header_text, @event.title) %> 2 | <%= content_for(:header_link, event_organizer_tools_path(@event)) %> 3 | 4 | <%= render partial: 'shared/organizer_breadcrumb', locals: {current_page_title: 'Attending volunteer details'} %> 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | <% @volunteer_rsvps.each do |rsvp| %> 14 | 15 | 19 | 20 | 21 | 22 | 23 | <% end %> 24 |
NameSubject ExperienceTeaching ExperienceVolunteer's Preference
16 | <%= user_gravatar(rsvp.user) %> 17 | <%= link_to rsvp.user.full_name, user_profile_path(rsvp.user) %> 18 | <%= rsvp.subject_experience %><%= rsvp.teaching_experience %><%= rsvp.formatted_preference %>
25 | 26 | -------------------------------------------------------------------------------- /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/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | exec "./bin/rails", "server", *ARGV 3 | -------------------------------------------------------------------------------- /bin/find_spec_or_impl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | filename = ARGV[0] 4 | in_spec = filename =~ /_spec/ 5 | in_js = filename =~ /js$/ 6 | 7 | if in_js 8 | impl_prefix, spec_prefix, extension = "app/assets/javascripts/", "spec/javascripts/", ".js" 9 | elsif filename =~ %r{^(lib|spec/lib)} 10 | impl_prefix, spec_prefix, extension = "lib/", "spec/lib/", ".rb" 11 | else 12 | impl_prefix, spec_prefix, extension = "app/", "spec/", ".rb" 13 | end 14 | 15 | related_file = if in_spec 16 | filename.gsub(/^#{spec_prefix}/, impl_prefix).gsub(/_spec#{extension}$/, extension.to_s) 17 | else 18 | filename.gsub(/^#{impl_prefix}/, spec_prefix).gsub(/#{extension}$/, "_spec#{extension}") 19 | end 20 | 21 | print related_file 22 | -------------------------------------------------------------------------------- /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/rerun_failures: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts "Paste a list of rspec failures, followed by a blank line:" 4 | 5 | lines = [] 6 | while (line = gets) 7 | break if line == "\n" 8 | lines << line 9 | end 10 | 11 | tests = lines.map { |line| line.match(/rspec (.*?) #/)[1] }.compact 12 | cmd = "rspec #{tests.join(' ')}" 13 | puts cmd 14 | exec(cmd) 15 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require 'bundler/setup' 8 | load Gem.bin_path('rspec-core', 'rspec') 9 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | Rails.application.load_server 9 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'logger' # Fix concurrent-ruby removing logger dependency which Rails itself does not have 7 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 8 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | <% if ENV['FORCE_POSTGRES'] %> 13 | development: 14 | adapter: postgresql 15 | database: bridgetroll_development 16 | host: localhost 17 | <% end %> 18 | 19 | # Warning: The database defined as "test" will be erased and 20 | # re-generated from your development database when you run "rake". 21 | # Do not set this db to the same as development or production. 22 | test: 23 | adapter: sqlite3 24 | database: db/test<%= ENV['TEST_ENV_NUMBER'] %>.sqlite3 25 | pool: 5 26 | timeout: 5000 27 | 28 | <% if ENV['FORCE_POSTGRES'] %> 29 | test: 30 | adapter: postgresql 31 | database: bridgetroll_test<%= ENV['TEST_ENV_NUMBER'] %> 32 | host: localhost 33 | user: <%= ENV["POSTGRES_USER"] %> 34 | password: <%= ENV["POSTGRES_PASSWORD"] %> 35 | <% end %> 36 | 37 | production: 38 | adapter: postgresql 39 | encoding: unicode 40 | url: <%= ENV["DATABASE_URL"] %> 41 | pool: <%= ENV["DB_POOL"] || ENV['RAILS_MAX_THREADS'] || 5 %> 42 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/environments/staging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Just use the production settings 4 | require File.expand_path('production.rb', __dir__) 5 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # ActiveSupport::Reloader.to_prepare do 6 | # ApplicationController.renderer.defaults.merge!( 7 | # http_host: 'example.org', 8 | # https: false 9 | # ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = '1.2' 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | # Add Yarn node_modules folder to the asset load path. 11 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 10 | Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE'] 11 | -------------------------------------------------------------------------------- /config/initializers/constants.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | COOKBOOK_LINK = 'https://github.com/bridgefoundry/WorkshopCookbook/wiki/Cookbook' 4 | COOKBOOK_PLANNING_TASKS_LINK = 'https://github.com/bridgefoundry/WorkshopCookbook/wiki/Workshop-Planning-Tasks' 5 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide content security policy. 6 | # See the Securing Rails Applications Guide for more information: 7 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 8 | 9 | # Rails.application.configure do 10 | # config.content_security_policy do |policy| 11 | # policy.default_src :self, :https 12 | # policy.font_src :self, :https, :data 13 | # policy.img_src :self, :https, :data 14 | # policy.object_src :none 15 | # policy.script_src :self, :https 16 | # policy.style_src :self, :https 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | # 21 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 22 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 23 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 24 | # 25 | # # Report violations without enforcing the policy. 26 | # # config.content_security_policy_report_only = true 27 | # end 28 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid options are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Avoid CORS issues when API is called from the frontend app. 6 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 7 | 8 | # Read more: https://github.com/cyu/rack-cors 9 | 10 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 11 | # allow do 12 | # origins 'example.com' 13 | # 14 | # resource '*', 15 | # headers: :any, 16 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 17 | # end 18 | # end 19 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 6 | # Use this to limit dissemination of sensitive information. 7 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 8 | Rails.application.config.filter_parameters += %i[ 9 | passw email secret token _key crypt salt certificate otp ssn cvv cvc 10 | ] 11 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, '\1en' 10 | # inflect.singular /^(ox)en/i, '\1' 11 | # inflect.irregular 'person', 'people' 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # inflect.acronym 'RESTful' 18 | # end 19 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new mime types for use in respond_to blocks: 6 | # Mime::Type.register "text/richtext", :rtf 7 | -------------------------------------------------------------------------------- /config/initializers/per_form_csrf_tokens.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Enable per-form CSRF tokens. 6 | Rails.application.config.action_controller.per_form_csrf_tokens = true 7 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 5 | # 6 | # Rails.application.config.permissions_policy do |f| 7 | # f.camera :none 8 | # f.gyroscope :none 9 | # f.microphone :none 10 | # f.usb :none 11 | # f.fullscreen :self 12 | # f.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /config/initializers/rack_profiler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rack-mini-profiler' 4 | 5 | # initialization is skipped so trigger it 6 | Rack::MiniProfilerRails.initialize!(Rails.application) 7 | -------------------------------------------------------------------------------- /config/initializers/request_forgery_protection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Enable origin-checking CSRF mitigation. 6 | Rails.application.config.action_controller.forgery_protection_origin_check = true 7 | -------------------------------------------------------------------------------- /config/initializers/sentry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if Rails.env.production? 4 | Sentry.init do |config| 5 | # get breadcrumbs from logs 6 | config.breadcrumbs_logger = %i[active_support_logger http_logger] 7 | # Add data like request headers and IP for users, if applicable; 8 | # see https://docs.sentry.io/platforms/ruby/data-management/data-collected/ for more info 9 | config.send_default_pii = true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Rails.application.config.session_store :cookie_store, key: '_bridgetroll_session' 6 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Labels and hints examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | 27 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Spring.watch( 4 | '.ruby-version', 5 | '.rbenv-vars', 6 | 'tmp/restart.txt', 7 | 'tmp/caching-dev.txt' 8 | ) 9 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/migrate/20160110000811_add_location_to_event_sessions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddLocationToEventSessions < ActiveRecord::Migration[4.2] 4 | def change 5 | add_reference :event_sessions, :location, index: true, foreign_key: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160211061631_add_chapter_leaderships_again.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddChapterLeadershipsAgain < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :chapter_leaderships do |t| 6 | t.integer :user_id 7 | t.integer :chapter_id 8 | 9 | t.references 10 | end 11 | 12 | add_foreign_key :chapter_leaderships, :users 13 | add_foreign_key :chapter_leaderships, :chapters 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20160312224628_create_organization_leaderships.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateOrganizationLeaderships < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :organization_leaderships do |t| 6 | t.references :user, index: true, foreign_key: true 7 | t.references :organization, index: true, foreign_key: true 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160620044512_add_unaccent_extension_for_postgres.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddUnaccentExtensionForPostgres < ActiveRecord::Migration[4.2] 4 | def change 5 | enable_extension 'unaccent' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160710221707_create_organization_subscriptions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateOrganizationSubscriptions < ActiveRecord::Migration[4.2] 4 | def change 5 | create_table :organization_subscriptions do |t| 6 | t.references :user 7 | t.references :organization 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160807055624_add_twitter_username_to_profiles.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddTwitterUsernameToProfiles < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :profiles, :twitter_username, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20161002052810_add_external_event_editor_to_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddExternalEventEditorToUsers < ActiveRecord::Migration[4.2] 4 | def change 5 | add_column :users, :external_event_editor, :boolean, default: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20161204040052_rename_external_event_data_to_imported_event_data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameExternalEventDataToImportedEventData < ActiveRecord::Migration[4.2] 4 | def change 5 | rename_column :events, :external_event_data, :imported_event_data 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20161206055208_regenerate_canonical_sfruby_meetup_urls.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RegenerateCanonicalSfrubyMeetupUrls < ActiveRecord::Migration[4.2] 4 | class Event < ApplicationRecord 5 | serialize :imported_event_data, coder: JSON 6 | end 7 | 8 | def change 9 | # The old format stored was like http://www.sfruby.info/events/13311831/, 10 | # but those URLs don't work anymore for whatever reason. 11 | 12 | # URLs of the form https://www.meetup.com/sfruby/events/13311831/ 13 | # still work fine. 14 | meetup_events = Event.where.not(imported_event_data: nil) 15 | .select { |e| e.imported_event_data['student_event']['url'].include?('sfruby') } 16 | meetup_events.each do |e| 17 | imported_event_data = e.imported_event_data 18 | imported_event_data['volunteer_event']['url'] = 19 | "https://www.meetup.com/sfruby/events/#{imported_event_data['volunteer_event']['id']}/" 20 | imported_event_data['student_event']['url'] = 21 | "https://www.meetup.com/sfruby/events/#{imported_event_data['student_event']['id']}/" 22 | e.update_attribute(:imported_event_data, imported_event_data) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20161228162402_create_courses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateCourses < ActiveRecord::Migration[5.0] 4 | def change 5 | create_table :courses do |t| 6 | t.string :name 7 | t.string :title 8 | t.text :description 9 | 10 | t.timestamps null: false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20161228162440_create_levels.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateLevels < ActiveRecord::Migration[5.0] 4 | def change 5 | create_table :levels do |t| 6 | t.references :course, foreign_key: true 7 | t.integer :num 8 | t.string :color 9 | t.string :title 10 | t.text :level_description 11 | 12 | t.timestamps null: false 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20170110062210_set_id_sequence_for_new_courses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SetIdSequenceForNewCourses < ActiveRecord::Migration[5.0] 4 | def up 5 | if Rails.application.using_postgres? 6 | execute('ALTER SEQUENCE courses_id_seq START with 10000 RESTART;') 7 | else 8 | execute("UPDATE sqlite_sequence SET seq = 10000 WHERE name = 'courses';") 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20170202035358_add_food_provided_to_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddFoodProvidedToEvents < ActiveRecord::Migration[5.0] 4 | def change 5 | add_column(:events, :food_provided, :boolean, default: true, null: false) 6 | Event.update_all(food_provided: true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20170319191233_add_custom_question_to_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddCustomQuestionToEvents < ActiveRecord::Migration[5.0] 4 | def change 5 | add_column :events, :custom_question, :text 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20170319192836_add_custom_question_answer_to_rsvps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddCustomQuestionAnswerToRsvps < ActiveRecord::Migration[5.0] 4 | def change 5 | add_column :rsvps, :custom_question_answer, :text 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20170707161519_add_appropriate_class_for_skill_to_survey.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddAppropriateClassForSkillToSurvey < ActiveRecord::Migration[5.0] 4 | def change 5 | add_column :surveys, :appropriate_for_skill, :text 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200102135517_add_primary_key_to_regions_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddPrimaryKeyToRegionsUsers < ActiveRecord::Migration[5.2] 4 | def change 5 | add_column :regions_users, :id, :primary_key 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Dir[Rails.root.join('db/seeds/*.rb')].each do |seed_file| 4 | require seed_file 5 | end 6 | 7 | if Rails.env.development? 8 | Seeder.seed_region 9 | Seeder.seed_chapter 10 | Seeder.admin_user 11 | Seeder.seed_courses 12 | Seeder.seed_event 13 | Seeder.seed_multiple_location_event 14 | Seeder.seed_past_event 15 | end 16 | -------------------------------------------------------------------------------- /db/seeds/admin_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Seeder 4 | def self.admin_user 5 | # seeds the database with an admin user 6 | admin = User.where(email: 'admin@example.com').first_or_initialize 7 | admin.update( 8 | password: 'password', 9 | first_name: 'Admin', 10 | last_name: 'User', 11 | time_zone: 'Pacific Time (US & Canada)', 12 | admin: true 13 | ) 14 | admin.confirm 15 | admin.save! 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/seeds/course_populator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CoursePopulator 4 | module_function 5 | 6 | def populate_courses 7 | file = YAML.safe_load(File.expand_path('courses.yaml', __dir__)) 8 | YAML.load_file(file).each do |course| 9 | c = Course.where( 10 | id: course[:id] 11 | ).first_or_create!( 12 | name: course[:name], 13 | title: course[:title], 14 | description: course[:description] 15 | ) 16 | course[:levels].each do |level| 17 | c.levels.where( 18 | num: level[:level] 19 | ).first_or_create!( 20 | color: level[:color], 21 | title: level[:title], 22 | level_description: level[:level_description] 23 | ) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/seeds/seed_chapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Seeder 4 | def self.seed_chapter 5 | org = Organization.find_or_create_by(name: 'RailsBridge') 6 | 7 | Chapter.find_or_create_by(name: 'RailsBridge Seattle', organization: org) 8 | Chapter.find_or_create_by(name: 'RailsBridge San Francisco', organization: org) 9 | Chapter.find_or_create_by(name: 'RailsBridge Tulsa', organization: org) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/seeds/seed_courses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'course_populator' 4 | 5 | module Seeder 6 | def self.seed_courses 7 | CoursePopulator.populate_courses 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/seeds/seed_region.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Seeder 4 | def self.seed_region 5 | Region.find_or_create_by(name: 'Seattle') 6 | Region.find_or_create_by(name: 'San Francisco') 7 | Region.find_or_create_by(name: 'Tulsa') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /devenv.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: 6 | 7 | let 8 | isCI = builtins.getEnv "CI" != ""; 9 | in 10 | { 11 | packages = with pkgs; [ 12 | git 13 | libyaml.dev # needed by psych / needed by rails 14 | ]; 15 | 16 | # this is required by the pg gem on linux 17 | env = lib.mkIf (isCI) { 18 | LD_LIBRARY_PATH = lib.makeLibraryPath [ 19 | pkgs.krb5 20 | pkgs.openldap 21 | ]; 22 | }; 23 | 24 | services.postgres = { 25 | enable = true; 26 | listen_addresses = "localhost"; 27 | package = pkgs.postgresql_17; # this aligns with heroku 28 | }; 29 | 30 | languages.ruby = { 31 | enable = true; 32 | versionFile = ./.ruby-version; 33 | bundler.enable = true; 34 | }; 35 | 36 | languages.javascript = { 37 | enable = true; 38 | yarn.enable = true; 39 | yarn.install.enable = true; 40 | }; 41 | 42 | # don't enable git-hooks on CI 43 | # although this also disables being able to execute these checks with `devenv test` 44 | # it's ok because hooks are broken in CI anyways. 45 | # 46 | # If we have a dev that can't use hooks on linux we should change this conditional 47 | git-hooks.hooks = lib.mkIf (!isCI) { 48 | shellcheck.enable = true; 49 | nixfmt-rfc-style.enable = true; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /devenv.yaml: -------------------------------------------------------------------------------- 1 | inputs: 2 | nixpkgs: 3 | url: github:cachix/devenv-nixpkgs/rolling 4 | nixpkgs-ruby: 5 | url: github:bobvanderlinden/nixpkgs-ruby 6 | inputs: 7 | nixpkgs: 8 | follows: nixpkgs 9 | -------------------------------------------------------------------------------- /doc/admin.md: -------------------------------------------------------------------------------- 1 | # Admin Tasks 2 | 3 | ## Create a new chapter 4 | 5 | 1. Log in to https://events.bridgefoundry.org/ 6 | 1. Navigate to https://events.bridgefoundry.org/chapters 7 | 1. At the bottom on the page click "New Chapter" which will direct you to https://events.bridgefoundry.org/chapters/new 8 | 1. Fill out the appropriate information for creating a new chapter. 9 | 1. After the new chapter is created, you have the options to edit chapter leaders and edit the new chapter information you just entered. 10 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/lib/assets/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/external_event_import.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | desc 'Import external events from a csv' 4 | task :import_external_events, [:url] => :environment do |_t, args| 5 | ExternalEventImporter.new.import(args[:url]) 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/jasmine.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :jasmine do 4 | desc 'Precompile assets, run Jasmine browser specs, and clean up precompiled assets' 5 | task ci: :environment do 6 | # 1. Precompile assets for Jasmine tests 7 | Rake::Task['assets:precompile'].invoke 8 | 9 | # 2. Run Jasmine browser specs 10 | raise 'Jasmine browser specs failed' unless system('jasmine-browser-runner runSpecs') 11 | ensure 12 | # 3. Clean up precompiled assets 13 | Rake::Task['assets:clobber'].invoke 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tasks/populate_courses.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require Rails.root.join('db/seeds/course_populator') 4 | 5 | desc 'Populate courses (only run once)' 6 | task populate_courses: :environment do |_t, _args| 7 | CoursePopulator.populate_courses 8 | end 9 | -------------------------------------------------------------------------------- /lib/tasks/rspec_with_retries.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'support/rspec_rerunner' 4 | 5 | desc 'Run rspec tests, rerunning if anything fails' 6 | task rspec_with_retries: :environment do 7 | RspecRerunner.new.run_tests 8 | end 9 | -------------------------------------------------------------------------------- /lib/tasks/scheduler.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # these reminders are sent by the heroku reminder addon 4 | 5 | desc 'Send reminders for upcoming events within the reminder window' 6 | task send_reminders: :environment do 7 | puts 'Sending reminder emails...' 8 | ReminderSender.send_all_reminders 9 | puts '...done.' 10 | end 11 | 12 | desc 'Send surveys for all past events' 13 | task send_surveys: :environment do 14 | puts 'Sending surveys...' 15 | SurveySender.send_all_surveys 16 | puts '...done.' 17 | end 18 | -------------------------------------------------------------------------------- /lib/tasks/support/failure_file_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class FailureFileParser 4 | def initialize(filename) 5 | @filename = filename 6 | end 7 | 8 | def failures_from_persistence_file 9 | File 10 | .readlines(@filename)[2..] 11 | .map { |l| l.split('|').map(&:strip) } 12 | .select { |file_ref| file_ref[1] == 'failed' } 13 | .map { |file_ref| { example_id: file_ref[0] } } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tasks/support/rspec_rerunner.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | require_relative 'failure_file_parser' 5 | 6 | class RspecRerunner 7 | RERUN_ATTEMPTS = ENV.fetch('RERUN_ATTEMPTS', 3).to_i 8 | RERUN_THRESHOLD = ENV.fetch('RERUN_THRESHOLD', 5).to_i 9 | 10 | def initialize 11 | @failure_file_parser = FailureFileParser.new( 12 | Rails.root.join('tmp/rspec_examples.txt') 13 | ) 14 | end 15 | 16 | def run_tests 17 | Bundler.with_unbundled_env do 18 | succeeded_initially = system('bundle exec rake parallel:spec') 19 | return if succeeded_initially 20 | end 21 | 22 | failure_count = @failure_file_parser.failures_from_persistence_file.length 23 | if failure_count > RERUN_THRESHOLD 24 | puts "#{failure_count} tests failed, first run, which is over the rerun threshold of #{RERUN_THRESHOLD}" 25 | exit 1 26 | end 27 | 28 | RERUN_ATTEMPTS.times do 29 | Bundler.with_unbundled_env do 30 | succeeded_on_retry = system("SPEC_OPTS='--only-failures' bundle exec rake parallel:spec") 31 | return if succeeded_on_retry 32 | end 33 | end 34 | 35 | puts "TESTS HAVE FAILED AFTER #{RERUN_ATTEMPTS} RETRIES!" 36 | exit 1 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge_troll", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "datatables.net": "1.10.22", 6 | "datatables.net-bs": "1.10.13", 7 | "markerclustererplus": "2.1.4", 8 | "masonry-layout": "4.1.1", 9 | "select2": "4.0.3" 10 | }, 11 | "devDependencies": { 12 | "jasmine-browser-runner": "^3.0.0", 13 | "jasmine-core": "^5.7.1", 14 | "jasmine-jquery": "^2.1.1", 15 | "sinon": "1.17.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/email_images/confirm_account.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/public/email_images/confirm_account.gif -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /public/troll_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/public/troll_icon.png -------------------------------------------------------------------------------- /script/rubymine_open_spec_or_impl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "This script must be called with an argument" >&2 5 | exit 1 6 | fi 7 | 8 | /usr/local/bin/mine $(script/find_spec_or_impl.rb $1) 9 | -------------------------------------------------------------------------------- /spec/controllers/chapters/leaders_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Chapters::LeadersController do 6 | let(:admin) { create(:user, admin: true, first_name: 'Admin', last_name: 'User') } 7 | let(:chapter) { create :chapter } 8 | 9 | describe 'potential leaders' do 10 | before do 11 | sign_in admin 12 | end 13 | 14 | it 'includes all users not currently assigned as leaders' do 15 | leader = create(:user, first_name: 'Steve') 16 | chapter.leaders << leader 17 | 18 | non_leader = create(:user, first_name: 'Steve') 19 | 20 | get :potential, params: { chapter_id: chapter.id, q: 'Steve' }, format: :json 21 | 22 | expect(JSON.parse(response.body).map { |u| u['id'] }).to eq([non_leader.id]) 23 | end 24 | end 25 | 26 | describe '#destroy' do 27 | before do 28 | sign_in admin 29 | end 30 | 31 | it 'removes a chapter leader' do 32 | leader = create(:user) 33 | chapter.leaders << leader 34 | 35 | expect do 36 | delete :destroy, params: { chapter_id: chapter.id, id: leader.id } 37 | end.to change(chapter.leaders, :count).by(-1) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/controllers/checkiners_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe CheckinersController do 6 | let(:event) { create(:event) } 7 | let(:user) { create(:user) } 8 | 9 | context 'a user that is logged in and is an organizer for a published event' do 10 | before do 11 | event.organizers << user 12 | 13 | sign_in user 14 | end 15 | 16 | it 'can see list of checkiners' do 17 | get :index, params: { event_id: event.id } 18 | expect(response).to be_successful 19 | end 20 | 21 | describe 'assigning checkiners' do 22 | it 'can promote a user to checkiner' do 23 | other_user_rsvp = create(:rsvp, event: event) 24 | expect do 25 | post :create, params: { event_id: event.id, event_checkiner: { rsvp_id: other_user_rsvp.id } } 26 | end.to(change { other_user_rsvp.reload.checkiner }) 27 | expect(response).to redirect_to(event_checkiners_path(event)) 28 | end 29 | 30 | it 'shows an error if no user is provided' do 31 | post :create, params: { event_id: event.id } 32 | expect(assigns(:event).errors[:base].length).to be >= 1 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/controllers/events/students_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Events::StudentsController do 6 | let(:student_rsvp) { create(:student_rsvp) } 7 | let(:event) { student_rsvp.event } 8 | let(:organizer) { create(:user) } 9 | 10 | before do 11 | event.organizers << organizer 12 | sign_in organizer 13 | end 14 | 15 | describe '#index' do 16 | it 'responds successfully, with the right headers' do 17 | get :index, params: { event_id: event.to_param }, format: :csv 18 | expect(assigns(:students)).to eq(event.student_rsvps) 19 | expect(response.media_type).to eq('text/csv') 20 | expect(response).to be_successful 21 | 22 | csv_rows = CSV.parse(response.body) 23 | expect(csv_rows[0][0]).to eq('Student Name') 24 | expect(csv_rows[1][0]).to eq(student_rsvp.user.full_name) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/controllers/profiles_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe ProfilesController do 6 | let(:user) { create(:user) } 7 | let(:other_user) { create(:user) } 8 | 9 | before do 10 | sign_in user 11 | end 12 | 13 | describe 'showing profiles' do 14 | render_views 15 | 16 | it 'lets users view their own profile' do 17 | get :show, params: { user_id: user.id } 18 | expect(response).to be_successful 19 | expect(response.body).to include(ERB::Util.html_escape(user.full_name)) 20 | end 21 | 22 | it "lets users view other user's profiles" do 23 | get :show, params: { user_id: other_user.id } 24 | expect(response).to be_successful 25 | expect(response.body).to include(ERB::Util.html_escape(other_user.full_name)) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/controllers/static_pages_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe StaticPagesController do 6 | describe 'GET #style_guide' do 7 | before do 8 | get :style_guide 9 | end 10 | 11 | it 'resonds with a success message' do 12 | expect(response.status).to eq(200) 13 | end 14 | 15 | it "renders the 'style_guide' template" do 16 | expect(response).to render_template(:style_guide) 17 | end 18 | end 19 | 20 | describe 'GET #about' do 21 | before do 22 | get :about 23 | end 24 | 25 | it 'resonds with a success message' do 26 | expect(response.status).to eq(200) 27 | end 28 | 29 | it "renders the 'about' template" do 30 | expect(response).to render_template(:about) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/controllers/users/events_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Users::EventsController do 6 | let(:user) { FactoryBot.create(:user) } 7 | let(:event) { FactoryBot.create(:event) } 8 | 9 | before { FactoryBot.create(:rsvp, user: user, event: event, checked_in: true) } 10 | 11 | describe '#index' do 12 | it 'responds successfully with json' do 13 | get :index, params: { user_id: user.id } 14 | expect(response.media_type).to eq('application/json') 15 | expect(response).to be_successful 16 | end 17 | 18 | it 'responds with the correct count' do 19 | get :index, params: { user_id: user.id } 20 | expected_response = { event_count: 1 }.to_json 21 | expect(response.body).to eq(expected_response) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/controllers/volunteers_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe VolunteersController do 6 | let!(:event) { create(:event) } 7 | let!(:user_organizer) { create(:user) } 8 | let!(:vol1) { create(:user, first_name: 'Vol1') } 9 | let!(:vol2) { create(:user, first_name: 'Vol2') } 10 | let!(:vol3) { create(:user, first_name: 'Vol3') } 11 | 12 | before do 13 | event.organizers << user_organizer 14 | create(:teacher_rsvp, user: vol1, event: event) 15 | create(:teacher_rsvp, user: vol2, event: event) 16 | create(:teacher_rsvp, user: vol3, event: event) 17 | sign_in user_organizer 18 | end 19 | 20 | context 'a user that is logged in and is an organizer for the event' do 21 | describe 'index' do 22 | render_views 23 | 24 | it 'is able to see list of volunteers' do 25 | get :index, params: { event_id: event.id } 26 | expect(response).to be_successful 27 | 28 | expect(response.body).to have_content 'Vol1' 29 | expect(response.body).to have_content 'Vol2' 30 | expect(response.body).to have_content 'Vol3' 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/features/admin/admin_dashboard_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'the admin dashboard' do 6 | context 'when signed in as a normal user' do 7 | before do 8 | sign_in_as(create(:user)) 9 | end 10 | 11 | it 'redirects to the homepage' do 12 | visit '/admin_dashboard' 13 | expect(page).to have_current_path('/') 14 | end 15 | end 16 | 17 | context 'when signed in as an admin' do 18 | before do 19 | admin = create(:user, first_name: 'Gavin', last_name: 'Grapejuice', admin: true) 20 | sign_in_as(admin) 21 | end 22 | 23 | it 'shows a list of admins' do 24 | visit '/admin_dashboard' 25 | expect(page).to have_content('Gavin Grapejuice') 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/features/events/edit_event_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'Edit Event' do 6 | let(:user_organizer) { create(:user, email: 'organizer@mail.com', first_name: 'Sam', last_name: 'Spade') } 7 | let(:drafted_event) { create(:event, title: 'draft title', current_state: :draft) } 8 | 9 | before do 10 | drafted_event.organizers << user_organizer 11 | 12 | sign_in_as(user_organizer) 13 | visit edit_event_path(drafted_event) 14 | end 15 | 16 | context 'event saved previously as draft' do 17 | it 'allows a draft to be saved' do 18 | expect(drafted_event).to be_draft 19 | fill_in 'Title', with: 'real title' 20 | check('coc') 21 | click_on 'Submit Event For Approval' 22 | 23 | expect(page).to have_content('Event was successfully updated') 24 | expect(page).to have_content('real title') 25 | 26 | expect(page).to have_current_path event_path(drafted_event), ignore_query: true 27 | expect(Event.find(drafted_event.id)).to be_pending_approval 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/features/events/organizer/attendee_details_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'the attendee details modal', :js do 6 | let(:event) { create(:event) } 7 | let!(:student_rsvp) { create(:student_rsvp, user: create(:user), event: event) } 8 | 9 | before do 10 | organizer = create(:user) 11 | event.organizers << organizer 12 | 13 | sign_in_as(organizer) 14 | 15 | visit event_organize_sections_path(event) 16 | 17 | within 'ul.students' do 18 | find('i').click 19 | end 20 | end 21 | 22 | it "lists the student's RSVP details" do 23 | within '.modal-body' do 24 | expect(page).to have_content(student_rsvp.operating_system_title) 25 | expect(page).to have_content(student_rsvp.level_title) 26 | expect(page).to have_content(student_rsvp.job_details) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/features/external_events/external_event_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'External Event' do 6 | let(:admin_user) { create(:user, admin: true) } 7 | let!(:region) { create(:region) } 8 | let!(:chapter) { create(:chapter) } 9 | 10 | before do 11 | sign_in_as(admin_user) 12 | end 13 | 14 | it 'can create a new external event' do 15 | visit external_events_path 16 | click_link 'New External Event' 17 | 18 | select region.name, from: 'external_event_region_id' 19 | select chapter.name, from: 'external_event_chapter_id' 20 | 21 | fill_in 'Name', with: 'Interesting External Event' 22 | fill_in 'URL', with: 'http://example.com/event' 23 | fill_in 'City', with: 'San Francisco' 24 | fill_in 'Location', with: 'Tonga Room' 25 | fill_in 'Organizers', with: 'Ham Sandwich and Cheese Fries' 26 | 27 | click_button 'Create External event' 28 | 29 | expect(ExternalEvent.last.region).to eq(region) 30 | 31 | expect(page).to have_content('Interesting External Event') 32 | 33 | visit region_path(region) 34 | 35 | expect(page).to have_content('Interesting External Event') 36 | expect(ExternalEvent.last.chapter).to eq(chapter) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/features/organizations/organizations_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'organization pages' do 6 | describe 'organization index' do 7 | let(:event) { create(:event, title: 'Some Event') } 8 | 9 | it 'shows a map of events with their last event', :js do 10 | visit organizations_path 11 | 12 | expect(page).to have_css('#gmaps4rails_map') 13 | end 14 | end 15 | 16 | describe 'creating an organization' do 17 | let(:admin) { create(:user, admin: true) } 18 | 19 | it 'allows admins to create a new organization' do 20 | sign_in_as(admin) 21 | 22 | visit new_organization_path 23 | fill_in 'Name', with: 'CantaloupeBridge' 24 | 25 | expect do 26 | click_on 'Create Organization' 27 | end.to change(Organization, :count).by(1) 28 | 29 | expect(Organization.last.name).to eq('CantaloupeBridge') 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/features/style_guide_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'visiting the style guide' do 6 | describe 'as an unauthenticated user' do 7 | it 'can view page' do 8 | visit '/style_guide' 9 | 10 | expect(page).to have_content('Style Guide') 11 | end 12 | 13 | it "can redirect from '/styleguide' to #style_guide" do 14 | visit '/styleguide' 15 | 16 | expect(page).to have_current_path '/style_guide' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/users/users_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'the users page', :js do 6 | before do 7 | sign_in_as(create(:user, first_name: 'Some', last_name: 'LoggedInUser')) 8 | create(:user, first_name: 'Some', last_name: 'OtherUser') 9 | create(:meetup_user, full_name: 'Some MeetupUser') 10 | end 11 | 12 | it 'shows a list of users' do 13 | visit '/users' 14 | expect(page).to have_content('Some LoggedInUser') 15 | expect(page).to have_content('Some OtherUser') 16 | expect(page).to have_content('Some MeetupUser') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/javascripts/helpers/ModelFactories.js: -------------------------------------------------------------------------------- 1 | Factories = (function () { 2 | var Role = Bridgetroll.Enums.Role; 3 | 4 | var id = 9; 5 | return { 6 | attendee: function (attrs) { 7 | id++; 8 | return _.extend({id: id, event_id: 191}, attrs); 9 | }, 10 | volunteer: function (attrs) { 11 | return _.extend(this.attendee({role_id: Role.VOLUNTEER}), attrs); 12 | }, 13 | student: function (attrs) { 14 | return _.extend(this.attendee({role_id: Role.STUDENT}), attrs); 15 | } 16 | }; 17 | })(); -------------------------------------------------------------------------------- /spec/javascripts/helpers/SpecHelper.js: -------------------------------------------------------------------------------- 1 | var fakeServer = _.extend(sinon.fakeServer, { 2 | requestFor: function (url) { 3 | return _.findWhere(this.requests, {url: url}); 4 | }, 5 | 6 | completeRequest: function (url, body) { 7 | var request = this.requestFor(url); 8 | if (!request) { 9 | throw 'No pending request for ' + url + '. Existing requests: ' + _.pluck(this.requests, 'url'); 10 | } 11 | 12 | request.respond(200, {'Content-Type': 'application/json'}, JSON.stringify(body)); 13 | } 14 | }); 15 | 16 | function getFixtures() { 17 | return $('#jasmine-fixtures'); 18 | } 19 | 20 | beforeEach(function() { 21 | if (getFixtures().length === 0) { 22 | $('body').append('
'); 23 | } 24 | this.server = fakeServer.create(); 25 | Bridgetroll.modalContainerSelector = '#jasmine-fixtures'; 26 | }); 27 | 28 | afterEach(function () { 29 | this.server.restore(); 30 | getFixtures().empty(); 31 | }); 32 | -------------------------------------------------------------------------------- /spec/javascripts/helpers/whenReady.js: -------------------------------------------------------------------------------- 1 | window.whenReady = function (fn) { 2 | if (document.readyState != "loading") { 3 | fn(); 4 | } else { 5 | document.addEventListener("DOMContentLoaded", fn); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /spec/mailers/previews/admin_preview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AdminPreview < ActionMailer::Preview 4 | def test_group_mail 5 | options = { to: 'test@example.com' } 6 | AdminMailer.test_group_mail(options) 7 | end 8 | 9 | def test_individual_mail 10 | options = { to: 'test@example.com' } 11 | AdminMailer.test_individual_mail(options) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/mailers/previews/devise/mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Devise 4 | class MailerPreview < ActionMailer::Preview 5 | def confirmation_instructions 6 | Devise::Mailer.confirmation_instructions(User.first, {}) 7 | end 8 | 9 | def reset_password_instructions 10 | Devise::Mailer.reset_password_instructions(User.first, {}) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/mailers/previews/event_preview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class EventPreview < ActionMailer::Preview 4 | def from_organizer 5 | event = Event.first 6 | options = { 7 | event: event, 8 | sender: event.organizers.first, 9 | recipients: event.students, 10 | body: "Howdy folks, what's the haps?" 11 | } 12 | 13 | EventMailer.from_organizer(options) 14 | end 15 | 16 | def unpublished_event 17 | EventMailer.unpublished_event(Event.last) 18 | end 19 | 20 | def event_pending_approval 21 | EventMailer.event_pending_approval(Event.first) 22 | end 23 | 24 | def event_has_been_approved 25 | EventMailer.event_has_been_approved(Event.first) 26 | end 27 | 28 | def new_event 29 | EventMailer.new_event(Event.first) 30 | end 31 | 32 | def new_organizer_alert 33 | EventMailer.new_organizer_alert(Event.first, User.first) 34 | end 35 | 36 | def multiple_location_event 37 | EventMailer.new_event(EventSession.where.not(location_id: nil).first.event) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/mailers/previews/rsvp_preview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RsvpPreview < ActionMailer::Preview 4 | def confirmation 5 | RsvpMailer.confirmation(Rsvp.first) 6 | end 7 | 8 | def reminder 9 | RsvpMailer.reminder(Rsvp.first) 10 | end 11 | 12 | def multiple_location_event_reminder 13 | rsvp = EventSession.where.not(location_id: nil).all.filter_map do |event_session| 14 | event_session.rsvps.where(role_id: Role::VOLUNTEER.id).first 15 | end.first 16 | RsvpMailer.reminder(rsvp) 17 | end 18 | 19 | def reminder_for_session 20 | RsvpMailer.reminder_for_session(RsvpSession.first) 21 | end 22 | 23 | def off_waitlist 24 | RsvpMailer.off_waitlist(Rsvp.first) 25 | end 26 | 27 | def childcare_notification 28 | RsvpMailer.childcare_notification(Rsvp.first) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/mailers/previews/survey_preview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SurveyPreview < ActionMailer::Preview 4 | def notification 5 | SurveyMailer.notification(Rsvp.first) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/mailers/survey_mailer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe SurveyMailer do 6 | let(:rsvp) { create(:rsvp) } 7 | let(:user) { rsvp.user } 8 | let(:event) { rsvp.event } 9 | let(:mail) { described_class.notification(rsvp) } 10 | 11 | describe 'the survey email' do 12 | it_behaves_like 'a mailer view' 13 | 14 | it "is sent to the RSVP'd person" do 15 | expect(mail.to).to eq([user.email]) 16 | end 17 | 18 | it 'includes the survey link' do 19 | expect(mail.subject).to eq "How was #{event.title}?" 20 | expect(mail.body).to include 'Click right here to take the survey!' 21 | expect(mail.body).to include 'Test greeting' 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/models/chapter_leadership_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe ChapterLeadership do 6 | describe 'validations' do 7 | let(:user) { create :user } 8 | let(:chapter) { create :chapter } 9 | 10 | describe 'uniqueness' do 11 | let(:duplicate_leadership) { described_class.new user: user, chapter: chapter } 12 | 13 | before { described_class.create user: user, chapter: chapter } 14 | 15 | it "doesn't save dupes" do 16 | expect(duplicate_leadership).to be_invalid 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/chapter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Chapter do 6 | it { is_expected.to validate_presence_of(:name) } 7 | 8 | describe '#leader?' do 9 | let(:chapter) { create :chapter } 10 | let(:user) { create :user } 11 | 12 | context 'with a user that is a leader' do 13 | before { ChapterLeadership.create(user: user, chapter: chapter) } 14 | 15 | it 'is true' do 16 | expect(chapter).to be_leader(user) 17 | end 18 | end 19 | 20 | context 'with a user that is not a leader' do 21 | it 'is false' do 22 | expect(chapter).not_to be_leader(user) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/models/course_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Course do 6 | it { is_expected.to have_many(:levels) } 7 | it { is_expected.to have_many(:events) } 8 | it { is_expected.to validate_presence_of(:name) } 9 | it { is_expected.to validate_presence_of(:title) } 10 | it { is_expected.to validate_presence_of(:description) } 11 | 12 | describe 'validations' do 13 | it 'does not allow multiple levels to have the same position' do 14 | course = build(:course) 15 | course.levels << build(:level, color: 'blue') 16 | course.levels << build(:level, color: 'pink') 17 | expect(course).not_to be_valid 18 | expect(course.errors[:level].join).to match(/position/) 19 | end 20 | 21 | it 'does not allow multiple levels to have the same color' do 22 | course = build(:course) 23 | course.levels << build(:level, num: 1) 24 | course.levels << build(:level, num: 2) 25 | expect(course).not_to be_valid 26 | expect(course.errors[:level].join).to match(/color/) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/models/event_email_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe EventEmail do 6 | it { is_expected.to belong_to(:event) } 7 | it { is_expected.to belong_to(:sender).required } 8 | 9 | it { is_expected.to have_many(:event_email_recipients).dependent(:destroy) } 10 | it { is_expected.to have_many(:recipient_rsvps).through(:event_email_recipients) } 11 | it { is_expected.to have_many(:recipients).through(:recipient_rsvps).source(:user) } 12 | 13 | it { is_expected.to validate_presence_of(:subject) } 14 | it { is_expected.to validate_presence_of(:body) } 15 | end 16 | -------------------------------------------------------------------------------- /spec/models/factory_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe 'FactoryBot factories' do 6 | FactoryBot.factories.map(&:name).uniq.each do |factory_name| 7 | next if factory_name.to_s.starts_with?('event') 8 | 9 | it "has a valid #{factory_name} factory" do 10 | expect(create(factory_name)).to be_persisted 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/models/level_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Level do 6 | it { is_expected.to belong_to(:course).optional } 7 | it { is_expected.to validate_presence_of(:num) } 8 | 9 | it { 10 | expect(described_class.new).to validate_inclusion_of(:num) 11 | .in_array((1..5).to_a) 12 | .with_message('Must be between 1 and 5') 13 | } 14 | 15 | it { is_expected.to validate_presence_of(:color) } 16 | it { is_expected.to validate_presence_of(:title) } 17 | it { is_expected.to validate_presence_of(:level_description) } 18 | 19 | describe 'description' do 20 | let(:course) { create :course } 21 | 22 | it 'shows description as array, not string' do 23 | expect(course.levels.first.description).to include('You have little to no experience with the terminal or a graphical IDE') 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/models/meetup_user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe MeetupUser do 6 | let(:user) { create(:meetup_user) } 7 | 8 | it "responds to 'profile' with an object that returns false to all profile attributes" do 9 | expect(user.profile.childcaring).to be false 10 | expect(user.profile.writing).to be false 11 | end 12 | 13 | describe '#profile_path' do 14 | it 'returns the same value as the appropriate rails helper' do 15 | expect(user.profile_path).to eq(Rails.application.routes.url_helpers.meetup_user_path(user)) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/models/region_leadership_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe RegionLeadership do 6 | describe 'validations' do 7 | let(:user) { create :user } 8 | let(:region) { create :region } 9 | 10 | describe 'uniqueness' do 11 | let(:duplicate_leadership) { described_class.new user: user, region: region } 12 | 13 | before { described_class.create user: user, region: region } 14 | 15 | it "doesn't save dupes" do 16 | expect(duplicate_leadership).to be_invalid 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/region_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Region do 6 | it { is_expected.to have_many(:locations) } 7 | it { is_expected.to have_many(:users).through(:regions_users) } 8 | 9 | it { is_expected.to validate_presence_of(:name) } 10 | 11 | describe '#leader?' do 12 | let(:region) { create :region } 13 | let(:user) { create :user } 14 | 15 | context 'with a user that is a leader' do 16 | before { RegionLeadership.create(user: user, region: region) } 17 | 18 | it 'is true' do 19 | expect(region).to be_leader(user) 20 | end 21 | end 22 | 23 | context 'with a user that is not a leader' do 24 | it 'is false' do 25 | expect(region).not_to be_leader(user) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/models/rsvp_session_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe RsvpSession do 6 | context 'checkins counter cache' do 7 | let(:rsvp) { create(:rsvp) } 8 | let!(:session1) { rsvp.rsvp_sessions.first } 9 | let!(:session2) { create(:rsvp_session, rsvp: rsvp) } 10 | 11 | it 'counts the number of checkins' do 12 | expect(rsvp.checkins_count).to eq(0) 13 | 14 | expect do 15 | session1.checked_in = true 16 | session1.save! 17 | end.to change { rsvp.reload.checkins_count }.by(1) 18 | 19 | expect do 20 | session2.checked_in = true 21 | session2.save! 22 | end.to change { rsvp.reload.checkins_count }.by(1) 23 | 24 | expect do 25 | session1.destroy 26 | end.to change { rsvp.reload.checkins_count }.by(-1) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/services/user_searcher_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe UserSearcher do 6 | let!(:user1) { create(:user, first_name: 'Abrahat', last_name: 'Zachson') } 7 | 8 | before { create(:user, first_name: 'Rochalle', last_name: 'Gorfdoor') } 9 | 10 | it 'returns the users matching a search query' do 11 | searcher = described_class.new(User, 'hat') 12 | expect(searcher.as_json).to contain_exactly({ id: user1.id, text: user1.full_name }) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/authorization_shared_behaviors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | shared_examples_for 'an action that requires user log-in' do 4 | it 'does not allow access to anonymous users' do 5 | make_request 6 | expect(response).to redirect_to(new_user_session_path) 7 | end 8 | end 9 | 10 | shared_examples_for 'an event action that requires an organizer' do 11 | it 'does not allow access to anonymous users' do 12 | make_request 13 | expect(response).to redirect_to(new_user_session_path) 14 | end 15 | 16 | it "does not allow access to users who aren't organizers of the event" do 17 | sign_in(create(:user)) 18 | make_request 19 | expect(response).to be_redirect 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/support/form_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormHelper 4 | def fill_in_select2(option) 5 | page.find('.select2-selection').click 6 | page.find('.select2-search__field').set(option) 7 | expect(page.find('.select2-results__option')).to have_content(option) 8 | page.find('.select2-results__option').click 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/jasmine-browser.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | srcDir: ".", 3 | srcFiles: [ 4 | "spec/javascripts/helpers/whenReady.js", 5 | "public/assets/application-*.js", 6 | "public/assets/section_organizer-*.js", 7 | ], 8 | specDir: ".", 9 | specFiles: ["spec/javascripts/**/*[sS]pec.?(m)js"], 10 | helpers: [ 11 | "node_modules/sinon/pkg/sinon.js", 12 | "node_modules/jasmine-jquery/lib/jasmine-jquery.js", 13 | "spec/javascripts/helpers/**/*.?(m)js", 14 | ], 15 | env: { 16 | stopSpecOnExpectationFailure: false, 17 | stopOnSpecFailure: false, 18 | random: true, 19 | // Fail if a suite contains multiple suites or specs with the same name. 20 | forbidDuplicateNames: true, 21 | }, 22 | 23 | // For security, listen only to localhost. You can also specify a different 24 | // hostname or IP address, or remove the property or set it to "*" to listen 25 | // to all network interfaces. 26 | listenAddress: "localhost", 27 | 28 | // The hostname that the browser will use to connect to the server. 29 | hostname: "localhost", 30 | 31 | browser: { 32 | name: "headlessChrome", 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /spec/support/mailers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec::Matchers.define :be_an_absolute_url do 4 | match do |actual| 5 | URI.parse(actual).host.present? 6 | end 7 | end 8 | 9 | shared_examples_for 'a mailer view' do 10 | def extract_body(mail) 11 | if mail.multipart? 12 | mail.parts.find do |p| 13 | p.media_type.starts_with?('text/html') 14 | end.body.encoded 15 | else 16 | mail.body.encoded 17 | end 18 | end 19 | 20 | it 'uses absolute URLs' do 21 | body = extract_body(mail) 22 | 23 | urls = Capybara.string(body).all('a').pluck(:href) 24 | 25 | expect(urls).to all(be_an_absolute_url) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/support/row_counting.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def assert_no_rows_present 4 | rows = {} 5 | total = 0 6 | ApplicationRecord.send(:subclasses).each do |sc| 7 | next if sc.name == 'ActiveRecord::SchemaMigration' 8 | 9 | rows[sc.name] = sc.all.size 10 | total += sc.all.size 11 | end 12 | return unless total > 0 13 | 14 | puts 'Leaked the following rows: ' 15 | rows.each do |klass, count| 16 | next unless count > 0 17 | 18 | puts "#{klass}: #{count}" 19 | end 20 | expect(total).to eq(0) 21 | end 22 | -------------------------------------------------------------------------------- /spec/support/sign_in_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SignInHelper 4 | def sign_in_as(user, options = {}) 5 | if options[:slowly] 6 | visit new_user_session_path 7 | within('#sign-in-page') do 8 | fill_in 'Email', with: user.email 9 | fill_in 'Password', with: user.password 10 | click_button 'Sign in' 11 | end 12 | expect(page).to have_content('Signed in successfully') 13 | else 14 | login_as user, scope: :user 15 | end 16 | end 17 | 18 | def sign_in_with_modal(user) 19 | expect(page).to have_selector('#sign_in_dialog') 20 | within '#sign_in_dialog' do 21 | fill_in 'Email', with: user.email 22 | fill_in 'Password', with: user.password 23 | click_button 'Sign in' 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/waiting_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def wait_for_condition 4 | Timeout.timeout(5) do 5 | loop do 6 | return if yield 7 | 8 | sleep 0.1 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/railsbridge/bridge_troll/3daa0091359ac1944b269cb3093fda0156112761/vendor/assets/stylesheets/.gitkeep --------------------------------------------------------------------------------