├── .env.example ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .mergify.yml ├── .rspec ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app.json ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── airtng-logo.png │ │ ├── spock.png │ │ └── tngbg.jpg │ ├── javascripts │ │ ├── application.js │ │ └── vacation_properties.coffee │ └── stylesheets │ │ ├── application.css │ │ ├── main.scss.erb │ │ ├── scaffolds.scss │ │ ├── users.scss │ │ └── vacation_properties.scss ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── main_controller.rb │ ├── reservations_controller.rb │ ├── sessions_controller.rb │ ├── users_controller.rb │ └── vacation_properties_controller.rb ├── helpers │ ├── application_helper.rb │ ├── main_helper.rb │ ├── users_helper.rb │ └── vacation_properties_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── reservation.rb │ ├── user.rb │ └── vacation_property.rb └── views │ ├── layouts │ └── application.html.erb │ ├── main │ └── index.html.erb │ ├── reservations │ └── index.html.erb │ ├── sessions │ └── login.html.erb │ ├── users │ ├── new.html.erb │ ├── show.html.erb │ └── show_verify.html.erb │ └── vacation_properties │ ├── _form.html.erb │ ├── _reservation_form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder ├── bin ├── bundle ├── rails ├── rake ├── setup └── spring ├── config.ru ├── config ├── application.rb ├── boot.rb ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── dotenv.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── permissions_policy.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── secrets.yml ├── spring.rb └── unicorn.rb ├── db ├── migrate │ ├── 20150219231715_create_users.rb │ ├── 20150220155337_adds_user_name_to_users.rb │ ├── 20150220161843_add_phone_number_and_country_code_to_users.rb │ ├── 20150624175446_create_vacation_properties.rb │ ├── 20150624223136_create_reservations.rb │ ├── 20150727233817_add_phone_number_to_reservations.rb │ └── 20150728042020_add_area_code_to_users.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico ├── landing.html └── robots.txt ├── spec ├── controllers │ ├── main_controller_spec.rb │ ├── reservations_controller_spec.rb │ ├── sessions_controller_spec.rb │ ├── users_controller_spec.rb │ └── vacation_properties_controller_spec.rb ├── fixtures │ ├── .keep │ ├── reservations.yml │ ├── users.yml │ └── vacation_properties.yml ├── models │ ├── .keep │ └── user_spec.rb ├── rails_helper.rb ├── spec_helper.rb ├── test_helper.rb └── vcr_cassettes │ ├── accept_or_reject_reservation.yml │ └── create_reservation.yml ├── tmp └── .keep └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.env.example: -------------------------------------------------------------------------------- 1 | # Your Twilio Account SID. Get a free account at twilio.com/try-twilio 2 | TWILIO_ACCOUNT_SID=your_twilio_account_SID 3 | # Your Twilio Auth Token. You can get it at twilio.com/console 4 | TWILIO_AUTH_TOKEN=your_twilio_auth_token 5 | # The Twilio phone number you want to use to send SMS. Get one in the Twilio Console 6 | TWILIO_NUMBER=your_twilio_phone_number 7 | # Application SID. Create a new one: https://www.twilio.com/console/voice/dev-tools/twiml-apps 8 | ANONYMOUS_APPLICATION_SID=your_application_sid 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: twilio-ruby 10 | versions: 11 | - 5.46.1 12 | - 5.47.0 13 | - 5.48.0 14 | - 5.49.0 15 | - 5.50.0 16 | - 5.51.0 17 | - dependency-name: bootsnap 18 | versions: 19 | - 1.7.2 20 | - 1.7.3 21 | - dependency-name: listen 22 | versions: 23 | - 3.5.0 24 | - dependency-name: rspec-rails 25 | versions: 26 | - 5.0.0 27 | - dependency-name: rails 28 | versions: 29 | - 4.2.11.1 30 | - 6.1.3 31 | - dependency-name: webmock 32 | versions: 33 | - 3.11.1 34 | - 3.11.2 35 | - 3.11.3 36 | - 3.12.0 37 | - 3.12.1 38 | - dependency-name: jquery-rails 39 | versions: 40 | - 4.3.4 41 | - dependency-name: uglifier 42 | versions: 43 | - 2.7.2 44 | - dependency-name: web-console 45 | versions: 46 | - 3.3.0 47 | - dependency-name: loofah 48 | versions: 49 | - 2.9.0 50 | - dependency-name: nokogiri 51 | versions: 52 | - 1.11.1 53 | - dependency-name: sqlite3 54 | versions: 55 | - 1.4.2 56 | - dependency-name: sprockets 57 | versions: 58 | - 3.7.2 59 | - dependency-name: i18n 60 | versions: 61 | - 0.9.5 62 | - dependency-name: rake 63 | versions: 64 | - 13.0.3 65 | - dependency-name: sanitize 66 | versions: 67 | - 5.2.1 68 | - dependency-name: byebug 69 | versions: 70 | - 11.1.3 71 | - dependency-name: rails-html-sanitizer 72 | versions: 73 | - 1.3.0 74 | - dependency-name: rack 75 | versions: 76 | - 1.6.13 77 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | - name: Install SQLite (Ubuntu) 20 | if: runner.os == 'Linux' 21 | run: sudo apt-get update && sudo apt-get install -yqq libsqlite3-dev 22 | - name: Install SQLite (OSX) 23 | if: runner.os == 'macOs' 24 | run: brew install sqlite3 25 | - name: Install SQLite (Windows) 26 | if: runner.os == 'Windows' 27 | uses: crazy-max/ghaction-chocolatey@v1 28 | with: 29 | args: install sqlite 30 | - name: Setup Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | bundler-cache: false 34 | ruby-version: '3.0' 35 | - name: Install SQLite gem (Windows) 36 | if: runner.os == 'Windows' 37 | run: gem install sqlite3 --platform=ruby -- --with-sqlite3-include=/c:/ProgramData/ProgramData/lib/SQLite/tools 38 | - name: Install gems 39 | run: | 40 | gem install bundler 41 | bundle config path vendor/bundle 42 | bundle install --jobs 4 --retry 3 43 | - name: Run tests 44 | run: | 45 | cp .env.example .env 46 | bundle exec rails db:migrate 47 | bundle exec rspec 48 | env: 49 | RAILS_ENV: test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-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 bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | /db/*.sqlite3-* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | 25 | /public/assets 26 | .byebug_history 27 | 28 | # Ignore master key for decrypting credentials and more. 29 | /config/master.key 30 | 31 | /public/packs 32 | /public/packs-test 33 | /node_modules 34 | /yarn-error.log 35 | yarn-debug.log* 36 | .yarn-integrity 37 | 38 | /vendor 39 | .env 40 | .env.test 41 | 42 | /.vscode 43 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge for Dependabot pull requests 3 | conditions: 4 | - author~=^dependabot(|-preview)\[bot\]$ 5 | - status-success=build 6 | actions: 7 | merge: 8 | method: squash 9 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at open-source@twilio.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Twilio 2 | 3 | All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under. 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby ' ~> 3.0' 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem 'rails', '~> 6.1.3' 8 | # Use postgresql as the database for Active Record 9 | gem 'sqlite3' 10 | # Use Puma as the app server 11 | gem 'puma', '~> 5.0' 12 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 13 | gem 'turbolinks', '~> 5' 14 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 15 | gem 'jbuilder', '~> 2.7' 16 | # Use Active Model has_secure_password 17 | gem 'bcrypt', '~> 3.1.7' 18 | 19 | gem 'uglifier' 20 | gem 'sass-rails' 21 | gem 'jquery-rails' 22 | 23 | # Reduces boot times through caching; required in config/boot.rb 24 | gem 'bootsnap', '>= 1.4.4', require: false 25 | 26 | group :development, :test do 27 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 28 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 29 | gem 'dotenv-rails' 30 | gem 'factory_bot_rails' 31 | gem "rspec-rails", "~> 4.0" 32 | end 33 | 34 | group :development do 35 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 36 | gem 'web-console', '>= 4.1.0' 37 | # Display performance information such as SQL time and flame graphs for each request in your browser. 38 | # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md 39 | gem 'rack-mini-profiler', '~> 2.0' 40 | gem 'listen', '~> 3.3' 41 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 42 | gem 'spring' 43 | end 44 | 45 | group :test do 46 | # Easy installation and use of web drivers to run system tests with browsers 47 | gem 'rails-controller-testing' 48 | gem 'vcr' 49 | gem 'webdrivers' 50 | gem 'webmock' 51 | end 52 | 53 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 54 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 55 | gem "twilio-ruby", "~> 5.46" 56 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.1.3.1) 5 | actionpack (= 6.1.3.1) 6 | activesupport (= 6.1.3.1) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | actionmailbox (6.1.3.1) 10 | actionpack (= 6.1.3.1) 11 | activejob (= 6.1.3.1) 12 | activerecord (= 6.1.3.1) 13 | activestorage (= 6.1.3.1) 14 | activesupport (= 6.1.3.1) 15 | mail (>= 2.7.1) 16 | actionmailer (6.1.3.1) 17 | actionpack (= 6.1.3.1) 18 | actionview (= 6.1.3.1) 19 | activejob (= 6.1.3.1) 20 | activesupport (= 6.1.3.1) 21 | mail (~> 2.5, >= 2.5.4) 22 | rails-dom-testing (~> 2.0) 23 | actionpack (6.1.3.1) 24 | actionview (= 6.1.3.1) 25 | activesupport (= 6.1.3.1) 26 | rack (~> 2.0, >= 2.0.9) 27 | rack-test (>= 0.6.3) 28 | rails-dom-testing (~> 2.0) 29 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 30 | actiontext (6.1.3.1) 31 | actionpack (= 6.1.3.1) 32 | activerecord (= 6.1.3.1) 33 | activestorage (= 6.1.3.1) 34 | activesupport (= 6.1.3.1) 35 | nokogiri (>= 1.8.5) 36 | actionview (6.1.3.1) 37 | activesupport (= 6.1.3.1) 38 | builder (~> 3.1) 39 | erubi (~> 1.4) 40 | rails-dom-testing (~> 2.0) 41 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 42 | activejob (6.1.3.1) 43 | activesupport (= 6.1.3.1) 44 | globalid (>= 0.3.6) 45 | activemodel (6.1.3.1) 46 | activesupport (= 6.1.3.1) 47 | activerecord (6.1.3.1) 48 | activemodel (= 6.1.3.1) 49 | activesupport (= 6.1.3.1) 50 | activestorage (6.1.3.1) 51 | actionpack (= 6.1.3.1) 52 | activejob (= 6.1.3.1) 53 | activerecord (= 6.1.3.1) 54 | activesupport (= 6.1.3.1) 55 | marcel (~> 1.0.0) 56 | mini_mime (~> 1.0.2) 57 | activesupport (6.1.3.1) 58 | concurrent-ruby (~> 1.0, >= 1.0.2) 59 | i18n (>= 1.6, < 2) 60 | minitest (>= 5.1) 61 | tzinfo (~> 2.0) 62 | zeitwerk (~> 2.3) 63 | addressable (2.7.0) 64 | public_suffix (>= 2.0.2, < 5.0) 65 | bcrypt (3.1.16) 66 | bindex (0.8.1) 67 | bootsnap (1.5.1) 68 | msgpack (~> 1.0) 69 | builder (3.2.4) 70 | byebug (11.1.3) 71 | childprocess (3.0.0) 72 | concurrent-ruby (1.1.8) 73 | crack (0.4.5) 74 | rexml 75 | crass (1.0.6) 76 | diff-lcs (1.4.4) 77 | dotenv (2.7.6) 78 | dotenv-rails (2.7.6) 79 | dotenv (= 2.7.6) 80 | railties (>= 3.2) 81 | erubi (1.10.0) 82 | execjs (2.7.0) 83 | factory_bot (6.1.0) 84 | activesupport (>= 5.0.0) 85 | factory_bot_rails (6.1.0) 86 | factory_bot (~> 6.1.0) 87 | railties (>= 5.0.0) 88 | faraday (1.3.0) 89 | faraday-net_http (~> 1.0) 90 | multipart-post (>= 1.2, < 3) 91 | ruby2_keywords 92 | faraday-net_http (1.0.1) 93 | ffi (1.14.2) 94 | ffi (1.14.2-x64-mingw32) 95 | globalid (0.4.2) 96 | activesupport (>= 4.2.0) 97 | hashdiff (1.0.1) 98 | i18n (1.8.9) 99 | concurrent-ruby (~> 1.0) 100 | jbuilder (2.10.1) 101 | activesupport (>= 5.0.0) 102 | jquery-rails (4.4.0) 103 | rails-dom-testing (>= 1, < 3) 104 | railties (>= 4.2.0) 105 | thor (>= 0.14, < 2.0) 106 | jwt (2.2.2) 107 | listen (3.4.1) 108 | rb-fsevent (~> 0.10, >= 0.10.3) 109 | rb-inotify (~> 0.9, >= 0.9.10) 110 | loofah (2.9.0) 111 | crass (~> 1.0.2) 112 | nokogiri (>= 1.5.9) 113 | mail (2.7.1) 114 | mini_mime (>= 0.1.1) 115 | marcel (1.0.0) 116 | method_source (1.0.0) 117 | mini_mime (1.0.3) 118 | mini_portile2 (2.5.0) 119 | minitest (5.14.4) 120 | msgpack (1.4.2) 121 | multipart-post (2.1.1) 122 | nio4r (2.5.7) 123 | nokogiri (1.11.2) 124 | mini_portile2 (~> 2.5.0) 125 | racc (~> 1.4) 126 | nokogiri (1.11.2-arm64-darwin) 127 | racc (~> 1.4) 128 | nokogiri (1.11.2-x64-mingw32) 129 | racc (~> 1.4) 130 | nokogiri (1.11.2-x86_64-linux) 131 | racc (~> 1.4) 132 | public_suffix (4.0.6) 133 | puma (5.1.1) 134 | nio4r (~> 2.0) 135 | racc (1.5.2) 136 | rack (2.2.3) 137 | rack-mini-profiler (2.3.0) 138 | rack (>= 1.2.0) 139 | rack-test (1.1.0) 140 | rack (>= 1.0, < 3) 141 | rails (6.1.3.1) 142 | actioncable (= 6.1.3.1) 143 | actionmailbox (= 6.1.3.1) 144 | actionmailer (= 6.1.3.1) 145 | actionpack (= 6.1.3.1) 146 | actiontext (= 6.1.3.1) 147 | actionview (= 6.1.3.1) 148 | activejob (= 6.1.3.1) 149 | activemodel (= 6.1.3.1) 150 | activerecord (= 6.1.3.1) 151 | activestorage (= 6.1.3.1) 152 | activesupport (= 6.1.3.1) 153 | bundler (>= 1.15.0) 154 | railties (= 6.1.3.1) 155 | sprockets-rails (>= 2.0.0) 156 | rails-controller-testing (1.0.5) 157 | actionpack (>= 5.0.1.rc1) 158 | actionview (>= 5.0.1.rc1) 159 | activesupport (>= 5.0.1.rc1) 160 | rails-dom-testing (2.0.3) 161 | activesupport (>= 4.2.0) 162 | nokogiri (>= 1.6) 163 | rails-html-sanitizer (1.3.0) 164 | loofah (~> 2.3) 165 | railties (6.1.3.1) 166 | actionpack (= 6.1.3.1) 167 | activesupport (= 6.1.3.1) 168 | method_source 169 | rake (>= 0.8.7) 170 | thor (~> 1.0) 171 | rake (13.0.3) 172 | rb-fsevent (0.10.4) 173 | rb-inotify (0.10.1) 174 | ffi (~> 1.0) 175 | rexml (3.2.4) 176 | rspec-core (3.10.1) 177 | rspec-support (~> 3.10.0) 178 | rspec-expectations (3.10.1) 179 | diff-lcs (>= 1.2.0, < 2.0) 180 | rspec-support (~> 3.10.0) 181 | rspec-mocks (3.10.2) 182 | diff-lcs (>= 1.2.0, < 2.0) 183 | rspec-support (~> 3.10.0) 184 | rspec-rails (4.0.2) 185 | actionpack (>= 4.2) 186 | activesupport (>= 4.2) 187 | railties (>= 4.2) 188 | rspec-core (~> 3.10) 189 | rspec-expectations (~> 3.10) 190 | rspec-mocks (~> 3.10) 191 | rspec-support (~> 3.10) 192 | rspec-support (3.10.2) 193 | ruby2_keywords (0.0.4) 194 | rubyzip (2.3.0) 195 | sass-rails (6.0.0) 196 | sassc-rails (~> 2.1, >= 2.1.1) 197 | sassc (2.4.0) 198 | ffi (~> 1.9) 199 | sassc (2.4.0-x64-mingw32) 200 | ffi (~> 1.9) 201 | sassc-rails (2.1.2) 202 | railties (>= 4.0.0) 203 | sassc (>= 2.0) 204 | sprockets (> 3.0) 205 | sprockets-rails 206 | tilt 207 | selenium-webdriver (3.142.7) 208 | childprocess (>= 0.5, < 4.0) 209 | rubyzip (>= 1.2.2) 210 | spring (2.1.1) 211 | sprockets (4.0.2) 212 | concurrent-ruby (~> 1.0) 213 | rack (> 1, < 3) 214 | sprockets-rails (3.2.2) 215 | actionpack (>= 4.0) 216 | activesupport (>= 4.0) 217 | sprockets (>= 3.0.0) 218 | sqlite3 (1.4.2) 219 | thor (1.1.0) 220 | tilt (2.0.10) 221 | turbolinks (5.2.1) 222 | turbolinks-source (~> 5.2) 223 | turbolinks-source (5.2.0) 224 | twilio-ruby (5.46.0) 225 | faraday (>= 0.9, < 2.0) 226 | jwt (>= 1.5, <= 2.5) 227 | nokogiri (>= 1.6, < 2.0) 228 | tzinfo (2.0.4) 229 | concurrent-ruby (~> 1.0) 230 | tzinfo-data (1.2020.6) 231 | tzinfo (>= 1.0.0) 232 | uglifier (4.2.0) 233 | execjs (>= 0.3.0, < 3) 234 | vcr (6.0.0) 235 | web-console (4.1.0) 236 | actionview (>= 6.0.0) 237 | activemodel (>= 6.0.0) 238 | bindex (>= 0.4.0) 239 | railties (>= 6.0.0) 240 | webdrivers (4.5.0) 241 | nokogiri (~> 1.6) 242 | rubyzip (>= 1.3.0) 243 | selenium-webdriver (>= 3.0, < 4.0) 244 | webmock (3.11.1) 245 | addressable (>= 2.3.6) 246 | crack (>= 0.3.2) 247 | hashdiff (>= 0.4.0, < 2.0.0) 248 | websocket-driver (0.7.3) 249 | websocket-extensions (>= 0.1.0) 250 | websocket-extensions (0.1.5) 251 | zeitwerk (2.4.2) 252 | 253 | PLATFORMS 254 | ruby 255 | universal-darwin-19 256 | x64-mingw32 257 | x86_64-linux 258 | 259 | DEPENDENCIES 260 | bcrypt (~> 3.1.7) 261 | bootsnap (>= 1.4.4) 262 | byebug 263 | dotenv-rails 264 | factory_bot_rails 265 | jbuilder (~> 2.7) 266 | jquery-rails 267 | listen (~> 3.3) 268 | puma (~> 5.0) 269 | rack-mini-profiler (~> 2.0) 270 | rails (~> 6.1.3) 271 | rails-controller-testing 272 | rspec-rails (~> 4.0) 273 | sass-rails 274 | spring 275 | sqlite3 276 | turbolinks (~> 5) 277 | twilio-ruby (~> 5.46) 278 | tzinfo-data 279 | uglifier 280 | vcr 281 | web-console (>= 4.1.0) 282 | webdrivers 283 | webmock 284 | 285 | RUBY VERSION 286 | ruby 3.0.0p0 287 | 288 | BUNDLED WITH 289 | 2.2.6 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Twilio Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Twilio 3 | 4 | 5 | # Airtng App: Part 2 - Anonymous Calling and SMS with Twilio 6 | 7 | ![](https://github.com/TwilioDevEd/anonymous-communications-rails/actions/workflows/build.yml/badge.svg) 8 | 9 | Protect your customers' privacy, and create a seamless interaction by provisioning Twilio numbers on the fly, and routing all voice calls, and messages through your very own 3rd party. This allows you to control the interaction between your customers, while putting your customer's privacy first. 10 | 11 | [Read the full tutorial here](https://www.twilio.com/docs/tutorials/walkthrough/masked-numbers/ruby/rails)! 12 | 13 | ## Local Development 14 | 15 | This project is built using [Ruby on Rails](http://rubyonrails.org/) Framework. 16 | 17 | 1. First clone this repository and `cd` into it. 18 | 19 | ```bash 20 | $ git clone git@github.com:TwilioDevEd/anonymous-communications-rails.git 21 | $ cd anonymous-communications-rails 22 | ``` 23 | 24 | 1. Install the dependencies. 25 | 26 | ```bash 27 | $ bundle install 28 | ``` 29 | 30 | 1. Expose your application to the wider internet using [ngrok](http://ngrok.com). This step 31 | is important because the application won't work as expected if you run it through 32 | localhost. 33 | 34 | ```bash 35 | $ ngrok http 3000 36 | ``` 37 | 38 | Your ngrok URL should look something like this: `http://9a159ccf.ngrok.io` 39 | 40 | You can read [this blog post](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html) 41 | for more details on how to use ngrok. 42 | 43 | 1. Configure Twilio App to call your webhooks. 44 | 45 | Before you can run this app you need to go into your account portal and [create a new Twilio Application](https://www.twilio.com/console/phone-numbers/runtime/twiml-apps). Once you have created an app the urls should look like: 46 | 47 | Voice: `https://.ngrok.io/reservations/connect_voice` 48 | 49 | SMS & MMS: `https://.ngrok.io/reservations/connect_sms` 50 | 51 | 1. Copy the sample configuration file and edit it to match your configuration. 52 | 53 | ```bash 54 | $ cp .env.example .env 55 | ``` 56 | 57 | You can find your `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` in your 58 | [Twilio Account Settings](https://www.twilio.com/console/account/settings). 59 | You will also need a `TWILIO_NUMBER`, which you may find [here](https://www.twilio.com/console/phone-numbers/incoming). 60 | 61 | 1. Create database and run migrations. Be sure to have [SQLite](https://www.sqlite.org/index.html) installed before running this command. 62 | 63 | ```bash 64 | $ bundle exec rails db:setup 65 | ``` 66 | 67 | 1. Make sure the tests succeed. 68 | 69 | ```bash 70 | $ bundle exec rspec 71 | ``` 72 | 73 | 1. Start the server. 74 | 75 | ```bash 76 | $ bundle exec rails s 77 | ``` 78 | 79 | That's it! 80 | 81 | ## Meta 82 | 83 | * No warranty expressed or implied. Software is as is. Diggity. 84 | * [MIT License](LICENSE) 85 | * Lovingly crafted by Twilio Developer Education. 86 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Airtng: Anonymous Communications", 3 | "description": "Anonymous Communications (Voice & SMS) with Twilio and Rails", 4 | "repository": "https://github.com/TwilioDevEd/anonymous-communications-rails", 5 | "success_url": "/landing.html", 6 | "keywords": [ 7 | "anonymous communications", "anonymous calling", "anonymous sms", "customer privacy", "privacy", "cloaked", "2-way anonymous" 8 | ], 9 | "addons": ["heroku-postgresql:hobby-dev"], 10 | "env": { 11 | "TWILIO_ACCOUNT_SID": { 12 | "description": "Your Twilio account secret ID, you can find at: https://www.twilio.com/user/account", 13 | "value": "enter_your_account_sid_here", 14 | "required": true 15 | }, 16 | "TWILIO_AUTH_TOKEN": { 17 | "description": "Your secret Twilio Auth token, you can find at: https://www.twilio.com/user/account", 18 | "value": "enter_your_auth_token_here", 19 | "required": true 20 | }, 21 | "TWILIO_NUMBER": { 22 | "description": "The Twilio phone number you are using for this app. You can get one here: https://www.twilio.com/user/account/phone-numbers/incoming", 23 | "value": "+15005550006", 24 | "required": true 25 | }, 26 | "ANONYMOUS_APPLICATION_SID": { 27 | "description": "The Twilio Application SID. You can get one here: https://www.twilio.com/user/account/apps", 28 | "value": "enter_your_account_sid_here", 29 | "required": true 30 | }, 31 | }, 32 | "scripts": { 33 | "postdeploy": "bundle exec rake db:create db:migrate" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/airtng-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/assets/images/airtng-logo.png -------------------------------------------------------------------------------- /app/assets/images/spock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/assets/images/spock.png -------------------------------------------------------------------------------- /app/assets/images/tngbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/assets/images/tngbg.jpg -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/vacation_properties.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | @import url(http://fonts.googleapis.com/css?family=Raleway:400,600,300); 17 | 18 | html { 19 | box-sizing:border-box; 20 | } 21 | 22 | body { 23 | font-family: 'Raleway', sans-serif; 24 | } 25 | 26 | *, *:before, *:after { 27 | box-sizing:inherit; 28 | } 29 | 30 | a:visited { 31 | color:white; 32 | } 33 | 34 | footer { 35 | font-size:12px; 36 | color:#787878; 37 | margin-top:20px; 38 | padding-top:20px; 39 | text-align:center; 40 | } 41 | 42 | footer i { 43 | color:#ff0000; 44 | } 45 | 46 | nav a:focus { 47 | text-decoration:none; 48 | color:#337ab7; 49 | } 50 | 51 | td { 52 | padding:10px; 53 | } 54 | 55 | #main { 56 | margin-top:10px; 57 | } 58 | 59 | #messages p { 60 | padding:10px; 61 | border:1px solid #eee; 62 | } 63 | 64 | #messages i { 65 | float:right; 66 | margin-left:5px; 67 | cursor:pointer; 68 | } 69 | 70 | #countries-input-0{ 71 | display: block; 72 | width: 100%; 73 | height: 34px; 74 | padding: 6px 12px; 75 | font-size: 14px; 76 | line-height: 1.42857; 77 | color: #555; 78 | background-color: #FFF; 79 | background-image: none; 80 | border: 1px solid #CCC; 81 | border-radius: 4px; 82 | box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset; 83 | transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s; 84 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/main.scss.erb: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the main controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | /* Index page */ 5 | body.cover { 6 | footer { 7 | text-align: center; 8 | position: absolute; 9 | bottom: 20px; 10 | width: 100%; 11 | } 12 | } 13 | .hero-text { 14 | background: url(<%= asset_path('tngbg.jpg') %>); 15 | margin: 0px; 16 | top: 0px; 17 | text-align: center; 18 | padding: 130px; 19 | color: white; 20 | background-size: cover; 21 | &.full-page { 22 | height: 100%; 23 | position: absolute; 24 | width: 100%; 25 | } 26 | h1 { 27 | font-size:60px; 28 | text-transform: uppercase; 29 | } 30 | p { 31 | font-size:30px; 32 | } 33 | .btn-transparent { 34 | background:rgba(255,255,255,0.6); 35 | margin: 30px; 36 | color:white; 37 | font-weight: 600; 38 | letter-spacing: 1px; 39 | } 40 | } 41 | 42 | .navbar { 43 | border-radius:0px !important; 44 | &.navbar-transparent { 45 | background:transparent; 46 | position: absolute; 47 | width: 100%; 48 | top:0px; 49 | z-index: 1020; 50 | padding: 20px; 51 | } 52 | &.navbar-space { 53 | background:url(<%= asset_path('tngbg.jpg') %>); 54 | height:90px; 55 | } 56 | .navbar-brand { 57 | background:url(<%= asset_path('airtng-logo.png') %>) no-repeat; 58 | padding-left:40px; 59 | color:white; 60 | font-size:32px; 61 | } 62 | img { 63 | width: 20px; 64 | } 65 | li { 66 | list-style: none; 67 | margin: 10px; 68 | a { 69 | color: #337ab7 !important; 70 | font-size:14px; 71 | &:hover { 72 | background:transparent; 73 | text-decoration: none; 74 | } 75 | &:visited { 76 | color: #337ab7 !important; 77 | } 78 | } 79 | } 80 | } 81 | 82 | #main.push-hero { 83 | margin-top: 30px; 84 | } 85 | 86 | #main.push-nav { 87 | margin-top: 60px; 88 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | p, ol, ul, td { 10 | font-family: verdana, arial, helvetica, sans-serif; 11 | font-size: 13px; 12 | line-height: 18px; 13 | } 14 | 15 | pre { 16 | background-color: #eee; 17 | padding: 10px; 18 | font-size: 11px; 19 | } 20 | 21 | a { 22 | color: #337ab7; 23 | &:visited { 24 | color: darken(#337ab7, 10%); 25 | } 26 | &:hover { 27 | color: darken(#337ab7, 10%); 28 | } 29 | } 30 | 31 | div { 32 | &.field, &.actions { 33 | margin-bottom: 10px; 34 | } 35 | } 36 | 37 | #notice { 38 | color: green; 39 | } 40 | 41 | .field_with_errors { 42 | padding: 2px; 43 | background-color: red; 44 | display: table; 45 | } 46 | .flash-above { 47 | position: absolute; 48 | width:100%; 49 | z-index: 1010; 50 | padding: 20px; 51 | top: 80px; 52 | } 53 | 54 | #error_explanation { 55 | width: 450px; 56 | border: 2px solid red; 57 | padding: 7px; 58 | padding-bottom: 0; 59 | margin-bottom: 20px; 60 | background-color: #f0f0f0; 61 | h2 { 62 | text-align: left; 63 | font-weight: bold; 64 | padding: 5px 5px 5px 15px; 65 | font-size: 12px; 66 | margin: -7px; 67 | margin-bottom: 0px; 68 | background-color: #c00; 69 | color: #fff; 70 | } 71 | ul li { 72 | font-size: 12px; 73 | list-style: square; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/vacation_properties.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the VacationProperties controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | body.property-page { 6 | footer { 7 | position:relative; 8 | } 9 | #main { 10 | min-height: 900px; 11 | } 12 | } 13 | 14 | .property { 15 | display: block; 16 | position: relative; 17 | border-radius:10px; 18 | text-align: center; 19 | padding-top: 88px; 20 | height: 240px; 21 | text-transform: uppercase; 22 | overflow:hidden; 23 | margin-bottom:20px; 24 | img { 25 | vertical-align: middle; 26 | width: 100%; 27 | position: absolute; 28 | left:0px; 29 | top:0px; 30 | border-radius:10px; 31 | } 32 | h2 { 33 | color:white; 34 | z-index: 1000; 35 | position: relative; 36 | } 37 | } 38 | 39 | .property-detail { 40 | position: absolute; 41 | top: 0px; 42 | width: 100%; 43 | .overview { 44 | height: 400px; 45 | overflow: hidden; 46 | 47 | } 48 | img { 49 | width:100%; 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :null_session 5 | 6 | def current_user 7 | User.find_by(id: session[:user_id]) 8 | end 9 | helper_method :current_user 10 | 11 | def signed_in? 12 | current_user.present? 13 | end 14 | helper_method :signed_in? 15 | 16 | protected 17 | 18 | def authenticate_user 19 | unless session[:user_id] 20 | redirect_to(controller: 'sessions', action: 'login') 21 | flash[:notice] = "You must be logged in to view that page." 22 | return false 23 | else 24 | @user = User.find session[:user_id] 25 | return true 26 | end 27 | end 28 | 29 | def save_login_state 30 | if session[:user_id] 31 | redirect_to home_path 32 | return false 33 | else 34 | return true 35 | end 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/main_controller.rb: -------------------------------------------------------------------------------- 1 | class MainController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/reservations_controller.rb: -------------------------------------------------------------------------------- 1 | class ReservationsController < ApplicationController 2 | skip_before_action :verify_authenticity_token, only: [:accept_or_reject, :connect_guest_to_host_sms, :connect_guest_to_host_voice] 3 | before_action :set_twilio_params, only: [:connect_guest_to_host_sms, :connect_guest_to_host_voice] 4 | before_action :authenticate_user, only: [:index] 5 | 6 | # GET /reservations 7 | def index 8 | @reservations = current_user.reservations.all 9 | end 10 | 11 | # GET /reservations/new 12 | def new 13 | @reservation = Reservation.new 14 | end 15 | 16 | def create 17 | @vacation_property = VacationProperty.find(params[:reservation][:property_id]) 18 | @reservation = @vacation_property.reservations.create(reservation_params) 19 | 20 | if @reservation.save 21 | flash[:notice] = "Sending your reservation request now." 22 | @reservation.host.check_for_reservations_pending 23 | else 24 | flash[:error] = @reservation.errors.full_messages.to_sentence 25 | end 26 | redirect_to @vacation_property 27 | end 28 | 29 | # webhook for twilio incoming message from host 30 | def accept_or_reject 31 | incoming = params[:From] 32 | sms_input = params[:Body].downcase 33 | begin 34 | @host = User.find_by(phone_number: incoming) 35 | @reservation = @host.pending_reservation 36 | if sms_input == "accept" || sms_input == "yes" 37 | @reservation.confirm! 38 | else 39 | @reservation.reject! 40 | end 41 | 42 | @host.check_for_reservations_pending 43 | 44 | sms_reponse = "You have successfully #{@reservation.status} the reservation." 45 | respond(sms_reponse) 46 | rescue Exception => e 47 | puts "ERROR: #{e.message}" 48 | sms_reponse = "Sorry, it looks like you don't have any reservations to respond to." 49 | respond(sms_reponse) 50 | end 51 | end 52 | 53 | # webhook for twilio to anonymously connect the two parties 54 | def connect_guest_to_host_sms 55 | # Guest -> Host 56 | if @reservation.guest.phone_number == @incoming_phone 57 | @outgoing_number = @reservation.host.phone_number 58 | 59 | # Host -> Guest 60 | elsif @reservation.host.phone_number == @incoming_phone 61 | @outgoing_number = @reservation.guest.phone_number 62 | end 63 | 64 | response = Twilio::TwiML::MessagingResponse.new 65 | response.message(:body => @message, :to => @outgoing_number) 66 | render plain: response.to_s 67 | end 68 | 69 | # webhook for twilio -> TwiML for voice calls 70 | def connect_guest_to_host_voice 71 | # Guest -> Host 72 | if @reservation.guest.phone_number == @incoming_phone 73 | @outgoing_number = @reservation.host.phone_number 74 | 75 | # Host -> Guest 76 | elsif @reservation.host.phone_number == @incoming_phone 77 | @outgoing_number = @reservation.guest.phone_number 78 | end 79 | response = Twilio::TwiML::VoiceResponse.new 80 | response.play(url: "http://howtodocs.s3.amazonaws.com/howdy-tng.mp3") 81 | response.dial(number: @outgoing_number) 82 | 83 | render plain: response.to_s 84 | end 85 | 86 | 87 | private 88 | # Send an SMS back to the Subscriber 89 | def respond(message) 90 | response = Twilio::TwiML::MessagingResponse.new 91 | response.message(body: message) 92 | 93 | render plain: response.to_s 94 | end 95 | 96 | # Never trust parameters from the scary internet, only allow the white list through. 97 | def reservation_params 98 | params.require(:reservation).permit(:name, :guest_phone, :message) 99 | end 100 | 101 | # Load up Twilio parameters 102 | def set_twilio_params 103 | @incoming_phone = params[:From] 104 | @message = params[:Body] 105 | anonymous_phone_number = params[:To] 106 | @reservation = Reservation.where(phone_number: anonymous_phone_number).first 107 | end 108 | 109 | end 110 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | before_action :authenticate_user, :except => [:login, :login_attempt] 3 | before_action :save_login_state, :only => [:login, :login_attempt] 4 | 5 | def login 6 | #Login Form 7 | end 8 | 9 | def login_attempt 10 | user = User.find_by_email(params[:email]) 11 | if user && user.authenticate(params[:login_password]) 12 | session[:user_id] = user.id 13 | flash[:notice] = "Welcome again, you logged in as #{user.email}" 14 | redirect_to home_path 15 | 16 | else 17 | flash[:notice] = "Invalid Username or Password" 18 | render "login" 19 | end 20 | end 21 | 22 | def logout 23 | session[:user_id] = nil 24 | redirect_to :action => 'login' 25 | end 26 | 27 | end -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | def new 3 | @user = User.new 4 | end 5 | 6 | def show 7 | @user = current_user 8 | end 9 | 10 | def create 11 | @user = User.create(user_params) 12 | 13 | if @user.valid? 14 | # Save the user_id to the session object 15 | session[:user_id] = @user.id 16 | redirect_to home_path 17 | else 18 | render :new 19 | flash.now[:danger] = @user.errors.full_messages 20 | end 21 | end 22 | 23 | private 24 | 25 | def user_params 26 | params.require(:user).permit( 27 | :email, :password, :name, :country_code, :area_code, :phone_number 28 | ) 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /app/controllers/vacation_properties_controller.rb: -------------------------------------------------------------------------------- 1 | class VacationPropertiesController < ApplicationController 2 | before_action :authenticate_user, except: [:index] 3 | before_action :set_vacation_property_and_user, only: [:show, :edit, :update, :destroy] 4 | 5 | # GET /vacation_properties 6 | # GET /vacation_properties.json 7 | def index 8 | @vacation_properties = VacationProperty.all 9 | end 10 | 11 | # GET /vacation_properties/1 12 | # GET /vacation_properties/1.json 13 | def show 14 | @reservation = Reservation.new 15 | begin 16 | @current_reservation = Reservation.where( 17 | vacation_property_id: @vacation_property.id, 18 | guest_phone: @user.phone_number, 19 | status: 1) 20 | .first 21 | rescue Exception => e 22 | puts "e.message" 23 | end 24 | end 25 | 26 | # GET /vacation_properties/new 27 | def new 28 | @vacation_property = VacationProperty.new 29 | end 30 | 31 | # GET /vacation_properties/1/edit 32 | def edit 33 | end 34 | 35 | def create 36 | @user = current_user 37 | @vacation_property = @user.vacation_properties.create(vacation_property_params) 38 | 39 | respond_to do |format| 40 | if @vacation_property.save 41 | format.html { redirect_to @vacation_property, notice: 'Vacation property was successfully created.' } 42 | format.json { render :show, status: :created, location: @vacation_property } 43 | else 44 | format.html { render :new } 45 | format.json { render json: @vacation_property.errors, status: :unprocessable_entity } 46 | end 47 | end 48 | end 49 | 50 | def update 51 | respond_to do |format| 52 | if @vacation_property.update(vacation_property_params) 53 | format.html { redirect_to @vacation_property, notice: 'Vacation property was successfully updated.' } 54 | format.json { render :show, status: :ok, location: @vacation_property } 55 | else 56 | format.html { render :edit } 57 | format.json { render json: @vacation_property.errors, status: :unprocessable_entity } 58 | end 59 | end 60 | end 61 | 62 | def destroy 63 | @vacation_property.destroy 64 | respond_to do |format| 65 | format.html { redirect_to vacation_properties_url, notice: 'Vacation property was successfully destroyed.' } 66 | format.json { head :no_content } 67 | end 68 | end 69 | 70 | private 71 | # Use callbacks to share common setup or constraints between actions. 72 | def set_vacation_property_and_user 73 | @user = current_user 74 | @vacation_property = VacationProperty.find(params[:id]) 75 | end 76 | 77 | # Never trust parameters from the scary internet, only allow the white list through. 78 | def vacation_property_params 79 | params.require(:vacation_property).permit(:description, :image_url) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def bootstrap_class_for flash_type 3 | { success: "alert-success", error: "alert-danger", alert: "alert-warning", notice: "alert-info" }[flash_type.to_sym] || flash_type.to_s 4 | end 5 | 6 | def flash_messages(opts = {}) 7 | flash.each do |msg_type, message| 8 | concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)}", role: 'alert') do 9 | message 10 | end) 11 | end 12 | nil 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/helpers/main_helper.rb: -------------------------------------------------------------------------------- 1 | module MainHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/vacation_properties_helper.rb: -------------------------------------------------------------------------------- 1 | module VacationPropertiesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/models/.keep -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/reservation.rb: -------------------------------------------------------------------------------- 1 | class Reservation < ActiveRecord::Base 2 | validates :name, presence: true 3 | validates :guest_phone, presence: true 4 | 5 | enum status: [ :pending, :confirmed, :rejected ] 6 | 7 | belongs_to :vacation_property 8 | belongs_to :user, optional: true 9 | 10 | def notify_host(force = false) 11 | # Don't send the message if we have more than one and we aren't being forced 12 | if self.host.pending_reservations.length > 1 and !force 13 | return 14 | else 15 | message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}: 16 | 17 | '#{self.message}' 18 | 19 | Reply [accept] or [reject]." 20 | 21 | self.host.send_message_via_sms(message) 22 | end 23 | end 24 | 25 | def host 26 | @host = User.find(self.vacation_property[:user_id]) 27 | end 28 | 29 | def guest 30 | @guest = User.find_by(phone_number: self.guest_phone) 31 | end 32 | 33 | def confirm! 34 | provision_phone_number 35 | self.update!(status: 1) 36 | end 37 | 38 | def reject! 39 | self.update!(status: 0) 40 | end 41 | 42 | def notify_guest 43 | if self.status_changed? && (self.status == :confirmed || self.status == :rejected) 44 | message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}." 45 | self.guest.send_message_via_sms(message) 46 | end 47 | end 48 | 49 | def send_message_to_guest(message) 50 | message = "From #{self.host.name}: #{message}" 51 | self.guest.send_message_via_sms(message, self.phone_number) 52 | end 53 | 54 | def send_message_to_host(message) 55 | message = "From guest #{self.guest.name}: #{message}" 56 | self.host.send_message_via_sms(message, self.phone_number) 57 | end 58 | 59 | private 60 | 61 | def provision_phone_number 62 | @client = Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']) 63 | begin 64 | # Lookup numbers in host area code, if none than lookup from anywhere 65 | @numbers = @client.api.available_phone_numbers('US').local.list(area_code: self.host.area_code) 66 | if @numbers.empty? 67 | @numbers = @client.api.available_phone_numbers('US').local.list() 68 | end 69 | 70 | # Purchase the number & set the application_sid for voice and sms, will 71 | # tell the number where to route calls/sms 72 | @number = @numbers.first.phone_number 73 | @client.api.incoming_phone_numbers.create( 74 | phone_number: @number, 75 | voice_application_sid: ENV['ANONYMOUS_APPLICATION_SID'], 76 | sms_application_sid: ENV['ANONYMOUS_APPLICATION_SID'] 77 | ) 78 | 79 | # Set the reservation.phone_number 80 | self.update!(phone_number: @number) 81 | 82 | rescue Exception => e 83 | puts "ERROR: #{e.message}" 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | has_secure_password 3 | 4 | validates :email, presence: true, format: { with: /\A.+@.+$\Z/ }, uniqueness: true 5 | validates :name, presence: true 6 | validates :country_code, presence: true 7 | validates :phone_number, presence: true, uniqueness: true 8 | validates_length_of :password, in: 6..20, on: :create 9 | 10 | has_many :vacation_properties 11 | has_many :reservations, through: :vacation_properties 12 | 13 | after_create :save_join_phone_number! 14 | 15 | def send_message_via_sms(message, from_number = ENV['TWILIO_NUMBER']) 16 | @client = Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']) 17 | @client.messages.create( 18 | from: from_number, 19 | to: "#{self.phone_number}", 20 | body: message, 21 | ) 22 | end 23 | 24 | def check_for_reservations_pending 25 | if pending_reservation 26 | pending_reservation.notify_host(true) 27 | end 28 | end 29 | 30 | def pending_reservation 31 | self.reservations.pending.first 32 | end 33 | 34 | def pending_reservations 35 | self.reservations.pending 36 | end 37 | 38 | private 39 | 40 | # No reason to save phone number without the area_code or country_code, it's what twilio & ActiveRecord expect 41 | def save_join_phone_number! 42 | self.update!(phone_number: "#{self.country_code}#{self.area_code}#{self.phone_number}") 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /app/models/vacation_property.rb: -------------------------------------------------------------------------------- 1 | class VacationProperty < ActiveRecord::Base 2 | belongs_to :user # host 3 | has_many :reservations 4 | has_many :users, through: :reservations #guests 5 | end 6 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Airtng - The Next Generation of Vacation rentals 5 | <%= stylesheet_link_tag '//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= stylesheet_link_tag '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css', media: 'all', 'data-turbolinks-track' => true %> 7 | <%= stylesheet_link_tag '//cdnjs.cloudflare.com/ajax/libs/authy-forms.css/2.2/form.authy.min.css', media: 'all', 'data-turbolinks-track' => true %> 8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 9 | <%= javascript_include_tag '//cdnjs.cloudflare.com/ajax/libs/authy-forms.js/2.2/form.authy.min.js', 'data-turbolinks-track' => true %> 10 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 11 | <%= csrf_meta_tags %> 12 | 13 | 14 | <%= yield :nav_bg %> 15 | 16 | 33 | 34 | <%= yield :hero %> 35 |
36 |
<%= flash_messages %>
37 | <%= yield %> 38 |
39 | 40 |
41 | Made with by your pals 42 | @twilio 43 |
44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/views/main/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :body_class, "cover" %> 2 | <% content_for :hero do %> 3 |
4 |

Lodging fit for a captain

5 |

The Next Generation of vacation rentals.

6 | 7 | View Properties 8 |
9 | <% end %> -------------------------------------------------------------------------------- /app/views/reservations/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :hero do %> 2 |
3 |

Lodging fit for a captain

4 |

The Next Generation of vacation rentals.

5 |
6 | <% end %> 7 |

<%= notice %>

8 |
9 |
10 | <% if @reservations.empty? %> 11 |

You are not hosting any reservations.

12 | <% else %> 13 |

Your current reservations as a Host.

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | <% @reservations.each do |reservation| %> 25 | 26 | 27 | 28 | 29 | 30 | 31 | <% end %> 32 | 33 |
#Guest NamePhone NumberMessage
<%= reservation.id %><%= reservation.name %><%= reservation.phone_number %><%= reservation.message %>
34 | <% end %> 35 |
36 |
37 | -------------------------------------------------------------------------------- /app/views/sessions/login.html.erb: -------------------------------------------------------------------------------- 1 | <% @page_title = "UserAuth | Login" -%> 2 | <% content_for :nav_bg do %> 3 | 4 | 6 | <% end %> 7 |
8 | 22 |
23 | -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :nav_bg do %> 2 | 3 | 5 | <% end %> 6 |
7 |

Sign up for Airtng

8 | <% if @user.errors.any? %> 9 |

Oops, something went wrong!

10 | 11 |
    12 | <% @user.errors.full_messages.each do |error| %> 13 |
  • <%= error %>
  • 14 | <% end -%> 15 |
16 | <% end -%> 17 | 18 | <%= form_for @user do |f| %> 19 |
20 | <%= f.label :name, "Tell us your name:" %> 21 | <%= f.text_field :name, class: "form-control", placeholder: "Zingelbert Bembledack" %> 22 |
23 |
24 | <%= f.label :email, "Enter Your E-mail Address:" %> 25 | <%= f.email_field :email, class: "form-control", placeholder: "me@mydomain.com" %> 26 |
27 |
28 | <%= f.label :password, "Enter a password:" %> 29 | <%= f.password_field :password, class: "form-control" %> 30 |
31 |
32 |
33 | <%= f.label :phone_number %> 34 | <%= f.text_field :country_code, maxlength: "3", size: "3", value: "+1", class: "form-control fields" %> 35 | <%= f.text_field :area_code, maxlength: "3", size: "3", placeholder: "555", class: "form-control fields" %> 36 | <%= f.text_field :phone_number, maxlength: "7", size: "7", placeholder: "8675309", class: "form-control fields" %> 37 |
38 |
39 | 40 | <% end -%> 41 |
-------------------------------------------------------------------------------- /app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @user.name %>

2 |

Account Status: <% if @user.verified? %>Verified<% else -%>Not Verified<% end -%> 3 | <% if !@user.verified? %> 4 |

5 | <%= link_to "Verify your account now.", verify_path %> 6 |

7 | <% end -%> -------------------------------------------------------------------------------- /app/views/users/show_verify.html.erb: -------------------------------------------------------------------------------- 1 |

Just To Be Safe...

2 |

3 | Your account has been created, but we need to make sure you're a human 4 | in control of the phone number you gave us. Can you please enter the 5 | verification code we just sent to your phone? 6 |

7 | <%= form_tag users_verify_path do %> 8 |
9 | <%= label_tag :code, "Verification Code:" %> 10 | <%= text_field_tag :token, '', class: "form-control" %> 11 |
12 | 13 | <% end -%> 14 | 15 |
16 | <%= form_tag users_resend_path do %> 17 | 18 | <% end -%> 19 | -------------------------------------------------------------------------------- /app/views/vacation_properties/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@vacation_property) do |f| %> 2 | <% if @vacation_property.errors.any? %> 3 |
4 |

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

5 | 6 |
    7 | <% @vacation_property.errors.full_messages.each do |message| %> 8 |
  • <%= message %>
  • 9 | <% end %> 10 |
11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :description %>
16 | <%= f.text_field :description, class: "form-control" %> 17 |
18 |
19 | <%= f.label :image_url %>
20 | <%= f.text_field :image_url, class: "form-control" %> 21 |
22 |
23 | <%= f.submit 'Create Property', class: 'btn btn-lg btn-primary' %> 24 |
25 | <% end %> 26 | -------------------------------------------------------------------------------- /app/views/vacation_properties/_reservation_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @reservation do |f| %> 2 |
3 | <%= f.label :message, "Would you like to say anything to the host?" %> 4 | <%= f.text_area :message, size: "10x10", class: "form-control", placeholder: "Hello! I am hoping to stay in your intergalactic suite..." %> 5 |
6 | 9 | 12 | 15 | 16 | <% end -%> -------------------------------------------------------------------------------- /app/views/vacation_properties/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing Vacation Property

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @vacation_property %> | 6 | <%= link_to 'Back', vacation_properties_path %> 7 | -------------------------------------------------------------------------------- /app/views/vacation_properties/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :hero do %> 2 |
3 |

Lodging fit for a captain

4 |

The Next Generation of vacation rentals.

5 |
6 | <% end %> 7 |

<%= notice %>

8 |
9 |
10 | <% @vacation_properties.each do |vacation_property| %> 11 | 18 | <% end %> 19 |
20 |
21 | -------------------------------------------------------------------------------- /app/views/vacation_properties/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@vacation_properties) do |vacation_property| 2 | json.extract! vacation_property, :id, :description, :image_url 3 | json.url vacation_property_url(vacation_property, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /app/views/vacation_properties/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :nav_bg do %> 2 | 3 | 5 | <% end %> 6 |
7 |

New Vacation Property

8 | 9 | <%= render 'form' %> 10 | 11 | <%= link_to 'Back', vacation_properties_path %> 12 |
13 | -------------------------------------------------------------------------------- /app/views/vacation_properties/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :body_class, "property-page" %> 2 |

<%= notice %>

3 |
4 |
5 | 6 |
7 |
8 |

<%= @vacation_property.description %>

<%= link_to 'Edit', edit_vacation_property_path(@vacation_property) %> 9 |
10 | <% if @current_reservation %> 11 |
12 |
13 |

Your upcoming reservation details.

14 |
15 |
16 |
  • Status: <%= @current_reservation.status %>
  • 17 |
  • Host Name: <%= @current_reservation.host.name %>
  • 18 | 19 | <% if @current_reservation.phone_number %> 20 |
  • Contact Phone Number: <%= @current_reservation.phone_number %>
  • 21 | <% end %> 22 |
    23 |
    24 |
  • <%= @current_reservation.message %>
  • 25 | <% else %> 26 |
    27 |
    28 |

    Make a Reservation

    29 |
    30 |
    31 | <%= render 'reservation_form' %> 32 |
    33 |
    34 | <% end %> 35 |
    36 |
    37 | -------------------------------------------------------------------------------- /app/views/vacation_properties/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @vacation_property, :id, :description, :image_url, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | load File.expand_path("spring", __dir__) 3 | APP_PATH = File.expand_path('../config/application', __dir__) 4 | require_relative "../config/boot" 5 | require "rails/commands" 6 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | load File.expand_path("spring", __dir__) 3 | require_relative "../config/boot" 4 | require "rake" 5 | Rake.application.run 6 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup 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 21 | system! 'bin/yarn' 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system! 'bin/rails log:clear tmp:clear' 25 | 26 | puts "\n== Restarting application server ==" 27 | system! 'bin/rails restart' 28 | end 29 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) 3 | gem "bundler" 4 | require "bundler" 5 | 6 | # Load Spring without loading other gems in the Gemfile, for speed. 7 | Bundler.locked_gems.specs.find { |spec| spec.name == "spring" }&.tap do |spring| 8 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 9 | gem "spring", spring.version 10 | require "spring/binstub" 11 | rescue Gem::LoadError 12 | # Ignore when Spring is not installed. 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | # require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "sprockets/railtie" 16 | require "rails/test_unit/railtie" 17 | 18 | # Require the gems listed in Gemfile, including any gems 19 | # you've limited to :test, :development, or :production. 20 | Bundler.require(*Rails.groups) 21 | 22 | module EtaNotificationsRails 23 | class Application < Rails::Application 24 | # Initialize configuration defaults for originally generated Rails version. 25 | config.load_defaults 6.1 26 | 27 | # Configuration for the application, engines, and railties goes here. 28 | # 29 | # These settings can be overridden in specific environments using the files 30 | # in config/environments, which are processed later. 31 | # 32 | # config.time_zone = "Central Time (US & Canada)" 33 | # config.eager_load_paths << Rails.root.join("extras") 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | H6mjhEwPEcDXfPhGXKSIIBaKVAReL3dt90szBW4IFxae7af296vfC83tf/rCU9p8F5KQmTzVCeSxHci12LYffsaORzExGpiD4dzbwECMP2XYq4ecZPmvUHYQHc+o32YpUs45lI7FPyHTd+83RxpTbWUWtkYFiVfjH/7ZouqS5W7XmBKs/yUZbQwv64fUgLUCI9Y9G3Tp4Q+0oVRCj8DG2/9WOgNdGmDtDouQ8ZNh+kNZGawh78EeRoiTWtQdnWFeZe2tciXIaKqaUZFlIqDK1bAaGba3ivk62Nw0Bw+MuHcb0lB3fBR1nxnaT1kCzW+NAuVkWWEtLKb0X8yFqi98Nto3wQECjjWUdrOgENSUMWocUxLHGo8iAKkJZ1hA2S6GsbR72qC8JukULLNmHjAqZLiiGLahXYxHfc3l--Uj+qoSoxXjWqNTuh--GIFbbasSAxfMMtpIBn2o1w== 2 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: sqlite3 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: 5 23 | 24 | development: 25 | <<: *default 26 | database: db/airtng_development.sqlite3 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: two_factor_authy 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: db/airtng_test.sqlite3 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: db/airtng_production.sqlite3 84 | username: airtng 85 | password: <%= ENV['AIRTNG_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 20 | config.action_controller.perform_caching = true 21 | config.action_controller.enable_fragment_cache_logging = true 22 | 23 | config.cache_store = :memory_store 24 | config.public_file_server.headers = { 25 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 26 | } 27 | else 28 | config.action_controller.perform_caching = false 29 | 30 | config.cache_store = :null_store 31 | end 32 | 33 | # Allow ngrok urls 34 | config.hosts << /[a-z0-9]+\.ngrok\.io/ 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raise an error on page load if there are pending migrations. 46 | config.active_record.migration_error = :page_load 47 | 48 | # Highlight code that triggered database queries in logs. 49 | config.active_record.verbose_query_logs = true 50 | 51 | # Debug mode disables concatenation and preprocessing of assets. 52 | # This option may cause significant delays in view rendering with a large 53 | # number of complex assets. 54 | config.assets.debug = true 55 | 56 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 57 | # yet still be able to expire them through the digest params. 58 | config.assets.digest = true 59 | 60 | # Adds additional error checking when serving assets at runtime. 61 | # Checks for improperly declared sprockets dependencies. 62 | # Raises helpful error messages. 63 | config.assets.raise_runtime_errors = true 64 | 65 | config.assets.check_precompiled_asset = false 66 | 67 | # Raises error for missing translations. 68 | # config.i18n.raise_on_missing_translations = true 69 | 70 | # Annotate rendered view with file names. 71 | # config.action_view.annotate_rendered_view_with_filenames = true 72 | 73 | # Use an evented file watcher to asynchronously detect changes in source code, 74 | # routes, locales, etc. This feature depends on the listen gem. 75 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 76 | 77 | Rails.logger = Logger.new(STDOUT) 78 | Rails.logger.datetime_format = '%Y-%m-%d %H:%M:%S' 79 | 80 | # log formatter 81 | Rails.logger.formatter = proc do |severity, datetime, progname, msg| 82 | "#{datetime}, #{severity}: #{progname} #{msg} \n" 83 | end 84 | config.logger = ActiveSupport::Logger.new("log/#{Rails.env}.log") 85 | end 86 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 28 | # config.asset_host = 'http://assets.example.com' 29 | 30 | # Specifies the header that your server uses for sending files. 31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 33 | 34 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 35 | # config.force_ssl = true 36 | 37 | # Include generic and useful information about system operation, but avoid logging too much 38 | # information to avoid inadvertent exposure of personally identifiable information (PII). 39 | config.log_level = :info 40 | 41 | # Prepend all log lines with the following tags. 42 | config.log_tags = [ :request_id ] 43 | 44 | # Use a different cache store in production. 45 | # config.cache_store = :mem_cache_store 46 | 47 | # Use a real queuing backend for Active Job (and separate queues per environment). 48 | # config.active_job.queue_adapter = :resque 49 | # config.active_job.queue_name_prefix = "eta_notifications_rails_production" 50 | 51 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 52 | # the I18n.default_locale when a translation cannot be found). 53 | config.i18n.fallbacks = true 54 | 55 | # Send deprecation notices to registered listeners. 56 | config.active_support.deprecation = :notify 57 | 58 | # Log disallowed deprecations. 59 | config.active_support.disallowed_deprecation = :log 60 | 61 | # Tell Active Support which deprecation messages to disallow. 62 | config.active_support.disallowed_deprecation_warnings = [] 63 | 64 | # Use default logging formatter so that PID and timestamp are not suppressed. 65 | config.log_formatter = ::Logger::Formatter.new 66 | 67 | # Use a different logger for distributed setups. 68 | # require "syslog/logger" 69 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 70 | 71 | if ENV["RAILS_LOG_TO_STDOUT"].present? 72 | logger = ActiveSupport::Logger.new(STDOUT) 73 | logger.formatter = config.log_formatter 74 | config.logger = ActiveSupport::TaggedLogging.new(logger) 75 | end 76 | 77 | # Compress JavaScripts and CSS. 78 | config.assets.js_compressor = :uglifier 79 | # config.assets.css_compressor = :sass 80 | 81 | # Do not fallback to assets pipeline if a precompiled asset is missed. 82 | config.assets.compile = false 83 | 84 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 85 | # yet still be able to expire them through the digest params. 86 | config.assets.digest = true 87 | 88 | # Do not dump schema after migrations. 89 | config.active_record.dump_schema_after_migration = false 90 | 91 | # Inserts middleware to perform automatic connection switching. 92 | # The `database_selector` hash is used to pass options to the DatabaseSelector 93 | # middleware. The `delay` is used to determine how long to wait after a write 94 | # to send a subsequent read to the primary. 95 | # 96 | # The `database_resolver` class is used by the middleware to determine which 97 | # database is appropriate to use based on the time delay. 98 | # 99 | # The `database_resolver_context` class is used by the middleware to set 100 | # timestamps for the last write to the primary. The resolver uses the context 101 | # class timestamps to determine how long to wait before reading from the 102 | # replica. 103 | # 104 | # By default Rails will store a last write timestamp in the session. The 105 | # DatabaseSelector middleware is designed as such you can define your own 106 | # strategy for connection switching and pass that into the middleware through 107 | # these configuration options. 108 | # config.active_record.database_selector = { delay: 2.seconds } 109 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 110 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 111 | end 112 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | config.cache_classes = false 12 | config.action_view.cache_template_loading = true 13 | 14 | # Do not eager load code on boot. This avoids loading your whole application 15 | # just for the purpose of running a single test. If you are using a tool that 16 | # preloads Rails for running tests, you may have to set it to true. 17 | config.eager_load = false 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Print deprecation notices to the stderr. 37 | config.active_support.deprecation = :stderr 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raises error for missing translations. 46 | # config.i18n.raise_on_missing_translations = true 47 | 48 | # Annotate rendered view with file names. 49 | # config.action_view.annotate_rendered_view_with_filenames = true 50 | end 51 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # 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.0' 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 | 13 | # Precompile additional assets. 14 | # application.js, application.css, and all non-JS/CSS in the app/assets 15 | # folder are already added. 16 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 17 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | # # If you are using webpack-dev-server then specify webpack-dev-server host 15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 16 | 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | 21 | # If you are using UJS then enable automatic nonce generation 22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 23 | 24 | # Set the nonce only to specific directives 25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 26 | 27 | # Report CSP violations to a specified URI 28 | # For further information see the following documentation: 29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 30 | # Rails.application.config.content_security_policy_report_only = true 31 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/dotenv.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Dotenv.require_keys( 4 | 'TWILIO_ACCOUNT_SID', 5 | 'TWILIO_AUTH_TOKEN', 6 | 'TWILIO_NUMBER' 7 | ) 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [ 5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 6 | ] 7 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_gps-tracking-rails_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | get "login/", to: "sessions#login", as: 'login' 4 | get "logout/", to: "sessions#logout" 5 | post "login_attempt/", to: "sessions#login_attempt" 6 | 7 | resources :users, only: [:new, :create, :show] 8 | 9 | resources :vacation_properties, path: "/properties" 10 | resources :reservations, only: [:index, :new, :create] 11 | 12 | post "reservations/incoming", to: 'reservations#accept_or_reject', as: 'incoming' 13 | post "reservations/connect_sms", to: 'reservations#connect_guest_to_host_sms' 14 | post "reservations/connect_voice", to: 'reservations#connect_guest_to_host_voice' 15 | 16 | # Home page 17 | root 'main#index', as: 'home' 18 | 19 | end 20 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 2995cd200a475082070d5ad7b11c69407a6219b0b9bf1f747f62234709506c097da19f731ecf125a3fb53694ee103798d6962c199603b92be8f08b00bf6dbb18 15 | 16 | test: 17 | secret_key_base: deff24bab059bbcceeee98afac8df04814c44dd87d30841f8db532a815b64387d69cfd3d09a78417869c4846f133ba7978068882ca0a96626136ebd084b70732 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3) 2 | timeout 15 3 | preload_app true 4 | 5 | before_fork do |server, worker| 6 | Signal.trap 'TERM' do 7 | puts 'Unicorn master intercepting TERM and sending myself QUIT instead' 8 | Process.kill 'QUIT', Process.pid 9 | end 10 | 11 | defined?(ActiveRecord::Base) and 12 | ActiveRecord::Base.connection.disconnect! 13 | end 14 | 15 | after_fork do |server, worker| 16 | Signal.trap 'TERM' do 17 | puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT' 18 | end 19 | 20 | defined?(ActiveRecord::Base) and 21 | ActiveRecord::Base.establish_connection 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20150219231715_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :users do |t| 4 | t.string :email, null: false 5 | t.string :password_digest, null: false 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150220155337_adds_user_name_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddsUserNameToUsers < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :users, :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150220161843_add_phone_number_and_country_code_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddPhoneNumberAndCountryCodeToUsers < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :users, :phone_number, :string 4 | add_column :users, :country_code, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150624175446_create_vacation_properties.rb: -------------------------------------------------------------------------------- 1 | class CreateVacationProperties < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :vacation_properties do |t| 4 | t.belongs_to :user, index:true 5 | t.string :description 6 | t.string :image_url 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20150624223136_create_reservations.rb: -------------------------------------------------------------------------------- 1 | class CreateReservations < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :reservations do |t| 4 | t.string :name 5 | t.string :guest_phone 6 | t.integer :status, default: 0 7 | t.text :message 8 | t.belongs_to :vacation_property, index:true 9 | 10 | t.timestamps null: false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20150727233817_add_phone_number_to_reservations.rb: -------------------------------------------------------------------------------- 1 | class AddPhoneNumberToReservations < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :reservations, :phone_number, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150728042020_add_area_code_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAreaCodeToUsers < ActiveRecord::Migration[6.1] 2 | def change 3 | add_column :users, :area_code, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2015_07_28_042020) do 14 | 15 | create_table "reservations", force: :cascade do |t| 16 | t.string "name" 17 | t.string "guest_phone" 18 | t.integer "status", default: 0 19 | t.text "message" 20 | t.integer "vacation_property_id" 21 | t.datetime "created_at", null: false 22 | t.datetime "updated_at", null: false 23 | t.string "phone_number" 24 | t.index ["vacation_property_id"], name: "index_reservations_on_vacation_property_id" 25 | end 26 | 27 | create_table "users", force: :cascade do |t| 28 | t.string "email", null: false 29 | t.string "password_digest", null: false 30 | t.datetime "created_at", null: false 31 | t.datetime "updated_at", null: false 32 | t.string "name" 33 | t.string "phone_number" 34 | t.string "country_code" 35 | t.string "area_code" 36 | end 37 | 38 | create_table "vacation_properties", force: :cascade do |t| 39 | t.integer "user_id" 40 | t.string "description" 41 | t.string "image_url" 42 | t.datetime "created_at", null: false 43 | t.datetime "updated_at", null: false 44 | t.index ["user_id"], name: "index_vacation_properties_on_user_id" 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The page you were looking for doesn't exist.

    62 |

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

    63 |
    64 |

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

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

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

    63 |
    64 |

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

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    We're sorry, but something went wrong.

    62 |
    63 |

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

    64 |
    65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/public/favicon.ico -------------------------------------------------------------------------------- /public/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Workflow Automation Tutorial (Airtng app) 10 | 19 | 20 | 87 | 88 | 89 |
    90 | 119 | 120 | 121 | 125 | 126 | 127 | 139 | 140 | 141 | 142 | 143 | 162 | 163 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/controllers/main_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | RSpec.describe MainController, type: :controller do 4 | describe "GET index" do 5 | it "returns a 200 response" do 6 | get :index 7 | expect(response).to be_successful 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/controllers/reservations_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | RSpec.describe ReservationsController, type: :controller do 4 | fixtures :all 5 | 6 | before(:each) do 7 | @host = users(:one) 8 | @guest = users(:two) 9 | @vacation_property = VacationProperty.find(reservation_params[:property_id]) 10 | @reservation = reservations(:two) 11 | # Need to login user to view properties 12 | session[:user_id] = @host.id 13 | end 14 | 15 | after(:each) do 16 | @host.destroy 17 | end 18 | 19 | describe "GET index" do 20 | it "has reservations" do 21 | get :index 22 | expect(response).to be_successful 23 | expect(assigns(:reservations)).not_to be_nil 24 | end 25 | end 26 | 27 | describe "POST accept_or_reject" do 28 | it "accepts reservations" do 29 | VCR.use_cassette("accept_or_reject_reservation", match_requests_on: [:method, :uri_regex]) do 30 | post :accept_or_reject, params: { From: @host.phone_number, Body: "accept"} 31 | 32 | expect(response).to be_successful 33 | reservation = Reservation.find_by(id: @host.id) 34 | expect(reservation.phone_number).to_not be_nil 35 | expect(reservation.status).to eq("confirmed") 36 | end 37 | end 38 | end 39 | 40 | describe "POST create" do 41 | it "redirects to vacation property" do 42 | VCR.use_cassette("create_reservation", match_requests_on: [:method, :uri_regex]) do 43 | post :create, params: { reservation: reservation_params} 44 | expect(response).to have_http_status(:redirect) 45 | expect(response).to redirect_to(@vacation_property) 46 | end 47 | end 48 | end 49 | 50 | describe "POST connect_host_to_guest_sms" do 51 | it "returns a TwiML response" do 52 | post :connect_guest_to_host_sms, params: { From: @guest.phone_number, Body: "message", To: @reservation.phone_number} 53 | 54 | expect(response).to be_successful 55 | expect(response.body).to include("\nmessage\n") 56 | end 57 | end 58 | 59 | describe "POST connect_guest_to_host_sms" do 60 | it "returns a TwiML response" do 61 | post :connect_guest_to_host_sms, params: { From: @host.phone_number, Body: "message", To: @reservation.phone_number} 62 | 63 | expect(response).to be_successful 64 | expect(response.body).to include("\nmessage\n") 65 | end 66 | end 67 | 68 | describe "POST connect_host_to_guest_voice" do 69 | it "returns a TwiML response" do 70 | post :connect_guest_to_host_voice, params: { From: @guest.phone_number, Body: "message", To: @reservation.phone_number} 71 | 72 | expect(response).to be_successful 73 | expect(response.body).to include("\nhttp://howtodocs.s3.amazonaws.com/howdy-tng.mp3\n+16195551234\n") 74 | end 75 | end 76 | 77 | describe "POST connect_guest_to_host_voice" do 78 | it "returns a TwiML response" do 79 | post :connect_guest_to_host_voice, params: { From: @host.phone_number, Body: "message", To: @reservation.phone_number} 80 | 81 | expect(response).to be_successful 82 | expect(response.body).to include("\nhttp://howtodocs.s3.amazonaws.com/howdy-tng.mp3\n+16195559091\n") 83 | end 84 | end 85 | 86 | 87 | end 88 | -------------------------------------------------------------------------------- /spec/controllers/sessions_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'ostruct' 3 | 4 | RSpec.describe SessionsController, type: :controller do 5 | 6 | before(:each) do 7 | @user = User.create(user_params) 8 | end 9 | 10 | after(:each) do 11 | @user.destroy 12 | end 13 | 14 | describe "GET login" do 15 | it "returns a 200" do 16 | get :login 17 | expect(response).to be_successful 18 | end 19 | end 20 | 21 | describe "POST login_attempt" do 22 | it "posts to login_attempt successfully" do 23 | post :login_attempt, params: { email: @user.email, login_password: @user.password } 24 | expect(response).to have_http_status(:redirect) 25 | expect(response).to redirect_to(home_path) 26 | expect(session[:user_id]).to eq(@user.id) 27 | end 28 | 29 | it "posts to login_attempt unsuccessfully" do 30 | post :login_attempt, params: { email: @user.email, login_password: "blah" } 31 | expect(response).to be_successful 32 | expect(response).to render_template(:login) 33 | expect(session[:user_id]).to be_nil 34 | end 35 | end 36 | 37 | describe "GET logout" do 38 | it "redirects to login" do 39 | session["user_id"] = @user.id 40 | assert session["user_id"], "Precondition: user should be logged in" 41 | get :logout 42 | expect(response).to have_http_status(:redirect) 43 | expect(response).to redirect_to(login_path) 44 | expect(session[:user_id]).to be_nil 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'ostruct' 3 | 4 | RSpec.describe UsersController, type: :controller do 5 | describe "GET new" do 6 | it "has new empty user" do 7 | get :new 8 | expect(response).to be_successful 9 | expect(assigns(:user)).to_not be_nil 10 | expect(assigns(:user).new_record?).to be(true) 11 | end 12 | end 13 | 14 | describe "POST create" do 15 | context "when params are valid" do 16 | it "creates a user" do 17 | expect do 18 | post :create, params: { user: user_params } 19 | expect(response).to have_http_status(:redirect) 20 | expect(response).to redirect_to(home_path) 21 | end.to change { User.count } 22 | end 23 | end 24 | 25 | context "when params are not valid" do 26 | it "does not create a user" do 27 | expect do 28 | post :create, params: { user: user_params(email: "blah") } 29 | expect(response).to be_successful 30 | expect(response).to render_template(:new) 31 | expect(assigns(:user)).to_not be_nil 32 | end.to_not change { User.count } 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/controllers/vacation_properties_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | RSpec.describe VacationPropertiesController, type: :controller do 4 | fixtures :vacation_properties 5 | 6 | before(:each) do 7 | @vacation_property = vacation_properties(:one) 8 | @user = User.create(user_params) 9 | # Need to login user to view properties 10 | session[:user_id] = @user.id 11 | end 12 | 13 | after(:each) do 14 | @user.destroy 15 | end 16 | 17 | describe "GET index" do 18 | it "has vacation properties" do 19 | get :index 20 | expect(response).to be_successful 21 | expect(assigns(:vacation_properties)).to_not be_nil 22 | end 23 | end 24 | 25 | describe "GET new" do 26 | it "has a new empty vacation property" do 27 | get :new 28 | expect(response).to be_successful 29 | expect(assigns(:vacation_property)).to_not be_nil 30 | expect(assigns(:vacation_property).new_record?).to be(true) 31 | end 32 | end 33 | 34 | describe "POST create" do 35 | it "creates a new vacation property" do 36 | expect do 37 | session[:user_id] = @user.id 38 | post :create, params: { vacation_property: { description: @vacation_property.description, image_url: @vacation_property.image_url } } 39 | end.to change { VacationProperty.count } 40 | expect(response).to redirect_to(vacation_property_path(assigns(:vacation_property))) 41 | end 42 | end 43 | 44 | describe "GET show" do 45 | it "returns a 200" do 46 | get :show, params: { id: @vacation_property } 47 | expect(response).to be_successful 48 | end 49 | end 50 | 51 | describe "GET edit" do 52 | it "returns a 200" do 53 | get :edit, params: { id: @vacation_property } 54 | expect(response).to be_successful 55 | end 56 | end 57 | 58 | describe "PATCH vacation_property" do 59 | it "updates and redirects" do 60 | patch :update, params: { id: @vacation_property, vacation_property: { description: @vacation_property.description, image_url: @vacation_property.image_url } } 61 | expect(response).to redirect_to(vacation_property_path(assigns(:vacation_property))) 62 | end 63 | end 64 | 65 | describe "DELETE vacation_property" do 66 | it "destroys and redirects" do 67 | expect do 68 | delete :destroy, params: { id: @vacation_property } 69 | end.to change { VacationProperty.count }.by(-1) 70 | 71 | expect(response).to redirect_to(vacation_properties_path) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/spec/fixtures/.keep -------------------------------------------------------------------------------- /spec/fixtures/reservations.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | id: 1 5 | name: Jardo 6 | status: 0 7 | message: Is the universe safe there? 8 | guest_phone: '+16195559091' 9 | phone_number: '+100343233' 10 | vacation_property_id: 1 11 | 12 | two: 13 | id: 2 14 | name: Kevin 15 | status: 1 16 | message: Looking forward to testing your space couch. 17 | guest_phone: '+16195551234' 18 | phone_number: '+100112233' 19 | vacation_property_id: 2 20 | -------------------------------------------------------------------------------- /spec/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | id: 1 5 | email: blah@blah.com 6 | password_digest: secret 7 | name: blah 8 | country_code: +1 9 | phone_number: '+16195551234' 10 | area_code: 619 11 | 12 | two: 13 | id: 2 14 | email: hello@hello.com 15 | password_digest: secret 16 | name: blah 17 | country_code: +1 18 | phone_number: '+16195559091' 19 | area_code: 619 20 | -------------------------------------------------------------------------------- /spec/fixtures/vacation_properties.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | id: 1 5 | description: Data's Test Suite 6 | image_url: http://example.com/starship.png 7 | user_id: 1 8 | 9 | two: 10 | id: 2 11 | description: Picard's Assertion 12 | image_url: http://example.com/picard.jpg 13 | user_id: 2 14 | -------------------------------------------------------------------------------- /spec/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/spec/models/.keep -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | RSpec.describe User, type: :model do 4 | fixtures :users 5 | 6 | context "without email" do 7 | it "is not valid" do 8 | expect(User.new(user_params(email: nil)).valid?).to be_falsey 9 | end 10 | end 11 | 12 | context "with invalid email" do 13 | it "is not valid" do 14 | expect(User.new(user_params(email: "blah")).valid?).to be_falsey 15 | end 16 | end 17 | 18 | context "with valid email" do 19 | it "is valid" do 20 | expect(User.new(user_params).valid?).to be_truthy 21 | end 22 | end 23 | 24 | context "with duplicated email" do 25 | it "is not valid" do 26 | user = users(:one) 27 | user2 = User.new(user_params(email: user.email)) 28 | expect(user2.valid?).to be_falsey 29 | end 30 | end 31 | 32 | context "with duplicated phone number" do 33 | it "is not valid" do 34 | user = users(:one) 35 | user2 = User.new(user_params(phone_number: user.phone_number)) 36 | expect(user2.valid?).to be_falsey 37 | end 38 | end 39 | 40 | context "with invalid password" do 41 | it "is not valid" do 42 | expect(User.new(user_params(password: "root")).valid?).to be_falsey 43 | end 44 | end 45 | 46 | context "with valid password" do 47 | it "is valid" do 48 | expect(User.new(user_params).valid?).to be_truthy 49 | end 50 | end 51 | 52 | context "without password" do 53 | it "is not valid" do 54 | expect(User.new(user_params(password: nil)).valid?).to be_falsey 55 | end 56 | end 57 | 58 | context "without name" do 59 | it "is not valid" do 60 | expect(User.new(user_params(name: nil)).valid?).to be_falsey 61 | end 62 | end 63 | 64 | context "without country code" do 65 | it "is not valid" do 66 | expect(User.new(user_params(country_code: nil)).valid?).to be_falsey 67 | end 68 | end 69 | 70 | context "without phone number" do 71 | it "is not valid" do 72 | expect(User.new(user_params(phone_number: nil)).valid?).to be_falsey 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../config/environment', __dir__) 5 | # Prevent database truncation if the environment is production 6 | abort("The Rails environment is running in production mode!") if Rails.env.production? 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove these lines. 27 | begin 28 | ActiveRecord::Migration.maintain_test_schema! 29 | rescue ActiveRecord::PendingMigrationError => e 30 | puts e.to_s.strip 31 | exit 1 32 | end 33 | RSpec.configure do |config| 34 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 35 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 36 | 37 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 38 | # examples within a transaction, remove the following line or assign false 39 | # instead of true. 40 | config.use_transactional_fixtures = true 41 | 42 | # You can uncomment this line to turn off ActiveRecord support entirely. 43 | # config.use_active_record = false 44 | 45 | # RSpec Rails can automatically mix in different behaviours to your tests 46 | # based on their file location, for example enabling you to call `get` and 47 | # `post` in specs under `spec/controllers`. 48 | # 49 | # You can disable this behaviour by removing the line below, and instead 50 | # explicitly tag your specs with their type, e.g.: 51 | # 52 | # RSpec.describe UsersController, type: :controller do 53 | # # ... 54 | # end 55 | # 56 | # The different available types are documented in the features, such as in 57 | # https://relishapp.com/rspec/rspec-rails/docs 58 | config.infer_spec_type_from_file_location! 59 | 60 | # Filter lines from Rails gems in backtraces. 61 | config.filter_rails_from_backtrace! 62 | # arbitrary gems may also be filtered via: 63 | # config.filter_gems_from_backtrace("gem name") 64 | end 65 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | =begin 50 | # This allows you to limit a spec run to individual examples or groups 51 | # you care about by tagging them with `:focus` metadata. When nothing 52 | # is tagged with `:focus`, all examples get run. RSpec also provides 53 | # aliases for `it`, `describe`, and `context` that include `:focus` 54 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 55 | config.filter_run_when_matching :focus 56 | 57 | # Allows RSpec to persist some state between runs in order to support 58 | # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # you configure your source control system to ignore this file. 60 | config.example_status_persistence_file_path = "spec/examples.txt" 61 | 62 | # Limits the available syntax to the non-monkey patched syntax that is 63 | # recommended. For more details, see: 64 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 65 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 67 | config.disable_monkey_patching! 68 | 69 | # Many RSpec users commonly either run the entire suite or an individual 70 | # file, and it's useful to allow more verbose output when running an 71 | # individual spec file. 72 | if config.files_to_run.one? 73 | # Use the documentation formatter for detailed output, 74 | # unless a formatter has already been configured 75 | # (e.g. via a command-line flag). 76 | config.default_formatter = "doc" 77 | end 78 | 79 | # Print the 10 slowest examples and example groups at the 80 | # end of the spec run, to help surface which specs are running 81 | # particularly slow. 82 | config.profile_examples = 10 83 | 84 | # Run specs in random order to surface order dependencies. If you find an 85 | # order dependency and want to debug it, you can fix the order by providing 86 | # the seed, which is printed after each run. 87 | # --seed 1234 88 | config.order = :random 89 | 90 | # Seed global randomization in this process using the `--seed` CLI option. 91 | # Setting this allows you to use `--seed` to deterministically reproduce 92 | # test failures related to randomization by passing the same `--seed` value 93 | # as the one that triggered the failure. 94 | Kernel.srand config.seed 95 | =end 96 | end 97 | -------------------------------------------------------------------------------- /spec/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'vcr' 3 | 4 | VCR.configure do |configure| 5 | configure.cassette_library_dir = "spec/vcr_cassettes" 6 | configure.hook_into :webmock 7 | configure.register_request_matcher :uri_regex do |request1, request2| 8 | request1.uri.match(request2.uri) 9 | end 10 | end 11 | 12 | module Params 13 | # Add more helper methods to be used by all tests here... 14 | 15 | def user_params(params={}) 16 | { 17 | password: "hello55", 18 | email: "jard@example.com", 19 | name: "Jard", 20 | phone_number: "6195559090", 21 | country_code: "+1" 22 | }.merge(params) 23 | end 24 | 25 | def reservation_params() 26 | { 27 | name: "reservation1", 28 | guest_phone: "6195559090", 29 | message: "message1", 30 | property_id: 1, 31 | } 32 | end 33 | end 34 | 35 | RSpec.configure do |c| 36 | c.include Params 37 | end 38 | -------------------------------------------------------------------------------- /spec/vcr_cassettes/accept_or_reject_reservation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: !ruby/regexp /^https:\/\/api\.twilio\.com\/2010-04-01\/Accounts\/.*\/AvailablePhoneNumbers\/US\/Local\.json\?AreaCode=619/ 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - twilio-ruby/5.0.0.rc17 (ruby/x86_64-linux 2.2.1-p85) 12 | Accept-Charset: 13 | - utf-8 14 | Accept: 15 | - application/json 16 | Authorization: 17 | - Basic QUM3NzE5NTg3ZGasdfsdfasdfdsxYzk5Y2YxNGQ0OTVkYTM0NTowOWVjZTRjafasdfasdfsdafM4MTU4ZA== 18 | Accept-Encoding: 19 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Access-Control-Allow-Credentials: 26 | - 'true' 27 | Access-Control-Allow-Headers: 28 | - Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, 29 | If-Unmodified-Since 30 | Access-Control-Allow-Methods: 31 | - GET, POST, DELETE, OPTIONS 32 | Access-Control-Allow-Origin: 33 | - "*" 34 | Access-Control-Expose-Headers: 35 | - ETag 36 | Content-Type: 37 | - application/json; charset=utf-8 38 | Date: 39 | - Thu, 29 Jun 2017 12:41:44 GMT 40 | Strict-Transport-Security: 41 | - max-age=15768000 42 | Twilio-Request-Duration: 43 | - '0.237' 44 | Twilio-Request-Id: 45 | - RQ45a4fd3bc28b448f86a372538e31caa2 46 | X-Powered-By: 47 | - AT-5000 48 | X-Shenanigans: 49 | - none 50 | Content-Length: 51 | - '11227' 52 | Connection: 53 | - keep-alive 54 | body: 55 | encoding: UTF-8 56 | string: '{"available_phone_numbers": [{"friendly_name": "(619) 632-4285", "phone_number": 57 | "+16196324285", "lata": "732", "rate_center": "CHULAVISTA", "latitude": "32.640100", 58 | "longitude": "-117.084200", "locality": "Chula Vista", "region": "CA", "postal_code": 59 | "91909", "iso_country": "US", "address_requirements": "none", "beta": false, 60 | "capabilities": {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 61 | "(619) 361-7754", "phone_number": "+16193617754", "lata": "732", "rate_center": 62 | "CHULAVISTA", "latitude": "32.640100", "longitude": "-117.084200", "locality": 63 | "Chula Vista", "region": "CA", "postal_code": "91909", "iso_country": "US", 64 | "address_requirements": "none", "beta": false, "capabilities": {"voice": true, 65 | "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": "(619) 361-7350", 66 | "phone_number": "+16193617350", "lata": "732", "rate_center": "CHULAVISTA", 67 | "latitude": "32.640100", "longitude": "-117.084200", "locality": "Chula Vista", 68 | "region": "CA", "postal_code": "91909", "iso_country": "US", "address_requirements": 69 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 70 | true, "fax": true}}, {"friendly_name": "(619) 900-6861", "phone_number": "+16199006861", 71 | "lata": "732", "rate_center": "NATIONALCY", "latitude": "32.678100", "longitude": 72 | "-117.099200", "locality": "National City", "region": "CA", "postal_code": 73 | "91950", "iso_country": "US", "address_requirements": "none", "beta": false, 74 | "capabilities": {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 75 | "(619) 319-9344", "phone_number": "+16193199344", "lata": "732", "rate_center": 76 | "CORONADO", "latitude": "32.685900", "longitude": "-117.183100", "locality": 77 | "Coronado", "region": "CA", "postal_code": "92118", "iso_country": "US", "address_requirements": 78 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 79 | true, "fax": true}}, {"friendly_name": "(619) 319-9576", "phone_number": "+16193199576", 80 | "lata": "732", "rate_center": "CORONADO", "latitude": "32.685900", "longitude": 81 | "-117.183100", "locality": "Coronado", "region": "CA", "postal_code": "92118", 82 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 83 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 84 | "(619) 319-9669", "phone_number": "+16193199669", "lata": "732", "rate_center": 85 | "CORONADO", "latitude": "32.685900", "longitude": "-117.183100", "locality": 86 | "Coronado", "region": "CA", "postal_code": "92118", "iso_country": "US", "address_requirements": 87 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 88 | true, "fax": true}}, {"friendly_name": "(619) 361-7658", "phone_number": "+16193617658", 89 | "lata": "732", "rate_center": "CHULAVISTA", "latitude": "32.640100", "longitude": 90 | "-117.084200", "locality": "Chula Vista", "region": "CA", "postal_code": "91909", 91 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 92 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 93 | "(619) 916-4821", "phone_number": "+16199164821", "lata": "732", "rate_center": 94 | "LA MESA", "latitude": "32.767800", "longitude": "-117.023100", "locality": 95 | "La Mesa", "region": "CA", "postal_code": "91941", "iso_country": "US", "address_requirements": 96 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 97 | true, "fax": true}}, {"friendly_name": "(619) 568-2107", "phone_number": "+16195682107", 98 | "lata": "732", "rate_center": "CORONADO", "latitude": "32.685900", "longitude": 99 | "-117.183100", "locality": "Coronado", "region": "CA", "postal_code": "92118", 100 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 101 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 102 | "(619) 319-9605", "phone_number": "+16193199605", "lata": "732", "rate_center": 103 | "CORONADO", "latitude": "32.685900", "longitude": "-117.183100", "locality": 104 | "Coronado", "region": "CA", "postal_code": "92118", "iso_country": "US", "address_requirements": 105 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 106 | true, "fax": true}}, {"friendly_name": "(619) 345-5204", "phone_number": "+16193455204", 107 | "lata": "732", "rate_center": "DULZURA", "latitude": "32.628300", "longitude": 108 | "-116.675100", "locality": "Dulzura", "region": "CA", "postal_code": "91917", 109 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 110 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 111 | "(619) 916-4560", "phone_number": "+16199164560", "lata": "732", "rate_center": 112 | "LA MESA", "latitude": "32.767800", "longitude": "-117.023100", "locality": 113 | "La Mesa", "region": "CA", "postal_code": "91941", "iso_country": "US", "address_requirements": 114 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 115 | true, "fax": true}}, {"friendly_name": "(619) 319-9029", "phone_number": "+16193199029", 116 | "lata": "732", "rate_center": "CORONADO", "latitude": "32.685900", "longitude": 117 | "-117.183100", "locality": "Coronado", "region": "CA", "postal_code": "92118", 118 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 119 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 120 | "(619) 789-6945", "phone_number": "+16197896945", "lata": "732", "rate_center": 121 | "NATIONALCY", "latitude": "32.678100", "longitude": "-117.099200", "locality": 122 | "National City", "region": "CA", "postal_code": "91950", "iso_country": "US", 123 | "address_requirements": "none", "beta": false, "capabilities": {"voice": true, 124 | "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": "(619) 568-2054", 125 | "phone_number": "+16195682054", "lata": "732", "rate_center": "CORONADO", 126 | "latitude": "32.685900", "longitude": "-117.183100", "locality": "Coronado", 127 | "region": "CA", "postal_code": "92118", "iso_country": "US", "address_requirements": 128 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 129 | true, "fax": true}}, {"friendly_name": "(619) 345-5250", "phone_number": "+16193455250", 130 | "lata": "732", "rate_center": "DULZURA", "latitude": "32.628300", "longitude": 131 | "-116.675100", "locality": "Dulzura", "region": "CA", "postal_code": "91917", 132 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 133 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 134 | "(619) 361-7434", "phone_number": "+16193617434", "lata": "732", "rate_center": 135 | "CHULAVISTA", "latitude": "32.640100", "longitude": "-117.084200", "locality": 136 | "Chula Vista", "region": "CA", "postal_code": "91909", "iso_country": "US", 137 | "address_requirements": "none", "beta": false, "capabilities": {"voice": true, 138 | "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": "(619) 319-9870", 139 | "phone_number": "+16193199870", "lata": "732", "rate_center": "CORONADO", 140 | "latitude": "32.685900", "longitude": "-117.183100", "locality": "Coronado", 141 | "region": "CA", "postal_code": "92118", "iso_country": "US", "address_requirements": 142 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 143 | true, "fax": true}}, {"friendly_name": "(619) 345-5338", "phone_number": "+16193455338", 144 | "lata": "732", "rate_center": "DULZURA", "latitude": "32.628300", "longitude": 145 | "-116.675100", "locality": "Dulzura", "region": "CA", "postal_code": "91917", 146 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 147 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 148 | "(619) 345-5196", "phone_number": "+16193455196", "lata": "732", "rate_center": 149 | "DULZURA", "latitude": "32.628300", "longitude": "-116.675100", "locality": 150 | "Dulzura", "region": "CA", "postal_code": "91917", "iso_country": "US", "address_requirements": 151 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 152 | true, "fax": true}}, {"friendly_name": "(619) 319-9849", "phone_number": "+16193199849", 153 | "lata": "732", "rate_center": "CORONADO", "latitude": "32.685900", "longitude": 154 | "-117.183100", "locality": "Coronado", "region": "CA", "postal_code": "92118", 155 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 156 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 157 | "(619) 825-2349", "phone_number": "+16198252349", "lata": "732", "rate_center": 158 | "LA MESA", "latitude": "32.767800", "longitude": "-117.023100", "locality": 159 | "La Mesa", "region": "CA", "postal_code": "91941", "iso_country": "US", "address_requirements": 160 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 161 | true, "fax": true}}, {"friendly_name": "(619) 361-7681", "phone_number": "+16193617681", 162 | "lata": "732", "rate_center": "CHULAVISTA", "latitude": "32.640100", "longitude": 163 | "-117.084200", "locality": "Chula Vista", "region": "CA", "postal_code": "91909", 164 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 165 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 166 | "(619) 319-9655", "phone_number": "+16193199655", "lata": "732", "rate_center": 167 | "CORONADO", "latitude": "32.685900", "longitude": "-117.183100", "locality": 168 | "Coronado", "region": "CA", "postal_code": "92118", "iso_country": "US", "address_requirements": 169 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 170 | true, "fax": true}}, {"friendly_name": "(619) 568-2077", "phone_number": "+16195682077", 171 | "lata": "732", "rate_center": "CORONADO", "latitude": "32.685900", "longitude": 172 | "-117.183100", "locality": "Coronado", "region": "CA", "postal_code": "92118", 173 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 174 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 175 | "(619) 632-4294", "phone_number": "+16196324294", "lata": "732", "rate_center": 176 | "CHULAVISTA", "latitude": "32.640100", "longitude": "-117.084200", "locality": 177 | "Chula Vista", "region": "CA", "postal_code": "91909", "iso_country": "US", 178 | "address_requirements": "none", "beta": false, "capabilities": {"voice": true, 179 | "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": "(619) 361-7390", 180 | "phone_number": "+16193617390", "lata": "732", "rate_center": "CHULAVISTA", 181 | "latitude": "32.640100", "longitude": "-117.084200", "locality": "Chula Vista", 182 | "region": "CA", "postal_code": "91909", "iso_country": "US", "address_requirements": 183 | "none", "beta": false, "capabilities": {"voice": true, "SMS": true, "MMS": 184 | true, "fax": true}}, {"friendly_name": "(619) 916-4970", "phone_number": "+16199164970", 185 | "lata": "732", "rate_center": "LA MESA", "latitude": "32.767800", "longitude": 186 | "-117.023100", "locality": "La Mesa", "region": "CA", "postal_code": "91941", 187 | "iso_country": "US", "address_requirements": "none", "beta": false, "capabilities": 188 | {"voice": true, "SMS": true, "MMS": true, "fax": true}}, {"friendly_name": 189 | "(619) 361-7404", "phone_number": "+16193617404", "lata": "732", "rate_center": 190 | "CHULAVISTA", "latitude": "32.640100", "longitude": "-117.084200", "locality": 191 | "Chula Vista", "region": "CA", "postal_code": "91909", "iso_country": "US", 192 | "address_requirements": "none", "beta": false, "capabilities": {"voice": true, 193 | "SMS": true, "MMS": true, "fax": true}}], "uri": "/2010-04-01/Accounts//AvailablePhoneNumbers/US/Local.json?AreaCode=619"}' 195 | http_version: 196 | recorded_at: Thu, 29 Jun 2017 12:41:44 GMT 197 | - request: 198 | method: post 199 | uri: !ruby/regexp /^https:\/\/api\.twilio\.com\/2010-04-01\/Accounts\/.*\/IncomingPhoneNumbers\.json/ 200 | body: 201 | encoding: UTF-8 202 | string: PhoneNumber=%2B16196324285 203 | headers: 204 | User-Agent: 205 | - twilio-ruby/5.0.0.rc17 (ruby/x86_64-linux 2.2.1-p85) 206 | Accept-Charset: 207 | - utf-8 208 | Content-Type: 209 | - application/x-www-form-urlencoded 210 | Accept: 211 | - application/json 212 | Authorization: 213 | - Basic QUM3NzE5NTg3ZGasdfsdfasdfdsxYzk5Y2YxNGQ0OTVkYTM0NTowOWVjZTRjafasdfasdfsdafM4MTU4ZA== 214 | Accept-Encoding: 215 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 216 | response: 217 | status: 218 | code: 201 219 | message: CREATED 220 | headers: 221 | Access-Control-Allow-Credentials: 222 | - 'true' 223 | Access-Control-Allow-Headers: 224 | - Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, 225 | If-Unmodified-Since 226 | Access-Control-Allow-Methods: 227 | - GET, POST, DELETE, OPTIONS 228 | Access-Control-Allow-Origin: 229 | - "*" 230 | Access-Control-Expose-Headers: 231 | - ETag 232 | Content-Type: 233 | - application/json; charset=utf-8 234 | Date: 235 | - Thu, 29 Jun 2017 12:41:45 GMT 236 | Strict-Transport-Security: 237 | - max-age=15768000 238 | Twilio-Request-Duration: 239 | - '0.851' 240 | Twilio-Request-Id: 241 | - RQ06c12853216c4a97a40f095f53c12fc6 242 | X-Powered-By: 243 | - AT-5000 244 | X-Shenanigans: 245 | - none 246 | Content-Length: 247 | - '1019' 248 | Connection: 249 | - keep-alive 250 | body: 251 | encoding: UTF-8 252 | string: '{"sid": "PN35e7de5f293f47c5bc3a622cdf19f13d", "account_sid": "", "friendly_name": "(619) 632-4285", "phone_number": "+16196324285", 254 | "voice_url": null, "voice_method": "POST", "voice_fallback_url": null, "voice_fallback_method": 255 | "POST", "voice_caller_id_lookup": false, "date_created": "Thu, 29 Jun 2017 256 | 12:41:45 +0000", "date_updated": "Thu, 29 Jun 2017 12:41:45 +0000", "sms_url": 257 | "", "sms_method": "POST", "sms_fallback_url": "", "sms_fallback_method": "POST", 258 | "address_requirements": "none", "beta": false, "capabilities": {"voice": true, 259 | "sms": true, "mms": true, "fax": true}, "voice_receive_mode": "voice", "status_callback": 260 | "", "status_callback_method": "POST", "api_version": "2010-04-01", "voice_application_sid": 261 | null, "sms_application_sid": "", "origin": "twilio", "trunk_sid": null, "emergency_status": 262 | "Inactive", "emergency_address_sid": null, "uri": "/2010-04-01/Accounts//IncomingPhoneNumbers/PN35e7de5f293f47c5bc3a622cdf19f13d.json"}' 264 | http_version: 265 | recorded_at: Thu, 29 Jun 2017 12:41:45 GMT 266 | - request: 267 | method: post 268 | uri: !ruby/regexp /^https:\/\/api\.twilio\.com\/2010-04-01\/Accounts\/.*\/Messages.json/ 269 | body: 270 | encoding: UTF-8 271 | string: Body=You+have+a+new+reservation+request+from+Kevin+for+Data%27s+Test+Suite%3A%0A%0A++++++%27Looking+forward+to+testing+your+space+couch.%27%0A%0A++++++Reply+%5Baccept%5D+or+%5Breject%5D.&From=%2B14049476065&To=5551234 272 | headers: 273 | User-Agent: 274 | - twilio-ruby/5.0.0.rc17 (ruby/x86_64-linux 2.2.1-p85) 275 | Accept-Charset: 276 | - utf-8 277 | Content-Type: 278 | - application/x-www-form-urlencoded 279 | Accept: 280 | - application/json 281 | Authorization: 282 | - Basic QUM3NzE5NTg3ZGasdfsdfasdfdsxYzk5Y2YxNGQ0OTVkYTM0NTowOWVjZTRjafasdfasdfsdafM4MTU4ZA== 283 | Accept-Encoding: 284 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 285 | response: 286 | status: 287 | code: 201 288 | message: BAD REQUEST 289 | headers: 290 | Access-Control-Allow-Credentials: 291 | - 'true' 292 | Access-Control-Allow-Headers: 293 | - Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, 294 | If-Unmodified-Since 295 | Access-Control-Allow-Methods: 296 | - GET, POST, DELETE, OPTIONS 297 | Access-Control-Allow-Origin: 298 | - "*" 299 | Access-Control-Expose-Headers: 300 | - ETag 301 | Content-Type: 302 | - application/json; charset=utf-8 303 | Date: 304 | - Thu, 29 Jun 2017 16:52:14 GMT 305 | Twilio-Request-Duration: 306 | - '0.059' 307 | Twilio-Request-Id: 308 | - RQ5369c5215e13464f91abf582eb6040be 309 | X-Powered-By: 310 | - AT-5000 311 | X-Shenanigans: 312 | - none 313 | Content-Length: 314 | - '154' 315 | Connection: 316 | - keep-alive 317 | body: 318 | encoding: UTF-8 319 | string: '{ 320 | "sid": "MMc781610ec0b3400c9e0cab8e757da937", 321 | "date_created": "Mon, 19 Oct 2015 07:07:03 +0000", 322 | "date_updated": "Mon, 19 Oct 2015 07:07:03 +0000", 323 | "date_sent": null, 324 | "account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 325 | "to": "+15558675309", 326 | "from": "+15017250604", 327 | "body": "This is the ship that made the Kessel Run in fourteen parsecs?", 328 | "status": "queued", 329 | "num_segments": "1", 330 | "num_media": "1", 331 | "direction": "outbound-api", 332 | "api_version": "2010-04-01", 333 | "price": null, 334 | "price_unit": "USD", 335 | "error_code": null, 336 | "error_message": null, 337 | "uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages/MMc781610ec0b3400c9e0cab8e757da937.json", 338 | "subresource_uris": { 339 | "media": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages/MMc781610ec0b3400c9e0cab8e757da937/Media.json" 340 | }}' 341 | http_version: 342 | recorded_at: Thu, 29 Jun 2017 16:52:14 GMT 343 | recorded_with: VCR 3.0.3 344 | -------------------------------------------------------------------------------- /spec/vcr_cassettes/create_reservation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: !ruby/regexp /^https:\/\/api\.twilio\.com\/2010-04-01\/Accounts\/.*\/Messages\.json/ 6 | body: 7 | encoding: UTF-8 8 | string: Body=You+have+a+new+reservation+request+from+Jardo+for+Data%27s+Test+Suite%3A%0A%0A++++++%27Is+the+universe+safe+there%3F%27%0A%0A++++++Reply+%5Baccept%5D+or+%5Breject%5D.&From=%2B14049476065&To=%2B16195551234 9 | headers: 10 | User-Agent: 11 | - twilio-ruby/5.0.0.rc17 (ruby/x86_64-linux 2.2.1-p85) 12 | Accept-Charset: 13 | - utf-8 14 | Content-Type: 15 | - application/x-www-form-urlencoded 16 | Accept: 17 | - application/json 18 | Authorization: 19 | - Basic QUM3NzE5NTg3ZGasdfsdfasdfdsxYzk5Y2YxNGQ0OTVkYTM0NTowOWVjZTRjafasdfasdfsdafM4MTU4ZA== 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | response: 23 | status: 24 | code: 201 25 | message: CREATED 26 | headers: 27 | Access-Control-Allow-Credentials: 28 | - 'true' 29 | Access-Control-Allow-Headers: 30 | - Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, 31 | If-Unmodified-Since 32 | Access-Control-Allow-Methods: 33 | - GET, POST, DELETE, OPTIONS 34 | Access-Control-Allow-Origin: 35 | - "*" 36 | Access-Control-Expose-Headers: 37 | - ETag 38 | Content-Type: 39 | - application/json; charset=utf-8 40 | Date: 41 | - Thu, 29 Jun 2017 17:43:38 GMT 42 | Strict-Transport-Security: 43 | - max-age=15768000 44 | Twilio-Request-Duration: 45 | - '0.215' 46 | Twilio-Request-Id: 47 | - RQ338ab140d27d4072b3146ec4533834d4 48 | X-Powered-By: 49 | - AT-5000 50 | X-Shenanigans: 51 | - none 52 | Content-Length: 53 | - '908' 54 | Connection: 55 | - keep-alive 56 | body: 57 | encoding: UTF-8 58 | string: '{"sid": "SM164c4dac645543518ce80cbebe4fe377", "date_created": "Thu, 59 | 29 Jun 2017 17:43:38 +0000", "date_updated": "Thu, 29 Jun 2017 17:43:38 +0000", 60 | "date_sent": null, "account_sid": "", "to": "+16195551234", 61 | "from": "", "messaging_service_sid": null, "body": "You have 62 | a new reservation request from Jardo for Data''s Test Suite:\n\n ''Is 63 | the universe safe there?''\n\n Reply [accept] or [reject].", "status": 64 | "queued", "num_segments": "1", "num_media": "0", "direction": "outbound-api", 65 | "api_version": "2010-04-01", "price": null, "price_unit": "USD", "error_code": 66 | null, "error_message": null, "uri": "/2010-04-01/Accounts//Messages/SM164c4dac645543518ce80cbebe4fe377.json", "subresource_uris": 68 | {"media": "/2010-04-01/Accounts//Messages/SM164c4dac645543518ce80cbebe4fe377/Media.json"}}' 69 | recorded_at: Thu, 29 Jun 2017 17:43:38 GMT 70 | recorded_with: VCR 6.0.0 71 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/tmp/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/anonymous-communications-rails/f97f55a63db17e0ed79c58e0a68773857a2238ec/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------